mirror of
https://github.com/Sea-Haven-Industries/open-swe.git
synced 2026-09-30 08:03:15 +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
|
||||
|
||||
|
||||
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:
|
||||
"""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)
|
||||
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,
|
||||
"agent",
|
||||
input={"messages": [{"role": "user", "content": content_blocks}]},
|
||||
config={"configurable": configurable, "metadata": _AGENT_VERSION_METADATA},
|
||||
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)
|
||||
|
||||
|
|
|
|||
|
|
@ -530,6 +530,61 @@ def test_slack_webhook_non_pr_review_request_starts_agent(monkeypatch) -> None:
|
|||
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:
|
||||
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"))
|
||||
|
||||
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