Fix AttributeError in ThreadDataMiddleware when runtime.context is None (#3989)

`before_agent` guards `context = runtime.context or {}` at the top, but the
`run_id` stamp on a trailing HumanMessage still read the raw `runtime.context`,
so a None context (thread_id resolved from `config.configurable`) plus a
HumanMessage last message raised `AttributeError: 'NoneType' object has no
attribute 'get'`. Use the guarded local `context` instead.

Adds a regression test.
This commit is contained in:
Daoyuan Li 2026-07-10 07:13:50 -07:00 committed by GitHub
parent 446ae98649
commit 1fa91fa39d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 18 additions and 1 deletions

View File

@ -107,7 +107,7 @@ class ThreadDataMiddleware(AgentMiddleware[ThreadDataMiddlewareState]):
content=last_message.content,
id=last_message.id,
name=last_message.name or "user-input",
additional_kwargs={**last_message.additional_kwargs, "run_id": runtime.context.get("run_id"), "timestamp": datetime.now(UTC).isoformat()},
additional_kwargs={**last_message.additional_kwargs, "run_id": context.get("run_id"), "timestamp": datetime.now(UTC).isoformat()},
)
return {

View File

@ -47,6 +47,23 @@ class TestThreadDataMiddleware:
assert _as_posix(result["thread_data"]["uploads_path"]).endswith("threads/thread-from-config/user-data/uploads")
assert runtime.context == {}
def test_before_agent_handles_none_context_with_trailing_human_message(self, tmp_path, monkeypatch):
# Regression: run_id was read via the unguarded `runtime.context`, so a None context plus a
# trailing HumanMessage raised AttributeError (thread_id still resolves from config.configurable).
from langchain_core.messages import HumanMessage
middleware = ThreadDataMiddleware(base_dir=str(tmp_path), lazy_init=True)
runtime = Runtime(context=None)
monkeypatch.setattr(
"deerflow.agents.middlewares.thread_data_middleware.get_config",
lambda: {"configurable": {"thread_id": "thread-from-config"}},
)
result = middleware.before_agent(state={"messages": [HumanMessage(content="hello", id="m1")]}, runtime=runtime)
assert result is not None
assert runtime.context is None
def test_before_agent_raises_clear_error_when_thread_id_missing_everywhere(self, tmp_path, monkeypatch):
middleware = ThreadDataMiddleware(base_dir=str(tmp_path), lazy_init=True)
monkeypatch.setattr(