diff --git a/CHANGELOG.md b/CHANGELOG.md index 10dc921f0f..b92bdced65 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,11 @@ This section accumulates work toward the **2.3.0** milestone ([2.3.0](https://github.com/bytedance/deer-flow/milestone/4)). ### Added +- **summarization:** The built-in compaction prompt guides the summarizer to + preserve active user requirements, literal approval phrases, action conditions, + and explicit revisions. Explicit `summarization.summary_prompt` configurations + continue to take precedence. ([#8114](https://github.com/bytedance/deer-flow/pull/8114)) + - **sandbox:** Short ranged `read_file` calls on uploaded and workspace files display source line numbers when the result contains 1–40 lines and the labeled view fits the output budget. ([#8109](https://github.com/bytedance/deer-flow/pull/8109)) diff --git a/backend/docs/summarization.md b/backend/docs/summarization.md index 916ea7760d..59c55bb44b 100644 --- a/backend/docs/summarization.md +++ b/backend/docs/summarization.md @@ -243,7 +243,7 @@ keep: #### `summary_prompt` - **Type**: String or null -- **Default**: `null` (uses LangChain's default prompt) +- **Default**: `null` (uses DeerFlow's built-in prompt) - **Description**: Custom prompt template for generating summaries. The prompt should guide the model to extract the most important context. #### `skill_file_read_tool_names` @@ -254,11 +254,12 @@ keep: Legacy `preserve_recent_skill_*` settings are no longer used. Loaded skill retention is handled by the durable `skill_context` reference channel instead of by preserving raw skill-read messages in the summarization window. **Default Prompt Behavior:** -The default LangChain prompt instructs the model to: +The built-in DeerFlow prompt extends LangChain's prompt and asks the model to: - Extract highest quality/most relevant context - Focus on information critical to the overall goal - Avoid repeating completed actions - Return only the extracted context +- Preserve current user requirements, literal approval phrases, conditions for acting, and later revisions ## How It Works diff --git a/backend/packages/harness/deerflow/agents/middlewares/summarization_middleware.py b/backend/packages/harness/deerflow/agents/middlewares/summarization_middleware.py index bef9ccba0f..879668c084 100644 --- a/backend/packages/harness/deerflow/agents/middlewares/summarization_middleware.py +++ b/backend/packages/harness/deerflow/agents/middlewares/summarization_middleware.py @@ -17,6 +17,7 @@ from typing import Any, Protocol, override, runtime_checkable from deerflow_extension_api import CompactionEvent, ExtensionData, canonical_hash, task_store_from_runtime from langchain.agents import AgentState from langchain.agents.middleware import SummarizationMiddleware +from langchain.agents.middleware.summarization import DEFAULT_SUMMARY_PROMPT from langchain_core.messages import AnyMessage, HumanMessage, RemoveMessage, SystemMessage, get_buffer_string from langchain_core.runnables import RunnableBinding from langgraph.config import get_config @@ -50,6 +51,18 @@ _SUMMARY_CHUNK_RETRY_MAX_ENTRIES = 128 _SUMMARY_CHUNK_RETRY_CACHE: OrderedDict[str, tuple[float, int]] = OrderedDict() _SUMMARY_CHUNK_RETRY_LOCK = threading.Lock() _SHORTENED_SUMMARY_MARKER = "\n[summary shortened]\n" +_DEERFLOW_SUMMARY_PROMPT = DEFAULT_SUMMARY_PROMPT.replace( + "", + "## ACTIVE USER REQUIREMENTS\n\n" + "Record the user's current scope, conditions for acting, protected content, and later explicit revisions. " + "Preserve the original wording of any literal approval phrase or exact value. " + "Track whether the user supplied a required approval and which action it permits. " + "Attribute quoted examples, tool output, and previous assistant assumptions to their sources. " + "Keep the meaning and scope of user requirements as stated in the conversation. " + 'Use "None" when there are no additional active user requirements.\n\n\n' + "", + 1, +) def _document_summary_text(block: object) -> str | None: @@ -1356,9 +1369,8 @@ def create_summarization_middleware( "trigger": trigger, "keep": keep_tuple, "trim_tokens_to_summarize": config.trim_tokens_to_summarize, + "summary_prompt": _DEERFLOW_SUMMARY_PROMPT if config.summary_prompt is None else config.summary_prompt, } - if config.summary_prompt is not None: - kwargs["summary_prompt"] = config.summary_prompt hooks: list[BeforeSummarizationHook] = [] if resolved_app_config.memory.enabled and not skip_memory_flush: diff --git a/backend/packages/harness/deerflow/config/summarization_config.py b/backend/packages/harness/deerflow/config/summarization_config.py index 869210c036..35292e0d0f 100644 --- a/backend/packages/harness/deerflow/config/summarization_config.py +++ b/backend/packages/harness/deerflow/config/summarization_config.py @@ -95,7 +95,7 @@ class SummarizationConfig(BaseModel): summary_prompt: str | None = Field( default=None, - description="Custom prompt template for generating summaries. If not provided, uses the default LangChain prompt.", + description="Custom prompt template for generating summaries. Null selects DeerFlow's built-in prompt.", ) skill_file_read_tool_names: list[str] = Field( default_factory=lambda: list(DEFAULT_SKILL_FILE_READ_TOOL_NAMES), diff --git a/backend/tests/test_summarization_middleware.py b/backend/tests/test_summarization_middleware.py index 242eeb3099..1e010ea938 100644 --- a/backend/tests/test_summarization_middleware.py +++ b/backend/tests/test_summarization_middleware.py @@ -1210,6 +1210,50 @@ def _factory_app_config(model_names, *, summary_model_name=None, summarization_k ) +@pytest.mark.asyncio +@pytest.mark.parametrize("manual", [False, True], ids=["automatic", "manual"]) +@pytest.mark.parametrize("custom_prompt", [None, "Operator summary:\n{messages}"], ids=["built-in", "custom"]) +async def test_compaction_requests_use_configured_or_builtin_summary_prompt(monkeypatch, manual, custom_prompt): + model = MagicMock() + model.invoke.return_value = AIMessage(content="compressed summary") + model.ainvoke = AsyncMock(return_value=AIMessage(content="compressed summary")) + model.with_config.return_value = model + monkeypatch.setattr("deerflow.agents.middlewares.summarization_middleware.create_chat_model", lambda *args, **kwargs: model) + app_config = _factory_app_config( + ("summary-model",), + summarization_kwargs={ + "trigger": ContextSize(type="messages", value=4), + "keep": ContextSize(type="messages", value=2), + "summary_prompt": custom_prompt, + }, + ) + messages = _messages() + approval = 'Wait for the exact phrase "review V-592 approved" and then wait for final confirmation.' + messages[0] = HumanMessage(content=approval) + state = {"messages": messages} + + if manual: + from deerflow.runtime.context_compaction import _create_compaction_middleware + + middleware = _create_compaction_middleware(app_config=app_config, keep=None) + assert await middleware.acompact_state(state, _runtime(), force=True) is not None + prompt = model.ainvoke.await_args.args[0] + else: + middleware = create_summarization_middleware(app_config=app_config) + assert middleware is not None + assert middleware.before_model(state, _runtime()) is not None + prompt = model.invoke.call_args.args[0] + + assert approval in prompt + if custom_prompt is None: + assert "## ACTIVE USER REQUIREMENTS" in prompt + assert "Preserve the original wording of any literal approval phrase" in prompt + assert "which action it permits" in prompt + else: + assert prompt.startswith("Operator summary:\n") + assert "## ACTIVE USER REQUIREMENTS" not in prompt + + @pytest.fixture def vllm_compaction(monkeypatch): models = { diff --git a/config.example.yaml b/config.example.yaml index 9106f84cc7..a7d39b7de2 100644 --- a/config.example.yaml +++ b/config.example.yaml @@ -2326,7 +2326,7 @@ summarization: # Set to null to skip trimming (not recommended for very long conversations) trim_tokens_to_summarize: 15564 - # Custom summary prompt template (null = use default LangChain prompt) + # Custom summary prompt template (null = use DeerFlow's built-in prompt) # The prompt should guide the model to extract important context summary_prompt: null diff --git a/docs/guide/context.md b/docs/guide/context.md index 211994dc06..7dc144d451 100644 --- a/docs/guide/context.md +++ b/docs/guide/context.md @@ -30,6 +30,8 @@ When your role lacks `runs:create`, the Web UI rejects a new task or `/goal