多轮对话的上下文来自消息表,会话侧不用图数据库做记忆。智能推理/快速问答两条路径都会「从库取出历史 → 拼进本轮输入」,但切分方式与超长策略不同:
| 智能推理 | 快速问答 | |
|---|---|---|
| 历史怎么切 | 系统提示 / 历史中间段 / 当前用户轮;历史每轮再展开工具轨迹 | 最多 N 对 {Query, Answer},再拼本轮 UserContent |
| 默认保留 | 约 5 轮(HistoryTurns) |
会话配置的 MaxRounds(≤0 则关闭多轮) |
| 超长怎么办 | 两级压缩:约半窗先 LLM 摘要合并中间历史 → 约八成再按工具消息组机械裁掉最旧;系统与当前轮始终不动 | 按轮硬丢 :超过 MaxRounds 的更早问答对直接不进模型;已留下的 N 对不再按 token 做摘要或二次裁剪 |
下面先按路径展开「分成哪些部分」和各自的超长策略,再介绍加载上限与检索是否跨轮。
智能推理:三段式 + 两级压缩
引擎拼装时大致是:
text
[1] system 系统提示(本轮现算,不从历史里复用旧 system)
[2] 历史中间段 最近 N 个已完成问答轮展开后的 messages
[3] 当前用户轮 本轮问题(及本轮后续的 assistant / tool,随 ReAct 增长)
其中 每一轮历史 再拆成:
text
user 当时的用户原文(可带图注、附件;故意不用旧的 RenderedContent,避免把过时 RAG 信封混进当前协议)
assistant + tool_calls AgentSteps 里的中间步(思考文案 + 工具调用)
tool(可多条) 与上面 tool_calls 成对的结果
...(可有多组中间步)
assistant 去掉 <think> 后的最终回答
超长压缩只动「历史中间段」 :系统提示与当前用户轮(及之后本轮工具轨迹)始终保留。每轮调模型前估计 token(优先上一轮 API Usage,再加新增消息增量),然后:
- 摘要合并(约超过窗口一半) :把较早的中间历史交给 LLM 收成一条系统侧
[Memory Summary...],较近历史与当前轮原样留下;摘要失败则改为原文归档,避免一次坏压缩毁掉上下文。 - 机械裁剪(仍超过约八成) :把中间历史按「assistant(tool_calls) + 对应 tool 结果」成组,从最旧一组开始丢掉,不拆断 Function Calling 配对。
go
// internal/agent/observe.go
if e.memoryConsolidator != nil && e.memoryConsolidator.ShouldConsolidate(currentTokens) {
consolidated, err := e.memoryConsolidator.Consolidate(ctx, messages)
// 失败则继续走机械压缩
}
messages = agenttoken.CompressContext(messages, e.tokenEstimator, e.config.MaxContextTokens, currentTokens)
加载时另有轮数上限:LoadAgentHistory 默认约 5 轮,库表为唯一来源,每轮问答前加载一次再交给引擎。
快速问答:问答对 + 轮数封顶
库里同样是按 RequestID 存的 user / assistant 消息行,但快速问答不会 像智能推理那样把 AgentSteps 展开成 assistant(tool_calls) + tool 序列。LOAD_HISTORY 先聚成内存结构 History,再压成普通多轮对话:
text
History 每条 ≈ {
Query ← 用户侧 Content(可附带历史图注、附件文案;不用旧 RenderedContent 当 Query)
Answer ← 助手最终回复(去掉 <think>...</think>)
KnowledgeReferences ← 该轮引用块(可选,供本轮合并引用,不是 tool 消息)
}
拼进模型时由 AppendHistoryMessages 变成严格的两行一组:
text
user: history.Query
assistant: history.Answer
...(最多 MaxRounds 组)
user: 本轮 UserContent(模板拼好的「检索上下文 + 当前问题」)
因此模型看到的历史里没有 role=tool,也看不到当时检索/工具的中间过程------只有「当时问了什么、最终答了什么」;引用靠旁路字段 KnowledgeReferences 再并进本轮,而不是重放工具轨迹。
加载细节:按 RequestID 聚对 → 去掉思考标签 → 按时间取最近 N 对再正序写入。MaxRounds <= 0 关闭多轮,历史整段不进模型。
超长策略 = 按轮丢弃,不是摘要压缩:
- 问答对数大于
MaxRounds时,更早的轮次直接丢掉(不进本轮模型上下文);库里的消息仍在,只是本次不加载。 - 没有智能推理那套「半窗 LLM 摘要 + 八成成组裁剪」:已经留下的 N 对不会再因窗口将满而摘要或按 token 二次裁掉中间内容。
- 体积主要靠
MaxRounds与提示词模板约束;检索/网页正文另有 top-K、长度截断,那是材料侧,不是对话历史的 Memory Summary。排障时不要按 Agent 压缩去对这条路径。
是否把上一轮「拼装后的检索正文」带回来,取决于是否把渲染结果写回消息字段(与 Agent 默认涂掉旧检索不同,见下节)------即便库里存了 RenderedContent,快速问答进历史时仍以用户 Content 为 Query,避免把上一轮整段「<context id="...">检索块</context> + 当时引用说明」原样塞进本轮,和当前引用格式(如 <ref id="cN"/>)打架。
另有隐藏的聊天记录知识库,可把问答对写入供搜索接口用,但不会自动注入任一条路径的提示词。
检索结果是否跨轮
- 智能推理 :默认把历史里知识库类工具(语义搜、grep、图谱、Wiki 读等)的 tool 结果换成「请重新检索」类占位,避免知识库变更后仍吃旧块;打开
RetainRetrievalHistory才原样保留。 - 快速问答 :可将本轮拼装好的
UserContent写入渲染字段,下一轮历史仍可能带上当时资料------与 Agent(智能推理) 默认方向相反,需按场景选用。
提升了什么
智能推理把「轮数上限 → token 阈值下的摘要 → 成组硬裁」叠在同一套三段切分上,窗口贴模型真实消耗,又保住 tool 协议与当前问题。快速问答用更简单的轮数模型,路径短、行为可预期。两条路径策略分开写清,避免把 Agent 压缩误套到 RAG 链上。
好处与代价
好处
- 三段切分让压缩作用域可读:永远保住系统设定与当前问题。
- 摘要失败有原文归档回退;机械裁剪不拆 tool 对。
- 用 API usage 估预算,触发点更贴近实际消耗。
代价
- 长对话更慢、更贵:摘要要调模型;默认涂掉检索又可能多一轮重搜。
- 涂掉工具结果后,模型可能重复检索相近内容。
- 快速问答靠硬丢旧轮控长:超
MaxRounds的对话对模型不可见,无摘要兜底;极长多轮只能调小轮数。若渲染字段带着过时检索,还会把旧资料带进新问题。
基于 WeKnora v0.7.0
上一篇:WeKnora 问答实现 | 下一篇:WeKnora 回答溯源