# 知识反馈时机设计文档 ## 文档维护规范 0. **先改文档,再动代码** - 新功能或重大修改需先完成文档更新、并完成审阅后,再进行代码实现;除非改动较小、不被文档涵盖 1. **文档分层,链接代码** - 重要或复杂设计可以另有详细文档;关键实现需标注代码文件路径;格式:`module/file.py:function_name` 2. **简洁快照,日志分离** - 只记录最重要的、与代码准确对应的或者明确的已完成的设计的信息,避免推测、建议、决策历史、修改日志、大量代码;决策依据或修改日志若有必要,可在 `knowhub/docs/decisions.md` 另行记录 --- ## 背景 ### 现有反馈机制的缺陷 当前的知识反馈存在以下问题(来自 `feedback-optimization-proposal.md`): - **反馈时机不明确**:没有明确定义何时、由谁来评估知识的有效性 - **缺少使用状态追踪**:知识被注入后,无法知道它是否真的被用到了 - **评估粒度粗糙**:只有 helpful/harmful 计数,缺少"为什么有用/无用"的上下文 ### 设计目标 1. 记录每条知识的完整生命周期(注入 → 使用 → 评估) 2. 在自然的执行节点(Goal 完成、压缩、任务结束)触发评估,不打断主流程 3. 为后续上报 KnowHub 提供结构化的评估数据 --- ## 核心概念 ### Knowledge Log(知识注入日志) 每个 trace 维护一个 `knowledge_log.json`,记录该 trace 中所有被注入的知识及其评估状态。 **位置**:`.trace/{trace_id}/knowledge_log.json` **数据结构**: ```json { "trace_id": "trace-xxx", "entries": [ { "knowledge_id": "knowledge-20260305-a1b2", "goal_id": "1", "injected_at_sequence": 42, "injected_at": "2026-03-20T10:00:00.000000", "task": "知识的原始task描述", "content": "知识内容摘要(截断至500字符)", "eval_result": { "eval_status": "helpful", "reason": "评估理由" }, "evaluated_at": "2026-03-20T10:05:00.000000", "evaluated_at_trigger": "goal_completion" } ] } ``` **字段说明**: | 字段 | 类型 | 说明 | |---|---|---| | `knowledge_id` | string | KnowHub 中的知识 ID | | `goal_id` | string | 注入时的 Goal ID(如 `"1"`, `"2.1"`) | | `injected_at_sequence` | int | 注入时的消息序列号 | | `injected_at` | datetime | 注入时间(ISO 格式,含毫秒) | | `task` | string | 知识的原始 task 描述 | | `content` | string | 知识内容(写入时截断至 500 字符) | | `eval_result` | object/null | 评估结果对象;未评估时为 `null` | | `evaluated_at` | datetime/null | 评估时间;未评估时为 `null` | | `evaluated_at_trigger` | string/null | 触发评估的事件(见下表);未评估时为 `null` | **`evaluated_at_trigger` 可能的值**: | 值 | 含义 | |---|---| | `"goal_completion"` | 由 Goal 完成(`completed` 或 `abandoned`)触发 | | `"compression"` | 由上下文压缩触发(压缩前必须先评估) | | `"task_completion"` | 由任务自然结束触发(主路径无工具调用退出时兜底) | > 注意:同一个 `knowledge_id` 可能在不同 Goal 中被多次注入,每次产生独立 entry。评估时优先更新最近注入(`injected_at_sequence` 最大)的未评估条目。 --- ## 评估触发机制 ### 触发点 1:Goal 完成 **时机**:Goal status 变为 `completed` 或 `abandoned` **触发逻辑**(`agent/trace/store.py:update_goal`): ``` Goal 完成 ↓ 查询 knowledge_log 中 eval_result == null 的条目 ↓ 如果有待评估条目 → 在 trace.context 中设置标志: pending_knowledge_eval = true knowledge_eval_trigger = "goal_completion" ↓ Runner 主循环下一次迭代开头检测到标志(agent/core/runner.py:_agent_loop) → 清除标志 → 将 "knowledge_eval" 加入 force_side_branch 队列 ``` ### 触发点 2:压缩(Compression) **时机**:上下文 token 数超过阈值,即将执行压缩 **触发逻辑**(`agent/core/runner.py:_manage_context_usage`): ``` 压缩条件触发 ↓ 查询 knowledge_log 中 eval_result == null 的条目 ↓ 如果有待评估条目 → 在 trace.context 中设置: knowledge_eval_trigger = "compression" → 将侧分支队列设为: ["reflection", "knowledge_eval", "compression"](启用知识提取时) ["knowledge_eval", "compression"](未启用知识提取时) → 返回"需要进入侧分支"信号,暂缓压缩 ↓ 依次执行侧分支队列后再压缩 ``` **原因**:压缩会删除消息历史,必须在压缩前完成评估,否则执行上下文永久丢失。 ### 触发点 3:任务结束(兜底) **时机**:主路径出现无工具调用的回复,Agent 即将结束任务 **触发逻辑**(`agent/core/runner.py:_agent_loop`,无工具调用分支): ``` 主路径无工具调用(任务即将结束) ↓ 查询 knowledge_log 中 eval_result == null 的条目 ↓ 如果有待评估条目 → 在 trace.context 中设置: knowledge_eval_trigger = "task_completion" → 将 ["knowledge_eval"] 加入 force_side_branch 队列 → continue(不 break,下一轮执行评估侧分支) ↓ 评估完成后再退出 ``` --- ## 评估分类(eval_status) | 状态 | 含义 | |---|---| | `irrelevant` | 知识的 task 与当前任务无关 | | `unused` | 知识与任务相关,但执行过程中没有被使用 | | `helpful` | 知识对当前任务有实质帮助 | | `harmful` | 知识对当前任务产生了负面作用 | | `neutral` | 知识与任务相关但无明显影响 | --- ## 侧分支评估流程 ### 侧分支类型 复用现有 `SideBranchContext` 机制,新增 `"knowledge_eval"` 类型(`agent/trace/models.py:Message.branch_type`): ```python SideBranchContext( type="knowledge_eval", branch_id=f"knowledge_eval_{uuid.uuid4().hex[:8]}", # 如 "knowledge_eval_1c5fffaf" max_turns=config.side_branch_max_turns # 默认 5 ) ``` `trigger_event` 记录在 `trace.context["active_side_branch"]["trigger_event"]` 中,侧分支退出后写入 `evaluated_at_trigger`。 ### 评估 Prompt 结构 完整实现见 `agent/core/runner.py:_build_knowledge_eval_prompt`,结构如下: ``` 你是知识评估助手。请评估以下知识在本次任务执行中的实际效果。 ## 当前任务(Mission) ← trace.task ## 当前 Goal ← goal_tree.current 的 description ## 待评估知识列表 ← 所有 eval_result == null 的条目 - knowledge_id / task / content / injected_at_sequence / goal_id ## 评估维度 ← helpfulness + relevance ## 评估分类 ← 5 个 eval_status 选项 ## 输出格式 ← JSON ``` > Prompt 中**不包含消息历史**。LLM 依据对话上下文中已有的执行过程作出判断。 ### 评估输出格式 LLM 直接输出 JSON,**无需调用工具**: ```json { "evaluations": [ { "knowledge_id": "knowledge-20260305-a1b2", "eval_status": "helpful", "reason": "1-2句评估理由" } ] } ``` ### 即时写入机制(`agent/core/runner.py:_agent_loop`) 每次 LLM 回复后立即尝试解析,三种策略依次降级:整体解析 → ` ```json ` 代码块 → 正则裸对象。 ``` LLM 输出评估 JSON ↓ 解析成功 → 立即调用 store.update_knowledge_evaluation() 写入每条评估结果 ↓ 侧分支达到退出条件(无工具调用 或 超过 max_turns)→ 恢复主路径 ``` 解析失败时记录日志,不中断主流程。 --- ## 数据流 ``` 知识注入(agent/trace/goal_tool.py:inject_knowledge_for_goal) ↓ 写入 knowledge_log.json(eval_result=null) ↓ ┌─────────────────────────────────────────────┐ │ 触发点 A:Goal 完成(goal_completion) │ │ 触发点 B:压缩执行前(compression) │ │ 触发点 C:任务自然结束(task_completion) │ └─────────────────────────────────────────────┘ ↓ Runner 进入 knowledge_eval 侧分支 ↓ LLM 直接输出 JSON 评估结果(无工具调用) ↓ Runner 每轮即时解析并写入 knowledge_log.json ↓ 侧分支退出 → 恢复主路径 ``` --- ## 与现有系统的集成点 | 集成位置 | 文件 | 说明 | |---|---|---| | 知识注入时写 log | `agent/trace/goal_tool.py:inject_knowledge_for_goal` | `goal(focus=...)` 触发知识搜索后写入 `knowledge_log.json` | | Goal 完成时设置标志 | `agent/trace/store.py:update_goal` | 设置 `trace.context["pending_knowledge_eval"]` 标志 | | 主循环检测 Goal 完成标志 | `agent/core/runner.py:_agent_loop` | 每轮迭代开头检测标志,触发 `["knowledge_eval"]` 侧分支 | | 压缩前触发评估 | `agent/core/runner.py:_manage_context_usage` | 压缩前检查 pending,先评估再压缩 | | 任务结束兜底 | `agent/core/runner.py:_agent_loop` | 任务退出前检查 pending,强制触发评估 | | 侧分支类型扩展 | `agent/trace/models.py:Message.branch_type` | Literal 中包含 `"knowledge_eval"` | | 即时写入评估结果 | `agent/core/runner.py:_agent_loop` | 存储 assistant 消息后即时解析 JSON 并写入 | | Log 文件管理 | `agent/trace/store.py` | `append_knowledge_entry` / `update_knowledge_evaluation` / `get_pending_knowledge_entries` |