mirror of
https://github.com/Sea-Haven-Industries/open-swe.git
synced 2026-10-05 21:12:13 +00:00
add logging (#1248)
This commit is contained in:
parent
6ded489751
commit
da74342da4
3 changed files with 204 additions and 2 deletions
|
|
@ -319,6 +319,15 @@ def _is_not_found_error(exc: Exception) -> bool:
|
||||||
return getattr(exc, "status_code", None) == 404
|
return getattr(exc, "status_code", None) == 404
|
||||||
|
|
||||||
|
|
||||||
|
def _run_id_for_logging(run: Any) -> str:
|
||||||
|
"""Extract a run id from SDK response shapes for log messages."""
|
||||||
|
if isinstance(run, dict):
|
||||||
|
run_id = run.get("run_id")
|
||||||
|
else:
|
||||||
|
run_id = getattr(run, "run_id", None)
|
||||||
|
return run_id if isinstance(run_id, str) and run_id else "<unknown>"
|
||||||
|
|
||||||
|
|
||||||
def _is_repo_org_allowed(repo_config: dict[str, str]) -> bool:
|
def _is_repo_org_allowed(repo_config: dict[str, str]) -> bool:
|
||||||
"""Check if the repo owner/org is in the allowlist.
|
"""Check if the repo owner/org is in the allowlist.
|
||||||
|
|
||||||
|
|
@ -873,13 +882,19 @@ async def process_slack_mention(event_data: dict[str, Any], repo_config: dict[st
|
||||||
logger.error("Failed to queue Slack message for thread %s", thread_id)
|
logger.error("Failed to queue Slack message for thread %s", thread_id)
|
||||||
return
|
return
|
||||||
|
|
||||||
await langgraph_client.runs.create(
|
logger.info("Creating Slack LangGraph run for thread %s", thread_id)
|
||||||
|
run = await langgraph_client.runs.create(
|
||||||
thread_id,
|
thread_id,
|
||||||
"agent",
|
"agent",
|
||||||
input={"messages": [{"role": "user", "content": content_blocks}]},
|
input={"messages": [{"role": "user", "content": content_blocks}]},
|
||||||
config={"configurable": configurable, "metadata": _AGENT_VERSION_METADATA},
|
config={"configurable": configurable, "metadata": _AGENT_VERSION_METADATA},
|
||||||
if_not_exists="create",
|
if_not_exists="create",
|
||||||
multitask_strategy="interrupt",
|
multitask_strategy="enqueue",
|
||||||
|
)
|
||||||
|
logger.info(
|
||||||
|
"Slack LangGraph run %s created for thread %s",
|
||||||
|
_run_id_for_logging(run),
|
||||||
|
thread_id,
|
||||||
)
|
)
|
||||||
await post_slack_trace_reply(channel_id, thread_ts, thread_id)
|
await post_slack_trace_reply(channel_id, thread_ts, thread_id)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -530,6 +530,61 @@ def test_slack_webhook_non_pr_review_request_starts_agent(monkeypatch) -> None:
|
||||||
assert event_data["text"] == "<@UBOT> review this branch"
|
assert event_data["text"] == "<@UBOT> review this branch"
|
||||||
|
|
||||||
|
|
||||||
|
def test_slack_webhook_threaded_followup_uses_parent_thread_ts(monkeypatch) -> None:
|
||||||
|
captured: dict[str, object] = {}
|
||||||
|
|
||||||
|
async def fake_get_slack_repo_config(
|
||||||
|
text: str, channel_id: str, thread_ts: str
|
||||||
|
) -> dict[str, str]:
|
||||||
|
captured["repo_config_request"] = {
|
||||||
|
"text": text,
|
||||||
|
"channel_id": channel_id,
|
||||||
|
"thread_ts": thread_ts,
|
||||||
|
}
|
||||||
|
return {"owner": "langchain-ai", "name": "open-swe"}
|
||||||
|
|
||||||
|
async def fake_process_slack_mention(
|
||||||
|
event_data: dict[str, object], repo_config: dict[str, str]
|
||||||
|
) -> None:
|
||||||
|
captured["event_data"] = event_data
|
||||||
|
captured["repo_config"] = repo_config
|
||||||
|
|
||||||
|
monkeypatch.setattr(webapp, "SLACK_SIGNING_SECRET", _TEST_SLACK_SECRET)
|
||||||
|
monkeypatch.setattr(webapp, "SLACK_BOT_USER_ID", "UBOT")
|
||||||
|
monkeypatch.setattr(webapp, "SLACK_BOT_USERNAME", "open-swe")
|
||||||
|
monkeypatch.setattr(slack_utils.time, "time", lambda: 1700000000)
|
||||||
|
monkeypatch.setattr(webapp, "get_slack_repo_config", fake_get_slack_repo_config)
|
||||||
|
monkeypatch.setattr(webapp, "process_slack_mention", fake_process_slack_mention)
|
||||||
|
|
||||||
|
client = TestClient(webapp.app)
|
||||||
|
response = _post_slack_webhook(
|
||||||
|
client,
|
||||||
|
{
|
||||||
|
"type": "event_callback",
|
||||||
|
"event": {
|
||||||
|
"type": "app_mention",
|
||||||
|
"channel": "C123",
|
||||||
|
"ts": "1700000000.000200",
|
||||||
|
"thread_ts": "1700000000.000100",
|
||||||
|
"user": "U123",
|
||||||
|
"text": "<@UBOT> continue on the branch",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response.json()["message"] == "Slack mention queued"
|
||||||
|
assert captured["repo_config_request"] == {
|
||||||
|
"text": "<@UBOT> continue on the branch",
|
||||||
|
"channel_id": "C123",
|
||||||
|
"thread_ts": "1700000000.000100",
|
||||||
|
}
|
||||||
|
event_data = captured["event_data"]
|
||||||
|
assert isinstance(event_data, dict)
|
||||||
|
assert event_data["thread_ts"] == "1700000000.000100"
|
||||||
|
assert event_data["event_ts"] == "1700000000.000200"
|
||||||
|
|
||||||
|
|
||||||
def test_process_slack_pr_review_request_posts_trace_reply(monkeypatch) -> None:
|
def test_process_slack_pr_review_request_posts_trace_reply(monkeypatch) -> None:
|
||||||
captured: dict[str, object] = {}
|
captured: dict[str, object] = {}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -414,3 +414,135 @@ def test_get_slack_repo_config_repo_name_only_space_syntax(
|
||||||
repo = asyncio.run(webapp.get_slack_repo_config("fix bug in repo open-swe", "C123", "1.234"))
|
repo = asyncio.run(webapp.get_slack_repo_config("fix bug in repo open-swe", "C123", "1.234"))
|
||||||
|
|
||||||
assert repo == {"owner": "langchain-ai", "name": "open-swe"}
|
assert repo == {"owner": "langchain-ai", "name": "open-swe"}
|
||||||
|
|
||||||
|
|
||||||
|
def test_process_slack_mention_creates_thread_followup_run_with_enqueue(
|
||||||
|
monkeypatch: pytest.MonkeyPatch,
|
||||||
|
) -> None:
|
||||||
|
captured: dict[str, object] = {}
|
||||||
|
|
||||||
|
async def fake_add_slack_reaction(channel_id: str, message_ts: str, emoji: str) -> bool:
|
||||||
|
captured["reaction"] = {
|
||||||
|
"channel_id": channel_id,
|
||||||
|
"message_ts": message_ts,
|
||||||
|
"emoji": emoji,
|
||||||
|
}
|
||||||
|
return True
|
||||||
|
|
||||||
|
async def fake_get_slack_user_info(user_id: str) -> dict:
|
||||||
|
return {
|
||||||
|
"profile": {
|
||||||
|
"email": "mason@example.com",
|
||||||
|
"display_name": "Mason",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async def fake_fetch_slack_thread_messages(channel_id: str, thread_ts: str) -> list[dict]:
|
||||||
|
captured["fetch_thread"] = {"channel_id": channel_id, "thread_ts": thread_ts}
|
||||||
|
return [
|
||||||
|
{"ts": "1700000000.000100", "text": "<@UBOT> first request", "user": "U123"},
|
||||||
|
{"ts": "1700000000.000150", "text": "context", "user": "U456"},
|
||||||
|
{
|
||||||
|
"ts": "1700000000.000200",
|
||||||
|
"text": "<@UBOT> continue on the branch",
|
||||||
|
"user": "U123",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
async def fake_get_slack_user_names(user_ids: list[str]) -> dict[str, str]:
|
||||||
|
captured["user_ids"] = user_ids
|
||||||
|
return {"U123": "Mason", "U456": "Teammate"}
|
||||||
|
|
||||||
|
async def fake_resolve_slack_links_in_context(
|
||||||
|
context_messages: list[dict], user_names_by_id: dict[str, str]
|
||||||
|
) -> tuple[str, list[str]]:
|
||||||
|
captured["context_messages"] = context_messages
|
||||||
|
captured["user_names_by_id"] = user_names_by_id
|
||||||
|
return "", []
|
||||||
|
|
||||||
|
async def fake_is_thread_active(thread_id: str) -> bool:
|
||||||
|
captured["active_thread_id"] = thread_id
|
||||||
|
return False
|
||||||
|
|
||||||
|
async def fake_post_slack_trace_reply(
|
||||||
|
channel_id: str, thread_ts: str, thread_id: str, message: str = "Working on it!"
|
||||||
|
) -> None:
|
||||||
|
captured["trace_reply"] = {
|
||||||
|
"channel_id": channel_id,
|
||||||
|
"thread_ts": thread_ts,
|
||||||
|
"thread_id": thread_id,
|
||||||
|
"message": message,
|
||||||
|
}
|
||||||
|
|
||||||
|
class _FakeRunsClient:
|
||||||
|
async def create(self, thread_id: str, graph: str, **kwargs) -> dict[str, str]:
|
||||||
|
captured["run_create"] = {
|
||||||
|
"thread_id": thread_id,
|
||||||
|
"graph": graph,
|
||||||
|
"kwargs": kwargs,
|
||||||
|
}
|
||||||
|
return {"run_id": "run-123"}
|
||||||
|
|
||||||
|
class _FakeThreadsClientForProcess:
|
||||||
|
async def update(self, *, thread_id: str, metadata: dict) -> None:
|
||||||
|
captured["metadata_update"] = {"thread_id": thread_id, "metadata": metadata}
|
||||||
|
|
||||||
|
class _FakeLangGraphClientForProcess:
|
||||||
|
runs = _FakeRunsClient()
|
||||||
|
threads = _FakeThreadsClientForProcess()
|
||||||
|
|
||||||
|
monkeypatch.setattr(webapp, "SLACK_BOT_USERNAME", "open-swe")
|
||||||
|
monkeypatch.setattr(webapp, "add_slack_reaction", fake_add_slack_reaction)
|
||||||
|
monkeypatch.setattr(webapp, "get_slack_user_info", fake_get_slack_user_info)
|
||||||
|
monkeypatch.setattr(webapp, "fetch_slack_thread_messages", fake_fetch_slack_thread_messages)
|
||||||
|
monkeypatch.setattr(webapp, "get_slack_user_names", fake_get_slack_user_names)
|
||||||
|
monkeypatch.setattr(
|
||||||
|
webapp, "resolve_slack_links_in_context", fake_resolve_slack_links_in_context
|
||||||
|
)
|
||||||
|
monkeypatch.setattr(webapp, "is_thread_active", fake_is_thread_active)
|
||||||
|
monkeypatch.setattr(webapp, "post_slack_trace_reply", fake_post_slack_trace_reply)
|
||||||
|
monkeypatch.setattr(webapp, "get_client", lambda url: _FakeLangGraphClientForProcess())
|
||||||
|
|
||||||
|
thread_ts = "1700000000.000100"
|
||||||
|
event_ts = "1700000000.000200"
|
||||||
|
expected_thread_id = generate_thread_id_from_slack_thread("C123", thread_ts)
|
||||||
|
|
||||||
|
asyncio.run(
|
||||||
|
webapp.process_slack_mention(
|
||||||
|
{
|
||||||
|
"channel_id": "C123",
|
||||||
|
"thread_ts": thread_ts,
|
||||||
|
"event_ts": event_ts,
|
||||||
|
"user_id": "U123",
|
||||||
|
"text": "<@UBOT> continue on the branch",
|
||||||
|
"bot_user_id": "UBOT",
|
||||||
|
},
|
||||||
|
{"owner": "langchain-ai", "name": "open-swe"},
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
assert captured["fetch_thread"] == {"channel_id": "C123", "thread_ts": thread_ts}
|
||||||
|
assert captured["active_thread_id"] == expected_thread_id
|
||||||
|
assert captured["metadata_update"] == {
|
||||||
|
"thread_id": expected_thread_id,
|
||||||
|
"metadata": {"repo": {"owner": "langchain-ai", "name": "open-swe"}},
|
||||||
|
}
|
||||||
|
assert captured["trace_reply"] == {
|
||||||
|
"channel_id": "C123",
|
||||||
|
"thread_ts": thread_ts,
|
||||||
|
"thread_id": expected_thread_id,
|
||||||
|
"message": "Working on it!",
|
||||||
|
}
|
||||||
|
|
||||||
|
run_create = captured["run_create"]
|
||||||
|
assert isinstance(run_create, dict)
|
||||||
|
assert run_create["thread_id"] == expected_thread_id
|
||||||
|
assert run_create["graph"] == "agent"
|
||||||
|
kwargs = run_create["kwargs"]
|
||||||
|
assert kwargs["if_not_exists"] == "create"
|
||||||
|
assert kwargs["multitask_strategy"] == "enqueue"
|
||||||
|
assert kwargs["config"]["configurable"]["slack_thread"]["thread_ts"] == thread_ts
|
||||||
|
prompt_block = kwargs["input"]["messages"][0]["content"][0]
|
||||||
|
assert prompt_block["text"].count("## Slack Thread") == 1
|
||||||
|
assert f"Thread TS: {thread_ts}" in prompt_block["text"]
|
||||||
|
assert "## Latest Mention Request\ncontinue on the branch" in prompt_block["text"]
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue