add logging (#1248)

This commit is contained in:
Johannes du Plessis 2026-05-07 10:19:53 -07:00 • committed by GitHub
parent 6ded489751
commit da74342da4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 204 additions and 2 deletions

View file

@ -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)

View file

@ -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] = {}

View file

@ -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"]