mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-11-12 14:00:35 +00:00
feat(summarization): align built-in compaction prompt to preserve user requirements and approval conditions (#8114) (#8114)
In long-running agent workflows with repeated context compaction, empirical gateway ablations across 132 real sessions revealed that standard boundary compliance remains robust, while semantic drift can occur when model-generated summaries rephrase literal approval phrases and action conditions. This change converges PR #8114 into a clean built-in summary prompt enhancement: - Replaces regex extraction, tag elevation, and heuristic revocation with an extended built-in DeerFlow summary prompt (_DEERFLOW_SUMMARY_PROMPT). - Instructs the summarizer to faithfully record active user requirements, literal approval phrases, permitted actions, and later explicit revisions without paraphrasing. - Treats summary text strictly as historical data, fully preserving the security boundary and control/data plane separation of DurableContextMiddleware. - Unifies automatic and manual compaction factory paths to select the built-in template when unconfigured, while preserving explicit summary_prompt precedence. - Adds comprehensive sync/async unit tests covering built-in and custom templates. Co-authored-by: Totoro <Totoro-qaq@users.noreply.github.com> Co-authored-by: Totoro <msy626836554@gmail.com>
This commit is contained in:
parent
4d95bff550
commit
218c64180e
@ -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))
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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(
|
||||
"</instructions>",
|
||||
"## 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'
|
||||
"</instructions>",
|
||||
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:
|
||||
|
||||
@ -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),
|
||||
|
||||
@ -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 = {
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -30,6 +30,8 @@ When your role lacks `runs:create`, the Web UI rejects a new task or `/goal <com
|
||||
Automatic and manual compaction process all selected history in chronological
|
||||
chunks before replacing it. `summarization.trim_tokens_to_summarize` bounds raw
|
||||
input per step; generated summaries use at most half to leave room for source.
|
||||
The built-in summary prompt asks the model to preserve active user requirements
|
||||
and approval conditions. Set `summarization.summary_prompt` to supply a custom template.
|
||||
Oversized summaries retain marked head/tail text; raw history is never clipped.
|
||||
Native document titles, context and direct plain-text bodies are included in the
|
||||
summary input. Binary documents and remote media are not decoded or fetched;
|
||||
|
||||
@ -212,7 +212,7 @@ summarization:
|
||||
# Maximum tokens to trim when preparing messages for the summarizer
|
||||
trim_tokens_to_summarize: 15564
|
||||
|
||||
# Custom summary prompt (null = use default LangChain prompt)
|
||||
# Custom summary prompt (null = use DeerFlow's built-in prompt)
|
||||
summary_prompt: null
|
||||
```
|
||||
|
||||
|
||||
@ -200,7 +200,7 @@ summarization:
|
||||
# 为摘要器准备消息时要裁剪的最大 token 数
|
||||
trim_tokens_to_summarize: 15564
|
||||
|
||||
# 自定义摘要提示词(null = 使用默认 LangChain 提示词)
|
||||
# 自定义摘要提示词(null = 使用 DeerFlow 内置提示词)
|
||||
summary_prompt: null
|
||||
```
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user