From f76e4e35c8c0e8749de646229d331a42bcaaa144 Mon Sep 17 00:00:00 2001
From: DanielWalnut <45447813+hetaoBackend@users.noreply.github.com>
Date: Sat, 9 May 2026 18:22:58 +0800
Subject: [PATCH] fix title generation with dynamic context reminder (#2830)
---
.../middlewares/dynamic_context_middleware.py | 7 +++-
.../agents/middlewares/title_middleware.py | 9 +++--
.../tests/test_title_middleware_core_logic.py | 36 +++++++++++++++++++
3 files changed, 49 insertions(+), 3 deletions(-)
diff --git a/backend/packages/harness/deerflow/agents/middlewares/dynamic_context_middleware.py b/backend/packages/harness/deerflow/agents/middlewares/dynamic_context_middleware.py
index 628a60e88..b7b0950e6 100644
--- a/backend/packages/harness/deerflow/agents/middlewares/dynamic_context_middleware.py
+++ b/backend/packages/harness/deerflow/agents/middlewares/dynamic_context_middleware.py
@@ -53,6 +53,11 @@ def _extract_date(content: str) -> str | None:
return m.group(1) if m else None
+def is_dynamic_context_reminder(message: object) -> bool:
+ """Return whether *message* is a hidden dynamic-context reminder."""
+ return isinstance(message, HumanMessage) and bool(message.additional_kwargs.get(_DYNAMIC_CONTEXT_REMINDER_KEY))
+
+
def _last_injected_date(messages: list) -> str | None:
"""Scan messages in reverse and return the most recently injected date.
@@ -61,7 +66,7 @@ def _last_injected_date(messages: list) -> str | None:
are not mistakenly treated as injected reminders.
"""
for msg in reversed(messages):
- if isinstance(msg, HumanMessage) and msg.additional_kwargs.get(_DYNAMIC_CONTEXT_REMINDER_KEY):
+ if is_dynamic_context_reminder(msg):
content_str = msg.content if isinstance(msg.content, str) else str(msg.content)
return _extract_date(content_str)
return None
diff --git a/backend/packages/harness/deerflow/agents/middlewares/title_middleware.py b/backend/packages/harness/deerflow/agents/middlewares/title_middleware.py
index 01080be14..b259ce4a4 100644
--- a/backend/packages/harness/deerflow/agents/middlewares/title_middleware.py
+++ b/backend/packages/harness/deerflow/agents/middlewares/title_middleware.py
@@ -9,6 +9,7 @@ from langchain.agents.middleware import AgentMiddleware
from langgraph.config import get_config
from langgraph.runtime import Runtime
+from deerflow.agents.middlewares.dynamic_context_middleware import is_dynamic_context_reminder
from deerflow.config.title_config import get_title_config
from deerflow.models import create_chat_model
@@ -61,6 +62,10 @@ class TitleMiddleware(AgentMiddleware[TitleMiddlewareState]):
return ""
+ @staticmethod
+ def _is_user_message_for_title(message: object) -> bool:
+ return getattr(message, "type", None) == "human" and not is_dynamic_context_reminder(message)
+
def _should_generate_title(self, state: TitleMiddlewareState) -> bool:
"""Check if we should generate a title for this thread."""
config = self._get_title_config()
@@ -77,7 +82,7 @@ class TitleMiddleware(AgentMiddleware[TitleMiddlewareState]):
return False
# Count user and assistant messages
- user_messages = [m for m in messages if m.type == "human"]
+ user_messages = [m for m in messages if self._is_user_message_for_title(m)]
assistant_messages = [m for m in messages if m.type == "ai"]
# Generate title after first complete exchange
@@ -91,7 +96,7 @@ class TitleMiddleware(AgentMiddleware[TitleMiddlewareState]):
config = self._get_title_config()
messages = state.get("messages", [])
- user_msg_content = next((m.content for m in messages if m.type == "human"), "")
+ user_msg_content = next((m.content for m in messages if self._is_user_message_for_title(m)), "")
assistant_msg_content = next((m.content for m in messages if m.type == "ai"), "")
user_msg = self._normalize_content(user_msg_content)
diff --git a/backend/tests/test_title_middleware_core_logic.py b/backend/tests/test_title_middleware_core_logic.py
index ede4dc0a4..5395f816e 100644
--- a/backend/tests/test_title_middleware_core_logic.py
+++ b/backend/tests/test_title_middleware_core_logic.py
@@ -7,6 +7,7 @@ from unittest.mock import AsyncMock, MagicMock
from langchain_core.messages import AIMessage, HumanMessage
from deerflow.agents.middlewares import title_middleware as title_middleware_module
+from deerflow.agents.middlewares.dynamic_context_middleware import _DYNAMIC_CONTEXT_REMINDER_KEY
from deerflow.agents.middlewares.title_middleware import TitleMiddleware
from deerflow.config.title_config import TitleConfig, get_title_config, set_title_config
@@ -44,6 +45,22 @@ class TestTitleMiddlewareCoreLogic:
assert middleware._should_generate_title(state) is True
+ def test_should_generate_title_with_dynamic_context_reminder(self):
+ _set_test_title_config(enabled=True)
+ middleware = TitleMiddleware()
+ state = {
+ "messages": [
+ HumanMessage(
+ content="\nUser prefers Python.\n",
+ additional_kwargs={_DYNAMIC_CONTEXT_REMINDER_KEY: True},
+ ),
+ HumanMessage(content="帮我总结这段代码"),
+ AIMessage(content="好的,我先看结构"),
+ ]
+ }
+
+ assert middleware._should_generate_title(state) is True
+
def test_should_not_generate_title_when_disabled_or_already_set(self):
middleware = TitleMiddleware()
@@ -243,6 +260,25 @@ class TestTitleMiddlewareCoreLogic:
prompt, _ = middleware._build_title_prompt(state)
assert "" not in prompt
+ def test_build_title_prompt_uses_real_user_message_with_dynamic_context_reminder(self):
+ _set_test_title_config(enabled=True)
+ middleware = TitleMiddleware()
+ state = {
+ "messages": [
+ HumanMessage(
+ content="\nUser prefers Python.\n",
+ additional_kwargs={_DYNAMIC_CONTEXT_REMINDER_KEY: True},
+ ),
+ HumanMessage(content="请帮我写测试"),
+ AIMessage(content="好的"),
+ ]
+ }
+
+ prompt, user_msg = middleware._build_title_prompt(state)
+ assert user_msg == "请帮我写测试"
+ assert "" not in prompt
+ assert "User prefers Python" not in prompt
+
def test_generate_title_async_strips_think_tags_in_response(self, monkeypatch):
"""Async title generation strips blocks from the model response."""
_set_test_title_config(max_chars=50)