doc:add react agent context manager,including compact, budget.

This commit is contained in:
aries_ckt
2026-05-09 15:58:49 +08:00
parent 8323d8a939
commit 42d94d846f
7 changed files with 604 additions and 8 deletions

View File

@@ -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/<conv_id>/
```
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: <observation text>
[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.

View File

@@ -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"
}
}

View File

@@ -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/<conv_id>/
```
如果设置了 `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: <observation text>
[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 已回到安全状态,就不会继续升级到更强压缩。

View File

@@ -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" },

View File

@@ -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,

View File

@@ -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 = []

View File

@@ -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({