From 42d94d846fcabd4f2eb9432d48119ab6669ae9de Mon Sep 17 00:00:00 2001 From: aries_ckt <916701291@qq.com> Date: Sat, 9 May 2026 15:58:49 +0800 Subject: [PATCH] doc:add react agent context manager,including compact, budget. --- .../docs/agents/modules/context-management.md | 317 ++++++++++++++++++ .../current.json | 4 + .../agents/modules/context-management.md | 279 +++++++++++++++ docs/sidebars.js | 1 + .../src/dbgpt/agent/core/base_agent.py | 5 - .../src/dbgpt/agent/core/context/compact.py | 4 +- web/hooks/use-chat.ts | 2 - 7 files changed, 604 insertions(+), 8 deletions(-) create mode 100644 docs/docs/agents/modules/context-management.md create mode 100644 docs/i18n/zh-CN/docusaurus-plugin-content-docs/current/agents/modules/context-management.md diff --git a/docs/docs/agents/modules/context-management.md b/docs/docs/agents/modules/context-management.md new file mode 100644 index 000000000..cfac781b5 --- /dev/null +++ b/docs/docs/agents/modules/context-management.md @@ -0,0 +1,317 @@ +--- +title: Context Management +--- + +# Agent Context Management + +Agent context management keeps long-running ReAct conversations inside the +model context window without losing the working state of the task. It tracks +token usage before each model call, emits live context status events, and applies +progressively stronger compaction when the conversation grows too large. + +## Overview + +```text +User task + | + v +Agent builds messages +system prompt + task progress + memory + recent ReAct rounds + | + v +Count tokens +ProxyTokenizerWrapper.count_token(model_name) +fallback: len(content) // 4 + | + v +Compute budget +effective_budget = max_context_tokens - reserved_tokens +usage_ratio = used_tokens / effective_budget + | + v +Classify state +normal < warning < error < critical < overflow + | + +-- normal --------------------------------------+ + | | + v | +Send messages to LLM | + | +warning or above | + | | + v | +Layer 1: Observation micro-compaction | +truncate old tool observations | + | | + v | +Recount and emit context.status | + | | + +-- below warning -------------------------------+ + | | + v | +Layer 2: Session memory compaction | +drop old ReAct rounds, keep recent rounds | + | | + v | +Recount and emit context.status | + | | + +-- below error ---------------------------------+ + | | + v | +Layer 3: Full context compression | +summarize old rounds with the LLM | + | | + v | +Recount and emit context.status | + | | + +----------------------------------------------->+ + +If the LLM still returns a context overflow error: + +LLM context_too_long / maximum context length error + | + v +Layer 4: Reactive compaction +keep system prompt + last 2 ReAct rounds + | + v +Retry the model call once with the compacted messages +``` + +Tool results are preserved through a separate snapshot path: + +```text +Action succeeds + | + v +Write full operation snapshot +step, action, action_input, observation, thought, timestamp + | + v +Store snapshot path on the memory fragment +and in task progress metadata + | + v +Rebuild memory for future prompts +Observation: short or compacted observation +[Full detail available at: /path/to/snapshot.json] + | + v +Layer 1 / Layer 2 can shrink prompt text +without deleting the original tool result file +``` + +## Token Budget + +The context manager counts the tokens in the current `AgentMessage` list before +the model call. Counting uses `ProxyTokenizerWrapper` with the active +`model_name`. If the tokenizer cannot count the content, DB-GPT falls back to a +rough estimate of four characters per token. + +The usable context window is: + +```text +effective_budget = max_context_tokens - reserved_tokens +``` + +`reserved_tokens` keeps space for the model response so the prompt does not fill +the entire model window. + +## States And Thresholds + +| State | Default trigger | Meaning | +| --- | --- | --- | +| `normal` | `< 70%` | No compaction. | +| `warning` | `>= 70%` | Start lightweight compaction. | +| `error` | `>= 90%` | Use LLM-based summary compaction when needed. | +| `critical` | `>= 95%` | Same as error, but reported as a more urgent state. | +| `overflow` | `>= 100%` | Prompt is over the effective budget. | + +After every count and every compaction layer, the backend emits a +`context.status` event with: + +```json +{ + "type": "context.status", + "used": 19000, + "budget": 115904, + "ratio": 0.164, + "state": "normal", + "compact_layer": null +} +``` + +The UI renders this as a compact context-window indicator. + +## Compaction Layers + +### Layer 1: Observation Micro-Compaction + +Layer 1 is the lightest compaction. It only shortens old `Observation:` messages +from tool calls. Recent rounds are preserved in full. + +Rules: + +- Triggered when usage reaches `warning_threshold`. +- A round is considered old when it is older than + `max_observation_age_rounds`. +- Old observations are truncated to `truncated_observation_max_chars`. +- If the observation has a snapshot path, the compacted message keeps a pointer + to the full detail. + +This layer is cheap and deterministic. It does not call the LLM. + +### Layer 2: Session Memory Compaction + +Layer 2 removes old complete ReAct rounds from the prompt. It relies on the +task-progress summary already injected into the system prompt, so the agent still +knows what has been completed. + +Rules: + +- Triggered when the prompt is still at or above `warning_threshold` after Layer + 1. +- Always keeps at least `min_keep_recent_rounds`. +- Also keeps enough recent content to satisfy `min_keep_tokens`. +- Drops complete old rounds rather than arbitrary individual messages. + +This layer is also deterministic and does not call the LLM. + +### Layer 3: Full Context Compression + +Layer 3 summarizes old conversation rounds into a structured context summary +with the LLM, then keeps that summary plus the recent rounds. + +Rules: + +- Triggered when usage is at or above `error_threshold`. +- Keeps the last `min_keep_recent_rounds` unchanged. +- Summarizes older messages into one synthetic summary message. +- The summary prompt asks the model to preserve exact task state, paths, values, + variable names, errors, and next steps. +- If summarization fails repeatedly, a circuit breaker stops retrying after + `max_compact_failures`. + +This layer is more expensive, but it preserves more semantic continuity than +simply dropping old messages. + +### Layer 4: Reactive Compaction + +Layer 4 is an emergency path. It is not triggered by the normal budget state +machine. Instead, it runs when the model call fails with a context overflow +error such as `context_too_long`, `context_length_exceeded`, or +`maximum context length`. + +Rules: + +- Keeps system messages. +- Keeps only the last two ReAct rounds. +- Relies on the task-progress summary in the system prompt to preserve task + continuity. +- Retries the model call once with the compacted messages. + +This layer is intentionally aggressive because it is only used after the model +has already rejected the prompt. + +## Tool Result Snapshots + +Tool observations can be large: SQL result tables, generated code output, +interpreter logs, file paths, report metadata, and intermediate computed values +may quickly dominate the prompt. DB-GPT keeps the prompt compact by separating +the full operation detail from the text that must stay in the model context. + +When an action succeeds, the agent writes a JSON snapshot for the full operation. +The snapshot includes: + +- `step` +- `action` +- `phase` +- `action_intention` +- `action_reason` +- `thought` +- `action_input` +- `observation` +- `timestamp` +- `conv_id` + +By default, snapshots are written under: + +```text +$DBGPT_HOME/workspace/op_snapshots// +``` + +If `AgentContext.output_dir` is set, DB-GPT uses that directory instead. + +Each snapshot file is named by step and action: + +```text +step_003_sql_query.json +step_006_code_interpreter.json +``` + +The snapshot path is attached to the in-memory `AgentMemoryFragment` and also +recorded in the task-progress metadata. When the agent later rebuilds memories +into prompt messages, it appends a lightweight reference: + +```text +Observation: +[Full detail available at: /path/to/step_003_sql_query.json] +``` + +This matters during compaction: + +- Layer 1 may truncate old `Observation:` text, but it preserves the snapshot + reference when available. +- Layer 2 may remove old ReAct rounds from the prompt, but task progress still + records the snapshot filename as a reference. +- Layer 3 summarizes old messages, while the original tool result remains on + disk for exact recovery. + +In other words, compaction reduces the prompt payload; it does not have to be +the only place where exact tool output lives. + +## Configuration + +Agent context management can be configured in the application TOML file: + +```toml +[service.web.agent_context] +# Set to 0 to auto-detect from the selected model metadata. +max_context_tokens = 0 +reserved_tokens = 4096 +warning_threshold = 0.70 +error_threshold = 0.90 +critical_threshold = 0.95 +min_keep_recent_rounds = 3 +max_observation_age_rounds = 5 +truncated_observation_max_chars = 200 +min_keep_tokens = 10000 +max_compact_failures = 3 +``` + +When `max_context_tokens` is `0`, DB-GPT tries to read the selected model's +`context_length` from `llm_client.get_model_metadata(model_name)`. If the model +metadata is unavailable, it falls back to the default budget. + +For stable behavior, set `context_length` on each LLM deployment: + +```toml +[[models.llms]] +name = "Qwen/Qwen2.5-Coder-32B-Instruct" +provider = "proxy/siliconflow" +api_key = "${env:SILICONFLOW_API_KEY}" +context_length = 32768 +``` + +With this setup, switching models also switches the effective context budget. + +## Design Notes + +- Layer 1 and Layer 2 are deterministic and cheap. They are preferred before + any LLM summarization. +- Layer 3 uses the LLM only when the context is close to failure. +- Layer 4 is a last-resort retry path for model-side context overflow errors. +- The frontend receives `context.status` events independently from normal chat + text, so UI indicators can update without polluting the conversation. +- Compaction is progressive: after each layer, DB-GPT recounts tokens and stops + escalating if the prompt returns to a safe state. diff --git a/docs/i18n/zh-CN/docusaurus-plugin-content-docs/current.json b/docs/i18n/zh-CN/docusaurus-plugin-content-docs/current.json index 9814062a3..955f15242 100644 --- a/docs/i18n/zh-CN/docusaurus-plugin-content-docs/current.json +++ b/docs/i18n/zh-CN/docusaurus-plugin-content-docs/current.json @@ -450,5 +450,9 @@ "sidebar.docsSidebar.doc.Use Custom Skills": { "message": "使用自定义技能", "description": "The label for the doc item Use Custom Skills in sidebar docsSidebar, linking to the doc dbgpts/how-to-use-custom-skill" + }, + "sidebar.docsSidebar.doc.Context Management": { + "message": "上下文管理", + "description": "The label for the doc item Context Management in sidebar docsSidebar, linking to the doc agents/modules/context-management" } } diff --git a/docs/i18n/zh-CN/docusaurus-plugin-content-docs/current/agents/modules/context-management.md b/docs/i18n/zh-CN/docusaurus-plugin-content-docs/current/agents/modules/context-management.md new file mode 100644 index 000000000..0318e9f82 --- /dev/null +++ b/docs/i18n/zh-CN/docusaurus-plugin-content-docs/current/agents/modules/context-management.md @@ -0,0 +1,279 @@ +--- +title: 上下文管理 +--- + +# Agent 上下文管理 + +Agent 上下文管理用于让长时间运行的 ReAct 对话稳定保持在模型上下文窗口内,同时尽量不丢失任务状态。它会在每次调用模型前统计 token 使用量,向前端发送实时上下文状态,并在对话变长时按层级逐步压缩。 + +## 总览 + +```text +用户任务 + | + v +Agent 构造消息 +system prompt + task progress + memory + 最近 ReAct 轮次 + | + v +统计 token +ProxyTokenizerWrapper.count_token(model_name) +兜底估算:len(content) // 4 + | + v +计算预算 +effective_budget = max_context_tokens - reserved_tokens +usage_ratio = used_tokens / effective_budget + | + v +判断状态 +normal < warning < error < critical < overflow + | + +-- normal --------------------------------------+ + | | + v | +发送消息给 LLM | + | +warning 及以上 | + | | + v | +第 1 层:Observation 微压缩 | +截断较旧的工具观察结果 | + | | + v | +重新统计并发送 context.status | + | | + +-- 低于 warning -------------------------------+ + | | + v | +第 2 层:Session memory 压缩 | +丢弃较旧 ReAct 轮次,保留最近轮次 | + | | + v | +重新统计并发送 context.status | + | | + +-- 低于 error ---------------------------------+ + | | + v | +第 3 层:完整上下文压缩 | +用 LLM 总结旧轮次 | + | | + v | +重新统计并发送 context.status | + | | + +----------------------------------------------->+ + +如果模型仍然返回上下文溢出错误: + +LLM context_too_long / maximum context length error + | + v +第 4 层:Reactive 压缩 +保留 system prompt + 最后 2 个 ReAct 轮次 + | + v +用压缩后的消息重试一次模型调用 +``` + +工具结果会通过单独的快照路径保留: + +```text +Action 执行成功 + | + v +写入完整操作快照 +step、action、action_input、observation、thought、timestamp + | + v +把 snapshot path 存到 memory fragment +以及 task progress metadata + | + v +未来重建 memory prompt +Observation: 短观察结果或已压缩观察结果 +[Full detail available at: /path/to/snapshot.json] + | + v +第 1 层 / 第 2 层可以缩小 prompt 文本 +但不会删除原始工具结果文件 +``` + +## Token 预算 + +上下文管理器会在模型调用前统计当前 `AgentMessage` 列表的 token 数。统计逻辑使用 `ProxyTokenizerWrapper`,并传入当前 `model_name`。如果 tokenizer 无法统计,则退化为粗略估算:每 4 个字符按 1 个 token 计算。 + +可用上下文窗口计算方式: + +```text +effective_budget = max_context_tokens - reserved_tokens +``` + +`reserved_tokens` 用于为模型输出预留空间,避免 prompt 本身占满整个模型窗口。 + +## 状态与阈值 + +| 状态 | 默认触发条件 | 含义 | +| --- | --- | --- | +| `normal` | `< 70%` | 不压缩。 | +| `warning` | `>= 70%` | 开始轻量压缩。 | +| `error` | `>= 90%` | 必要时使用 LLM 总结压缩。 | +| `critical` | `>= 95%` | 与 error 类似,但表示更紧急。 | +| `overflow` | `>= 100%` | prompt 超过有效预算。 | + +每次统计和每层压缩后,后端都会发送 `context.status` 事件: + +```json +{ + "type": "context.status", + "used": 19000, + "budget": 115904, + "ratio": 0.164, + "state": "normal", + "compact_layer": null +} +``` + +前端会把这个事件展示为上下文窗口指示器。 + +## 压缩层级 + +### 第 1 层:Observation 微压缩 + +第 1 层是最轻量的压缩,只处理旧工具调用产生的 `Observation:` 消息。最近轮次会完整保留。 + +规则: + +- 当使用率达到 `warning_threshold` 时触发。 +- 超过 `max_observation_age_rounds` 的轮次会被视为旧轮次。 +- 旧 Observation 会被截断到 `truncated_observation_max_chars`。 +- 如果 Observation 有快照路径,压缩后的消息会保留完整详情的引用。 + +这一层成本低、可确定,不调用 LLM。 + +### 第 2 层:Session Memory 压缩 + +第 2 层会从 prompt 中移除旧的完整 ReAct 轮次。它依赖已经注入 system prompt 的 task-progress summary,因此 Agent 仍然知道哪些工作已经完成。 + +规则: + +- 当第 1 层后仍然达到或超过 `warning_threshold` 时触发。 +- 至少保留 `min_keep_recent_rounds` 个最近轮次。 +- 同时尽量保留不少于 `min_keep_tokens` 的最近内容。 +- 丢弃的是完整旧轮次,而不是任意切分单条消息。 + +这一层也是确定性逻辑,不调用 LLM。 + +### 第 3 层:完整上下文压缩 + +第 3 层会用 LLM 把较旧的对话轮次压缩成结构化上下文摘要,然后保留摘要和最近轮次。 + +规则: + +- 当使用率达到或超过 `error_threshold` 时触发。 +- 最近 `min_keep_recent_rounds` 个轮次原样保留。 +- 更旧的消息会被总结成一条合成摘要消息。 +- 摘要提示词会要求模型保留精确的任务状态、路径、数值、变量名、错误和下一步。 +- 如果总结连续失败,达到 `max_compact_failures` 后会触发熔断,停止继续尝试。 + +这一层成本更高,但比直接丢弃旧消息更能保持语义连续性。 + +### 第 4 层:Reactive 压缩 + +第 4 层是紧急兜底路径。它不是由正常预算状态机触发,而是在模型调用返回上下文溢出错误时触发,例如 `context_too_long`、`context_length_exceeded` 或 `maximum context length`。 + +规则: + +- 保留 system 消息。 +- 只保留最后 2 个 ReAct 轮次。 +- 依赖 system prompt 中的 task-progress summary 保持任务连续性。 +- 用压缩后的消息重试一次模型调用。 + +这一层非常激进,因为它只在模型已经拒绝当前 prompt 后才使用。 + +## 工具结果快照 + +工具观察结果可能非常大:SQL 结果表、生成代码输出、解释器日志、文件路径、报告元数据和中间计算值都可能快速占满 prompt。DB-GPT 通过把完整操作详情和需要进入模型上下文的文本拆开,来保持 prompt 紧凑。 + +当一个 action 成功执行后,Agent 会为完整操作写入一份 JSON 快照。快照包含: + +- `step` +- `action` +- `phase` +- `action_intention` +- `action_reason` +- `thought` +- `action_input` +- `observation` +- `timestamp` +- `conv_id` + +默认快照目录为: + +```text +$DBGPT_HOME/workspace/op_snapshots// +``` + +如果设置了 `AgentContext.output_dir`,DB-GPT 会优先使用该目录。 + +快照文件名由步骤和 action 组成: + +```text +step_003_sql_query.json +step_006_code_interpreter.json +``` + +快照路径会挂到内存中的 `AgentMemoryFragment` 上,也会记录到 task-progress metadata 中。当 Agent 后续把 memory 重建为 prompt 消息时,会附加一条轻量引用: + +```text +Observation: +[Full detail available at: /path/to/step_003_sql_query.json] +``` + +这对压缩很重要: + +- 第 1 层可能会截断旧的 `Observation:` 文本,但会尽量保留快照引用。 +- 第 2 层可能会把旧 ReAct 轮次从 prompt 中移除,但 task progress 仍会记录快照文件名作为引用。 +- 第 3 层会总结旧消息,而原始工具结果仍然保留在磁盘上,便于精确恢复。 + +换句话说,压缩减少的是 prompt 负载;精确工具结果不必只存在于上下文文本里。 + +## 配置 + +可以在应用 TOML 文件中配置 Agent 上下文管理: + +```toml +[service.web.agent_context] +# 设置为 0 时,会从当前模型 metadata 自动读取上下文窗口。 +max_context_tokens = 0 +reserved_tokens = 4096 +warning_threshold = 0.70 +error_threshold = 0.90 +critical_threshold = 0.95 +min_keep_recent_rounds = 3 +max_observation_age_rounds = 5 +truncated_observation_max_chars = 200 +min_keep_tokens = 10000 +max_compact_failures = 3 +``` + +当 `max_context_tokens` 为 `0` 时,DB-GPT 会尝试通过 `llm_client.get_model_metadata(model_name)` 读取所选模型的 `context_length`。如果模型 metadata 不可用,则回退到默认预算。 + +为了让行为稳定,建议在每个 LLM 部署配置中显式设置 `context_length`: + +```toml +[[models.llms]] +name = "Qwen/Qwen2.5-Coder-32B-Instruct" +provider = "proxy/siliconflow" +api_key = "${env:SILICONFLOW_API_KEY}" +context_length = 32768 +``` + +这样切换模型时,上下文预算也会跟随模型能力自动变化。 + +## 设计说明 + +- 第 1 层和第 2 层都是确定性、低成本压缩,优先于 LLM 总结。 +- 第 3 层只在上下文接近失败时调用 LLM。 +- 第 4 层是模型侧上下文溢出错误后的最后重试路径。 +- 前端独立接收 `context.status` 事件,因此上下文窗口指示器可以实时更新,不会污染正常对话内容。 +- 压缩是渐进式的:每一层后都会重新统计 token,如果 prompt 已回到安全状态,就不会继续升级到更强压缩。 diff --git a/docs/sidebars.js b/docs/sidebars.js index bd097975c..2044f6d31 100755 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -218,6 +218,7 @@ const sidebars = { label: "Memory", items: [ { type: "doc", id: "agents/modules/memory/memory" }, + { type: "doc", id: "agents/modules/context-management", label: "Context Management" }, { type: "doc", id: "agents/modules/memory/sensory_memory" }, { type: "doc", id: "agents/modules/memory/short_term_memory" }, { type: "doc", id: "agents/modules/memory/long_term_memory" }, diff --git a/packages/dbgpt-core/src/dbgpt/agent/core/base_agent.py b/packages/dbgpt-core/src/dbgpt/agent/core/base_agent.py index f5f085e88..61c5fada6 100644 --- a/packages/dbgpt-core/src/dbgpt/agent/core/base_agent.py +++ b/packages/dbgpt-core/src/dbgpt/agent/core/base_agent.py @@ -1374,11 +1374,6 @@ class ConversableAgent(Role, Agent): # Multi-layer context management: compress if budget exceeded ctx_mgr: Optional[ContextManager] = getattr(self, "_context_manager", None) - logger.warning( - "[CTX-DEBUG] _context_manager lookup: agent=%s, found=%s", - self.name, - ctx_mgr is not None, - ) if ctx_mgr is not None: agent_messages = await ctx_mgr.manage_context( messages=agent_messages, diff --git a/packages/dbgpt-core/src/dbgpt/agent/core/context/compact.py b/packages/dbgpt-core/src/dbgpt/agent/core/context/compact.py index 41ef8c0ac..63948fb1e 100644 --- a/packages/dbgpt-core/src/dbgpt/agent/core/context/compact.py +++ b/packages/dbgpt-core/src/dbgpt/agent/core/context/compact.py @@ -237,7 +237,9 @@ class FullContextCompression: old_indices.add(i) old_msgs = [conv_msgs[i] for i in sorted(old_indices)] - recent_msgs = [conv_msgs[i] for i in range(len(conv_msgs)) if i not in old_indices] + recent_msgs = [ + conv_msgs[i] for i in range(len(conv_msgs)) if i not in old_indices + ] # Build conversation text for summarization conv_lines = [] diff --git a/web/hooks/use-chat.ts b/web/hooks/use-chat.ts index 39225d28b..72c16b55a 100644 --- a/web/hooks/use-chat.ts +++ b/web/hooks/use-chat.ts @@ -109,7 +109,6 @@ const useChat = ({ queryAgentURL = '/api/v1/chat/completions', app_code }: Props let message = event.data; let needReplaceNewline = false; let parsedData; - console.log('[CTX-DEBUG] SSE onmessage raw:', message?.substring(0, 200)); try { parsedData = JSON.parse(message); @@ -119,7 +118,6 @@ const useChat = ({ queryAgentURL = '/api/v1/chat/completions', app_code }: Props // React-agent format: {"type": "context.status", "used": ..., "budget": ..., ...} const cs = parsedData.context_status ?? (parsedData.type === 'context.status' ? parsedData : null); if (cs) { - console.log('[CTX-DEBUG] Received context_status SSE event:', cs); // Only show banner when Layer 3 (LLM compression) is active if (cs.compact_layer === 'layer3') { setContextStatus({