feedback-timing-design.md 9.5 KB

知识反馈时机设计文档

文档维护规范

  1. 先改文档,再动代码 - 新功能或重大修改需先完成文档更新、并完成审阅后,再进行代码实现;除非改动较小、不被文档涵盖
  2. 文档分层,链接代码 - 重要或复杂设计可以另有详细文档;关键实现需标注代码文件路径;格式:module/file.py:function_name
  3. 简洁快照,日志分离 - 只记录最重要的、与代码准确对应的或者明确的已完成的设计的信息,避免推测、建议、决策历史、修改日志、大量代码;决策依据或修改日志若有必要,可在 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

数据结构

{
  "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 完成(completedabandoned)触发
"compression" 由上下文压缩触发(压缩前必须先评估)
"task_completion" 由任务自然结束触发(主路径无工具调用退出时兜底)

注意:同一个 knowledge_id 可能在不同 Goal 中被多次注入,每次产生独立 entry。评估时优先更新最近注入(injected_at_sequence 最大)的未评估条目。


评估触发机制

触发点 1:Goal 完成

时机:Goal status 变为 completedabandoned

触发逻辑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):

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,无需调用工具

{
  "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