sync: 鍚屾灞€鍩熺綉鏈€鏂颁唬鐮?(source master @ b69568e4)
This commit is contained in:
1 parent
4f7f07504a
commit
8bc8b0e082
1651 files changed
+278599
-11416
No files matched your search
@@ -1,6 +1,59 @@
|
||||
# Conversation Summarization
|
||||
|
||||
DeerFlow includes automatic conversation summarization to handle long conversations that approach model token limits. When enabled, the system automatically condenses older messages while preserving recent context.
|
||||
DeerFlow includes automatic conversation summarization to handle long conversations that approach model token limits. When enabled, the system automatically condenses older messages while preserving recent context.
|
||||
|
||||
## Administrator recent-three-turn window
|
||||
|
||||
Administrators can enable a simpler deployment-wide sliding window in
|
||||
「设置 → 上下文管理」. It is stored as
|
||||
`system_settings.json -> conversation_context.recent_turn_window_enabled`, is
|
||||
disabled by default, and is exposed through
|
||||
`GET/PUT /api/system-settings/conversation-context` (write access is admin-only).
|
||||
|
||||
When enabled, every lead-agent model call receives only the latest three real
|
||||
user turns, counting the current request, plus the assistant messages and tool
|
||||
results that belong to those turns. Hidden business context immediately attached
|
||||
to the first retained turn and system instructions are preserved. Trimming is
|
||||
performed on `ModelRequest` only, so checkpoint history and the transcript shown
|
||||
to the user remain complete.
|
||||
|
||||
This mode takes priority over the per-chat 「上下文压缩」 option and does not run
|
||||
the summarization model. Turning it off restores the existing summarization
|
||||
behavior without losing earlier messages.
|
||||
|
||||
## Hard input limit (always-on safety net)
|
||||
|
||||
Summarization is a quality feature and may be disabled by the user. A model can
|
||||
also exceed its real window because the final request contains more than chat
|
||||
history: the system prompt, tool schemas, hidden memory/RAG context, and
|
||||
historical reasoning are all sent to the provider. For that reason DeerFlow
|
||||
also supports a model-specific, always-on hard limit:
|
||||
|
||||
```yaml
|
||||
models:
|
||||
- name: qwen-local
|
||||
use: langchain_openai:ChatOpenAI
|
||||
model: Qwen3.8-27B
|
||||
# This is the safe INPUT ceiling, not the model's advertised total window.
|
||||
# For a 200k deployment, 160k leaves room for output and tokenizer variance.
|
||||
max_input_tokens: 160000
|
||||
max_tokens: 8192
|
||||
```
|
||||
|
||||
`max_tokens` limits generated output. `max_input_tokens` limits the complete
|
||||
input request and is consumed only by DeerFlow; it is never sent to the
|
||||
OpenAI-compatible endpoint. The final model middleware conservatively counts
|
||||
the system message, ordinary and hidden messages, tool schemas, response
|
||||
format, tool calls/results, and historical reasoning. If the request is over
|
||||
budget it transiently removes the oldest complete turns, drops expendable
|
||||
hidden context, and truncates oversized tool/message bodies as a last resort.
|
||||
The checkpoint and the conversation displayed in the UI are unchanged.
|
||||
|
||||
For LangChain-compatible configuration, the same limit may be expressed as
|
||||
`profile.max_input_tokens`. A top-level `max_input_tokens` is preferred in this
|
||||
project because it is visible alongside `max_tokens` in model configuration.
|
||||
Do not set `max_tokens: 200000`: that asks the provider to generate up to 200k
|
||||
tokens and does not protect the input context.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -19,7 +72,7 @@ The summarization feature uses DeerFlow's `DeerFlowSummarizationMiddleware` (a s
|
||||
LangChain's stock `SummarizationMiddleware` runs in `before_model` and persists a `RemoveMessage(REMOVE_ALL_MESSAGES)` to the checkpoint — which permanently deletes the original Q&A from both the model context *and* the conversation the frontend renders. DeerFlow overrides this:
|
||||
|
||||
- `before_model` / `abefore_model` are **no-ops** (the destructive path is disabled).
|
||||
- Compression runs in `wrap_model_call` / `awrap_model_call`: for a single model call, the message list is replaced with `[summary, *recent]` via `request.override(messages=...)`. **Nothing is written back to state.**
|
||||
- Compression runs in `wrap_model_call` / `awrap_model_call`: for a single model call, the hidden summary is merged into LangChain's dedicated `ModelRequest.system_message` slot and the ordinary message list is replaced with the recent tail. This guarantees exactly one leading system message even for strict Qwen-style or generic `langchain_chat` adapters. **Nothing is written back to state.**
|
||||
- The full original messages stay in the checkpoint, so the frontend keeps showing the user's real conversation. The summary message is named `summary` and flagged `hide_from_ui`, and because it only ever lives in the transient request it never reaches the frontend at all.
|
||||
- The generated summary is cached **per-thread** so we don't pay an extra LLM call on every model invocation. The cached summary is reused (no LLM call) until enough *new* messages accumulate to push the compressed context back over the trigger threshold (a sticky boundary mirroring the stock middleware's post-compaction behavior). The cache is bounded (oldest threads evicted first).
|
||||
- **Threshold-first**: while the full current conversation is still under the trigger, the request is passed through untouched — no cache lookup, no summary substitution. Compression only ever engages once the real context crosses the configured trigger.
|
||||
@@ -185,7 +238,7 @@ The default LangChain prompt instructs the model to:
|
||||
- Messages to summarize (older messages beyond the `keep` threshold)
|
||||
- Messages to preserve (recent messages within the `keep` threshold)
|
||||
4. **Summary Generation**: The model generates a concise summary of the older messages
|
||||
5. **Context Replacement (transient)**: The message list **handed to the model for this call** is rebuilt as `[summary, *recent]`. The persisted state is left untouched — old messages are **not** removed, so the frontend still renders the full original conversation.
|
||||
5. **Context Replacement (transient)**: The request **handed to the model for this call** is rebuilt with one system message (agent prompt + hidden summary) and the recent non-system tail. The persisted state is left untouched — old messages are **not** removed, so the frontend still renders the full original conversation.
|
||||
6. **AI/Tool Pair Protection**: The system ensures AI messages and their corresponding tool messages stay together
|
||||
7. **Skill Rescue**: Before the summary is generated, the most recently loaded skill files (tool results whose tool name is in `skill_file_read_tool_names` and whose target path is under `skills.container_path`) are lifted out of the summarization set and prepended to the preserved tail. Selection walks newest-first under three budgets: `preserve_recent_skill_count`, `preserve_recent_skill_tokens`, and `preserve_recent_skill_tokens_per_skill`. The triggering AIMessage and all of its paired ToolMessages move together so tool_call ↔ tool_result pairing stays intact.
|
||||
|
||||
@@ -202,12 +255,14 @@ The middleware intelligently preserves message context:
|
||||
|
||||
- **Recent Messages**: Always kept intact based on `keep` configuration
|
||||
- **AI/Tool Pairs**: Never split - if a cutoff point falls within tool messages, the system adjusts to keep the entire AI + Tool message sequence together
|
||||
- **Summary Format**: Summary is injected as a HumanMessage with the format:
|
||||
```
|
||||
Here is a summary of the conversation to date:
|
||||
|
||||
[Generated summary text]
|
||||
```
|
||||
- **Summary Format**: Summary is merged into the leading SystemMessage with the format:
|
||||
```
|
||||
Internal conversation summary for continuity only...
|
||||
|
||||
<internal_context_summary>
|
||||
[generated summary text]
|
||||
</internal_context_summary>
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
@@ -329,7 +384,8 @@ Summarization runs after ThreadData and Sandbox initialization but before Title
|
||||
|
||||
- Configuration is loaded once at startup
|
||||
- Summarization is **non-destructive**: the summary replaces older messages only in the transient model request (`wrap_model_call`), never in persisted state. The checkpointer therefore keeps the full original conversation
|
||||
- The per-thread summary cache lives in a **process-wide, thread-safe LRU** (`_GLOBAL_SUMMARY_CACHE`, bounded), **not** on the middleware instance. `make_lead_agent` rebuilds a fresh middleware every run, so an instance cache would be cold each turn and re-run a blocking summary LLM call on every turn of a long conversation; the module-level cache keyed by `thread_id` lets a summary survive across turns. It is a cost optimization only — a cold/stale cache simply re-summarizes, which is always correct
|
||||
- The per-thread summary cache lives in a **process-wide, thread-safe LRU** (`_GLOBAL_SUMMARY_CACHE`, bounded), **not** on the middleware instance. `make_lead_agent` rebuilds a fresh middleware every run, so an instance cache would be cold each turn and re-run a blocking summary LLM call on every turn of a long conversation; the module-level cache keyed by `thread_id` lets a summary survive across turns. It is a cost optimization only — a cold/stale cache simply re-summarizes, which is always correct.
|
||||
- Compression normalizes LangChain's split request representation before invoking the next middleware/model: the dedicated `system_message` contains all system context exactly once, while `messages` contains no `SystemMessage`. This makes the fix independent of the configured provider adapter.
|
||||
|
||||
## Example Configurations
|
||||
|
||||
|
||||
Reference in new issue
Block a user