From cf3afe2bac817ac6d114fb7b8026a86b575e9f18 Mon Sep 17 00:00:00 2001 From: WuWenbo Date: Fri, 25 Sep 2026 14:18:09 +0800 Subject: [PATCH] fix(middleware): preserve human message metadata (#5823) * fix(middleware): preserve human message metadata * test(middleware): cover human message metadata injection --- .../middlewares/thread_data_middleware.py | 14 ++++++---- backend/tests/test_thread_data_middleware.py | 28 +++++++++++++++++-- 2 files changed, 35 insertions(+), 7 deletions(-) diff --git a/backend/packages/harness/deerflow/agents/middlewares/thread_data_middleware.py b/backend/packages/harness/deerflow/agents/middlewares/thread_data_middleware.py index 2dcdcdc00f..9507ca6a6d 100644 --- a/backend/packages/harness/deerflow/agents/middlewares/thread_data_middleware.py +++ b/backend/packages/harness/deerflow/agents/middlewares/thread_data_middleware.py @@ -103,11 +103,15 @@ class ThreadDataMiddleware(AgentMiddleware[ThreadDataMiddlewareState]): last_message = messages[-1] if messages else None if last_message and isinstance(last_message, HumanMessage): - messages[-1] = HumanMessage( - content=last_message.content, - id=last_message.id, - name=last_message.name or "user-input", - additional_kwargs={**last_message.additional_kwargs, "run_id": context.get("run_id"), "timestamp": datetime.now(UTC).isoformat()}, + messages[-1] = last_message.model_copy( + update={ + "name": last_message.name or "user-input", + "additional_kwargs": { + **last_message.additional_kwargs, + "run_id": context.get("run_id"), + "timestamp": datetime.now(UTC).isoformat(), + }, + }, ) return { diff --git a/backend/tests/test_thread_data_middleware.py b/backend/tests/test_thread_data_middleware.py index e239700a6b..479a5a2cdd 100644 --- a/backend/tests/test_thread_data_middleware.py +++ b/backend/tests/test_thread_data_middleware.py @@ -1,4 +1,5 @@ import pytest +from langchain_core.messages import HumanMessage from langgraph.runtime import Runtime from deerflow.agents.middlewares.thread_data_middleware import ThreadDataMiddleware @@ -66,8 +67,6 @@ class TestThreadDataMiddleware: 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( @@ -80,6 +79,31 @@ class TestThreadDataMiddleware: assert result is not None assert runtime.context is None + def test_before_agent_preserves_human_message_response_metadata(self, tmp_path): + middleware = ThreadDataMiddleware(base_dir=str(tmp_path), lazy_init=True) + message = HumanMessage( + content="hello", + id="message-1", + response_metadata={"source": "gateway"}, + ) + + result = middleware.before_agent( + state={"messages": [message]}, + runtime=Runtime( + context={ + "thread_id": "thread-123", + "run_id": "run-123", + }, + ), + ) + + assert result is not None + updated_message = result["messages"][-1] + assert updated_message.response_metadata == {"source": "gateway"} + assert updated_message.name == "user-input" + assert updated_message.additional_kwargs["run_id"] == "run-123" + assert updated_message.additional_kwargs["timestamp"] + 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(