Explorar o código

feat: knowledge management config

Talegorithm hai 4 días
pai
achega
778351cd62

+ 0 - 411
agent/core/prompts.py

@@ -1,411 +0,0 @@
-"""
-Agent 系统 Prompt 集中管理
-
-本文件集中管理 Agent 系统中使用的所有 prompt 模板,
-包括 runner.py、compaction.py、subagent.py、knowledge.py 等文件中的 prompt。
-
-## 📑 目录索引
-
-### 1. 系统级 Prompt (行 50+)
-- DEFAULT_SYSTEM_PREFIX                      # Agent 基础系统提示
-
-### 2. 压缩相关 Prompt (行 60+)
-- COMPRESSION_PROMPT_TEMPLATE                # Level 2 压缩摘要
-- REFLECT_PROMPT                             # 经验反思提取
-- SUMMARY_HEADER_TEMPLATE                    # 压缩后的摘要头部
-
-### 3. 工具执行 Prompt (行 130+)
-- TRUNCATION_HINT                            # 响应截断提示
-- TOOL_INTERRUPTED_MESSAGE                   # 工具执行中断提示
-- AGENT_INTERRUPTED_SUMMARY                  # Agent 中断摘要
-- AGENT_CONTINUE_HINT_TEMPLATE               # Agent 继续执行提示
-
-### 4. 任务生成 Prompt (行 160+)
-- TASK_NAME_GENERATION_SYSTEM_PROMPT         # 任务名称生成
-- TASK_NAME_FALLBACK                         # 默认任务名称
-
-### 5. 经验管理 Prompt (行 175+)
-- EXPERIENCE_ENTRY_TEMPLATE                  # 经验条目格式
-- EXPERIENCE_PARSE_WARNING                   # 经验解析警告
-
-### 6. 辅助函数 - 基础 Prompt 构建 (行 195+)
-- build_compression_eval_prompt()            # 构建压缩摘要 prompt
-- build_summary_header()                     # 构建摘要头部
-- build_tool_interrupted_message()           # 构建工具中断消息
-- build_agent_continue_hint()                # 构建 Agent 继续提示
-- build_experience_entry()                   # 构建经验条目
-
-### 7. 子 Agent 相关 Prompt (行 240+)
-- EVALUATE_PROMPT_TEMPLATE                   # 评估任务 prompt
-- DELEGATE_RESULT_HEADER                     # 委托任务结果头部
-- DELEGATE_SAVED_KNOWLEDGE_HEADER            # 保存知识头部
-- DELEGATE_STATS_HEADER                      # 执行统计头部
-- EXPLORE_RESULT_HEADER                      # 探索结果头部
-- EXPLORE_BRANCH_TEMPLATE                    # 探索分支模板
-- EXPLORE_STATUS_SUCCESS                     # 成功状态
-- EXPLORE_STATUS_FAILED                      # 失败状态
-- EXPLORE_STATUS_ERROR                       # 错误状态
-- EXPLORE_SUMMARY_HEADER                     # 探索总结头部
-
-### 8. 辅助函数 - 子 Agent Prompt 构建 (行 300+)
-- build_evaluate_prompt()                    # 构建评估 prompt
-
-### 9. 知识管理相关 Prompt (行 340+)
-- KNOWLEDGE_SEMANTIC_ROUTE_PROMPT_TEMPLATE   # 知识语义路由
-- KNOWLEDGE_EVOLVE_PROMPT_TEMPLATE           # 知识进化重写
-- KNOWLEDGE_SLIM_PROMPT_TEMPLATE             # 知识库瘦身
-
-### 10. 辅助函数 - 知识管理 Prompt 构建 (行 390+)
-- build_knowledge_semantic_route_prompt()    # 构建知识路由 prompt
-- build_knowledge_evolve_prompt()            # 构建知识进化 prompt
-- build_knowledge_slim_prompt()              # 构建知识瘦身 prompt
-
-## 🔍 快速查找
-
-**按使用场景查找:**
-- 对话压缩:第 2 节
-- 工具调用:第 3 节
-- 经验管理:第 5 节
-- 子 Agent:第 7 节
-- 知识管理:第 9 节
-
-**按文件来源查找:**
-- runner.py → 第 1, 3, 4 节
-- compaction.py → 第 2 节
-- subagent.py → 第 7 节
-- knowledge.py → 第 9 节
-
-## ⚠️ 重要提示
-
-1. **变量占位符**:所有 `{变量名}` 格式的占位符必须保留
-2. **特殊标记**:`[[EVALUATION]]`、`[[SUMMARY]]` 等标记不可删除
-3. **输出格式关键字**:代码解析依赖的关键字需要保持一致
-4. **使用辅助函数**:优先使用 `build_*()` 函数而非直接 `.format()`
-"""
-
-# ============================================================
-# 系统级 Prompt
-# ============================================================
-
-DEFAULT_SYSTEM_PREFIX = "你是最顶尖的AI助手,可以拆分并调用工具逐步解决复杂问题。"
-
-
-# ============================================================
-# 压缩相关 Prompt - Level 2 压缩摘要
-# ============================================================
-
-COMPRESSION_PROMPT_TEMPLATE = """请对以上对话历史进行压缩总结。
-
-### 摘要要求
-1. 保留关键决策、结论和产出(如创建的文件、修改的代码、得出的分析结论)
-2. 保留重要的上下文(如用户的要求、约束条件、之前的讨论结果)
-3. 省略中间探索过程、重复的工具调用细节
-4. 使用结构化格式(标题 + 要点 + 相关资源引用,若有)
-5. 控制在 2000 字以内
-
-当前 GoalTree 状态:
-{goal_tree_prompt}
-
-格式要求:
-[[SUMMARY]]
-(此处填写结构化的摘要内容)
-"""
-
-# 保留旧名以兼容 compaction.py 的调用
-COMPRESSION_EVAL_PROMPT_TEMPLATE = COMPRESSION_PROMPT_TEMPLATE
-
-
-# ============================================================
-# 压缩相关 Prompt - 经验反思
-# ============================================================
-
-REFLECT_PROMPT = """请回顾以上整个执行过程,提取有价值的经验教训。
-你必须将经验与当前的任务意图(Intent)和环境状态(State)挂钩,以便未来精准检索。
-关注以下方面:
-1. 人工干预:用户中途的指令是否说明了原来的执行过程哪里有问题
-2. 弯路:哪些尝试是不必要的,有没有更直接的方法
-3. 好的决策:哪些判断和选择是正确的,值得记住
-4. 工具使用:哪些工具用法是高效的,哪些可以改进
-
-输出格式(严格遵守):
-- 在每条经验前加一个[]中添加自定义的标签,标签要求总结实际的内容为若干词语,包括:
-    - intent: 当前的goal
-    - state: 环境状态(如果与工具相关,可以在标签中加入工具的名称)
-- 经验标签可用自然语言描述
-- 每条经验单独成段,格式固定为:- 当 [条件] 时,应该 [动作](原因:[一句话说明])。具体案例:[案例]
-- 条目之间用一个空行分隔
-- 不输出任何标题、分类、编号、分隔线或其他结构
-- 不使用 markdown 加粗、表格、代码块等格式
-- 每条经验自包含,读者无需上下文即可理解
-- 只提取最有价值的 5-10 条,宁少勿滥
-
-示例(仅供参考格式,不要复制内容):
-- [intent:示例生成 state:用户提醒,指定样本] 当用户说"给我示例"时,应该用真实数据而不是编造(原因:编造的示例无法验证质量)。具体案例:training_samples.json 中的示例全是 LLM 自己编造的,用户明确要求"基于我指定的样本"。
-"""
-
-
-# ============================================================
-# 压缩相关 Prompt - 压缩后的摘要头部
-# ============================================================
-
-SUMMARY_HEADER_TEMPLATE = """## 对话历史摘要(自动压缩)
-
-{summary_text}
-
----
-请基于以上摘要和当前 GoalTree 继续执行任务。"""
-
-
-# ============================================================
-# 工具执行 Prompt - 响应截断提示
-# ============================================================
-
-TRUNCATION_HINT = """你的响应因为 max_tokens 限制被截断,tool call 参数不完整,未执行。请将大内容拆分为多次小的工具调用(例如用 write_file 的 append 模式分批写入)。"""
-
-
-# ============================================================
-# 工具执行 Prompt - 工具执行中断提示
-# ============================================================
-
-TOOL_INTERRUPTED_MESSAGE = """⚠️ 工具 {tool_name} 执行被中断(进程异常退出),未获得执行结果。请根据需要重新调用。"""
-
-AGENT_INTERRUPTED_SUMMARY = "⚠️ 子Agent执行被中断(进程异常退出)"
-
-AGENT_CONTINUE_HINT_TEMPLATE = '使用 continue_from="{sub_trace_id}" 可继续执行,保留已有进度'
-
-
-# ============================================================
-# 任务生成 Prompt
-# ============================================================
-
-TASK_NAME_GENERATION_SYSTEM_PROMPT = "用中文为以下任务生成一个简短标题(10-30字),只输出标题本身:"
-
-TASK_NAME_FALLBACK = "未命名任务"
-
-
-# ============================================================
-# 经验保存 Prompt - 经验条目格式
-# ============================================================
-
-EXPERIENCE_ENTRY_TEMPLATE = """---
-id: {ex_id}
-trace_id: {trace_id}
-tags: {{intent: {intents}, state: {states}}}
-metrics: {{helpful: 1, harmful: 0}}
-created_at: {created_at}
----
-{content}
-"""
-
-
-# ============================================================
-# 经验评估 Prompt - 格式解析警告
-# ============================================================
-
-EXPERIENCE_PARSE_WARNING = "未能解析出符合格式的经验条目,请检查 REFLECT_PROMPT。"
-
-
-# ============================================================
-# 辅助函数:构建特定场景的 Prompt
-# ============================================================
-
-def build_compression_eval_prompt(
-    goal_tree_prompt: str,
-    ex_reference_list: str = ""
-) -> str:
-    """构建 Level 2 压缩 prompt"""
-    return COMPRESSION_EVAL_PROMPT_TEMPLATE.format(
-        goal_tree_prompt=goal_tree_prompt,
-        ex_reference_list=ex_reference_list
-    )
-
-
-def build_summary_header(summary_text: str) -> str:
-    """构建压缩后的摘要头部"""
-    return SUMMARY_HEADER_TEMPLATE.format(summary_text=summary_text)
-
-
-def build_tool_interrupted_message(tool_name: str) -> str:
-    """构建工具中断消息"""
-    return TOOL_INTERRUPTED_MESSAGE.format(tool_name=tool_name)
-
-
-def build_agent_continue_hint(sub_trace_id: str) -> str:
-    """构建 Agent 继续执行提示"""
-    return AGENT_CONTINUE_HINT_TEMPLATE.format(sub_trace_id=sub_trace_id)
-
-
-def build_experience_entry(
-    ex_id: str,
-    trace_id: str,
-    intents: list,
-    states: list,
-    created_at: str,
-    content: str
-) -> str:
-    """构建经验条目"""
-    return EXPERIENCE_ENTRY_TEMPLATE.format(
-        ex_id=ex_id,
-        trace_id=trace_id,
-        intents=intents,
-        states=states,
-        created_at=created_at,
-        content=content
-    )
-
-# ============================================================
-# 子 Agent 相关 Prompt - 评估任务
-# ============================================================
-
-EVALUATE_PROMPT_TEMPLATE = """# 评估任务
-
-请评估以下任务的执行结果是否满足要求。
-
-## 目标描述
-
-{goal_description}
-
-## 执行结果
-
-{result_text}
-
-## 输出格式
-
-## 评估结论
-[通过/不通过]
-
-## 评估理由
-[详细说明通过或不通过原因]
-
-## 修改建议(如果不通过)
-1. [建议1]
-2. [建议2]
-"""
-
-
-# ============================================================
-# 子 Agent 相关 Prompt - 结果格式化
-# ============================================================
-
-DELEGATE_RESULT_HEADER = "## 委托任务完成\n"
-
-DELEGATE_SAVED_KNOWLEDGE_HEADER = "**保存的知识** ({count} 条):"
-
-DELEGATE_STATS_HEADER = "**执行统计**:"
-
-EXPLORE_RESULT_HEADER = "## 探索结果\n"
-
-EXPLORE_BRANCH_TEMPLATE = "### 方案 {branch_name}: {task}"
-
-EXPLORE_STATUS_SUCCESS = "**状态**: ✓ 完成"
-
-EXPLORE_STATUS_FAILED = "**状态**: ✗ 失败"
-
-EXPLORE_STATUS_ERROR = "**状态**: ✗ 异常"
-
-EXPLORE_SUMMARY_HEADER = "## 总结"
-
-
-# ============================================================
-# 辅助函数:构建子 Agent 相关 Prompt
-# ============================================================
-
-def build_evaluate_prompt(goal_description: str, result_text: str) -> str:
-    """构建评估 prompt"""
-    return EVALUATE_PROMPT_TEMPLATE.format(
-        goal_description=goal_description,
-        result_text=result_text or "(无执行结果)"
-    )
-
-
-# ============================================================
-# 知识管理相关 Prompt - 语义路由
-# ============================================================
-
-KNOWLEDGE_SEMANTIC_ROUTE_PROMPT_TEMPLATE = """你是一个知识检索专家。根据用户的当前任务需求,从下列原子知识元数据中挑选出最相关的最多 {routing_k} 个知识 ID。
-任务需求:"{query_text}"
-
-可选知识列表:
-{routing_data}
-
-请直接输出 ID 列表,用逗号分隔(例如: knowledge-20260302-001, research-20260302-002)。若无相关项请输出 "None"。
-"""
-
-
-# ============================================================
-# 知识管理相关 Prompt - 知识进化重写
-# ============================================================
-
-KNOWLEDGE_EVOLVE_PROMPT_TEMPLATE = """你是一个 AI Agent 知识库管理员。请根据反馈建议,对现有的知识内容进行重写进化。
-
-【原知识内容】:
-{old_content}
-
-【实战反馈建议】:
-{feedback}
-
-【重写要求】:
-1. 融合知识:将反馈中的避坑指南、新参数或修正后的选择逻辑融入原知识,使其更具通用性和准确性。
-2. 保持结构:如果原内容有特定格式(如 Markdown、代码示例等),请保持该格式。
-3. 语言:简洁直接,使用中文。
-4. 禁止:严禁输出任何开场白、解释语或额外的 Markdown 标题,直接返回重写后的正文。
-"""
-
-
-# ============================================================
-# 知识管理相关 Prompt - 知识库瘦身
-# ============================================================
-
-KNOWLEDGE_SLIM_PROMPT_TEMPLATE = """你是一个 AI Agent 知识库管理员。以下是当前知识库的全部条目,请执行瘦身操作:
-
-【任务】:
-1. 识别语义高度相似或重复的知识,将它们合并为一条更精炼、更通用的知识。
-2. 合并时保留 helpful 最高的那条的 ID 和 metrics(metrics 中 helpful/harmful 取各条之和)。
-3. 对于独立的、无重复的知识,保持原样不动。
-4. 保持原有的知识结构和格式。
-
-【当前知识库】:
-{entries_text}
-
-【输出格式要求】:
-严格按以下格式输出每条知识,条目之间用 === 分隔:
-ID: <保留的id>
-TAGS: <yaml格式的tags>
-METRICS: <yaml格式的metrics>
-SCORE: <评分>
-SCENARIO: <场景描述>
-CONTENT: <知识内容>
-===
-"""
-
-
-# ============================================================
-# 辅助函数:构建知识管理相关 Prompt
-# ============================================================
-
-def build_knowledge_semantic_route_prompt(
-    query_text: str,
-    routing_data: str,
-    routing_k: int
-) -> str:
-    """构建知识语义路由 prompt"""
-    return KNOWLEDGE_SEMANTIC_ROUTE_PROMPT_TEMPLATE.format(
-        query_text=query_text,
-        routing_data=routing_data,
-        routing_k=routing_k
-    )
-
-
-def build_knowledge_evolve_prompt(old_content: str, feedback: str) -> str:
-    """构建知识进化重写 prompt"""
-    return KNOWLEDGE_EVOLVE_PROMPT_TEMPLATE.format(
-        old_content=old_content,
-        feedback=feedback
-    )
-
-
-def build_knowledge_slim_prompt(entries_text: str) -> str:
-    """构建知识库瘦身 prompt"""
-    return KNOWLEDGE_SLIM_PROMPT_TEMPLATE.format(
-        entries_text=entries_text
-    )

+ 58 - 0
agent/core/prompts/__init__.py

@@ -0,0 +1,58 @@
+"""
+agent.core.prompts - Agent 系统 Prompt 集中管理
+
+子模块:
+- runner.py     系统提示、工具中断、任务命名、经验格式
+- knowledge.py  知识反思提取(压缩时 + 任务完成后)
+- compression.py  消息压缩总结
+- subagent.py   子 Agent 评估、结果格式化、知识管理
+"""
+
+from agent.core.prompts.runner import (
+    DEFAULT_SYSTEM_PREFIX,
+    TRUNCATION_HINT,
+    TOOL_INTERRUPTED_MESSAGE,
+    AGENT_INTERRUPTED_SUMMARY,
+    AGENT_CONTINUE_HINT_TEMPLATE,
+    TASK_NAME_GENERATION_SYSTEM_PROMPT,
+    TASK_NAME_FALLBACK,
+    build_tool_interrupted_message,
+    build_agent_continue_hint,
+)
+
+from agent.core.prompts.knowledge import (
+    REFLECT_PROMPT,
+    COMPLETION_REFLECT_PROMPT,
+    build_reflect_prompt,
+)
+
+from agent.core.prompts.compression import (
+    COMPRESSION_PROMPT_TEMPLATE,
+    COMPRESSION_EVAL_PROMPT_TEMPLATE,
+    SUMMARY_HEADER_TEMPLATE,
+    build_compression_eval_prompt,
+    build_summary_header,
+)
+
+__all__ = [
+    # runner
+    "DEFAULT_SYSTEM_PREFIX",
+    "TRUNCATION_HINT",
+    "TOOL_INTERRUPTED_MESSAGE",
+    "AGENT_INTERRUPTED_SUMMARY",
+    "AGENT_CONTINUE_HINT_TEMPLATE",
+    "TASK_NAME_GENERATION_SYSTEM_PROMPT",
+    "TASK_NAME_FALLBACK",
+    "build_tool_interrupted_message",
+    "build_agent_continue_hint",
+    # knowledge
+    "REFLECT_PROMPT",
+    "COMPLETION_REFLECT_PROMPT",
+    "build_reflect_prompt",
+    # compression
+    "COMPRESSION_PROMPT_TEMPLATE",
+    "COMPRESSION_EVAL_PROMPT_TEMPLATE",
+    "SUMMARY_HEADER_TEMPLATE",
+    "build_compression_eval_prompt",
+    "build_summary_header",
+]

+ 50 - 0
agent/core/prompts/compression.py

@@ -0,0 +1,50 @@
+"""
+压缩相关 Prompt
+
+包含 Level 2 消息压缩(LLM 总结)使用的 prompt。
+"""
+
+# ===== 压缩总结 =====
+
+COMPRESSION_PROMPT_TEMPLATE = """请对以上对话历史进行压缩总结。
+
+### 摘要要求
+1. 保留关键决策、结论和产出(如创建的文件、修改的代码、得出的分析结论)
+2. 保留重要的上下文(如用户的要求、约束条件、之前的讨论结果)
+3. 省略中间探索过程、重复的工具调用细节
+4. 使用结构化格式(标题 + 要点 + 相关资源引用,若有)
+5. 控制在 2000 字以内
+
+当前 GoalTree 状态:
+{goal_tree_prompt}
+
+格式要求:
+[[SUMMARY]]
+(此处填写结构化的摘要内容)
+"""
+
+# 保留旧名以兼容 compaction.py 的调用
+COMPRESSION_EVAL_PROMPT_TEMPLATE = COMPRESSION_PROMPT_TEMPLATE
+
+SUMMARY_HEADER_TEMPLATE = """## 对话历史摘要(自动压缩)
+
+{summary_text}
+
+---
+*以上为压缩摘要,原始对话历史已归档。*
+"""
+
+# ===== 辅助函数 =====
+
+def build_compression_eval_prompt(
+    goal_tree_prompt: str,
+    ex_reference_list: str = "",
+) -> str:
+    return COMPRESSION_EVAL_PROMPT_TEMPLATE.format(
+        goal_tree_prompt=goal_tree_prompt,
+        ex_reference_list=ex_reference_list,
+    )
+
+
+def build_summary_header(summary_text: str) -> str:
+    return SUMMARY_HEADER_TEMPLATE.format(summary_text=summary_text)

+ 61 - 0
agent/core/prompts/knowledge.py

@@ -0,0 +1,61 @@
+"""
+知识提取相关 Prompt
+
+两个场景,各自独立配置:
+- REFLECT_PROMPT:            压缩时阶段性反思(消息量超阈值,对当前批历史提炼)
+- COMPLETION_REFLECT_PROMPT: 任务完成后全局复盘(对整个任务的全局视角)
+
+两个 prompt 都要求 LLM 直接调用 `knowledge_save` 工具保存经验,
+而不是输出结构化文本再由 runner 解析。
+"""
+
+# ===== 压缩时阶段性反思 =====
+
+REFLECT_PROMPT = """请回顾以上执行过程,将值得沉淀的经验直接用 `knowledge_save` 工具保存到知识库。
+
+**关注以下方面**:
+1. 人工干预:用户中途的指令说明了哪里出了问题
+2. 弯路:哪些尝试是不必要的,有没有更直接的方法
+3. 好的决策:哪些判断和选择是正确的,值得记住
+4. 工具使用:哪些工具用法是高效的,哪些可以改进
+
+**每条经验调用一次 `knowledge_save`,参数说明**:
+- `task`: 这条经验适用的场景,格式:「在[什么情境]下,[要完成什么]」
+- `content`: 具体经验内容,格式:「当[条件]时,应该[动作](原因:[一句话])。案例:[具体案例]」
+- `types`: 选 `["strategy"]`;如果涉及工具用法也可加 `"tool"`
+- `tags`: 用 `intent`(任务意图)和 `state`(环境状态/相关工具名)标注,便于检索
+- `score`: 1-5,根据这条经验的价值评估
+
+**注意**:
+- 只保存最有价值的 3-8 条,宁少勿滥
+- 不需要输出任何文字,直接调用工具即可
+- 如果没有值得保存的经验,不调用任何工具
+"""
+
+
+# ===== 任务完成后全局复盘 =====
+
+COMPLETION_REFLECT_PROMPT = """请对整个任务进行复盘,将值得沉淀的经验直接用 `knowledge_save` 工具保存到知识库。
+
+与压缩时的阶段性反思不同,这是任务结束后的全局视角,关注:
+1. 任务整体路径:实际走的路径与最初计划的偏差
+2. 关键决策点:哪些决策显著影响了最终结果
+3. 可复用的模式:哪些做法在类似任务中可以直接复用
+4. 踩过的坑:哪些问题本可提前规避
+
+**每条经验调用一次 `knowledge_save`,参数说明**:
+- `task`: 这条经验适用的场景,格式:「在[什么情境]下,[要完成什么]」
+- `content`: 具体经验内容,格式:「当[条件]时,应该[动作](原因:[一句话])。案例:[具体案例]」
+- `types`: 选 `["strategy"]`;如果涉及工具用法也可加 `"tool"`
+- `tags`: 用 `intent`(任务意图)和 `state`(环境状态/相关工具名)标注,便于检索
+- `score`: 1-5,根据这条经验的价值评估
+
+**注意**:
+- 只保存最有价值的 2-5 条,宁少勿滥
+- 不需要输出任何文字,直接调用工具即可
+- 如果没有值得保存的经验,不调用任何工具
+"""
+
+
+def build_reflect_prompt() -> str:
+    return REFLECT_PROMPT

+ 39 - 0
agent/core/prompts/runner.py

@@ -0,0 +1,39 @@
+"""
+Runner 相关 Prompt
+
+包含 AgentRunner 主循环使用的 prompt:
+- 系统提示前缀
+- 工具执行中断提示
+- 任务名称生成
+- 经验条目格式
+"""
+
+# ===== 系统提示 =====
+
+DEFAULT_SYSTEM_PREFIX = "你是最顶尖的AI助手,可以拆分并调用工具逐步解决复杂问题。"
+
+# ===== 工具执行 =====
+
+TRUNCATION_HINT = """你的响应因为 max_tokens 限制被截断,tool call 参数不完整,未执行。请将大内容拆分为多次小的工具调用(例如用 write_file 的 append 模式分批写入)。"""
+
+TOOL_INTERRUPTED_MESSAGE = """⚠️ 工具 {tool_name} 执行被中断(进程异常退出),未获得执行结果。请根据需要重新调用。"""
+
+AGENT_INTERRUPTED_SUMMARY = "⚠️ 子Agent执行被中断(进程异常退出)"
+
+AGENT_CONTINUE_HINT_TEMPLATE = '使用 continue_from="{sub_trace_id}" 可继续执行,保留已有进度'
+
+# ===== 任务命名 =====
+
+TASK_NAME_GENERATION_SYSTEM_PROMPT = "用中文为以下任务生成一个简短标题(10-30字),只输出标题本身:"
+
+TASK_NAME_FALLBACK = "未命名任务"
+
+# ===== 辅助函数 =====
+
+def build_tool_interrupted_message(tool_name: str) -> str:
+    return TOOL_INTERRUPTED_MESSAGE.format(tool_name=tool_name)
+
+
+def build_agent_continue_hint(sub_trace_id: str) -> str:
+    return AGENT_CONTINUE_HINT_TEMPLATE.format(sub_trace_id=sub_trace_id)
+

+ 130 - 132
agent/core/runner.py

@@ -26,7 +26,6 @@ from typing import AsyncIterator, Optional, Dict, Any, List, Callable, Literal,
 from agent.trace.models import Trace, Message
 from agent.trace.models import Trace, Message
 from agent.trace.protocols import TraceStore
 from agent.trace.protocols import TraceStore
 from agent.trace.goal_models import GoalTree
 from agent.trace.goal_models import GoalTree
-from agent.tools.builtin.knowledge import knowledge_save, knowledge_batch_update
 from agent.trace.compaction import (
 from agent.trace.compaction import (
     CompressionConfig,
     CompressionConfig,
     filter_by_goal_status,
     filter_by_goal_status,
@@ -46,17 +45,42 @@ from agent.core.prompts import (
     AGENT_CONTINUE_HINT_TEMPLATE,
     AGENT_CONTINUE_HINT_TEMPLATE,
     TASK_NAME_GENERATION_SYSTEM_PROMPT,
     TASK_NAME_GENERATION_SYSTEM_PROMPT,
     TASK_NAME_FALLBACK,
     TASK_NAME_FALLBACK,
-    EXPERIENCE_PARSE_WARNING,
     SUMMARY_HEADER_TEMPLATE,
     SUMMARY_HEADER_TEMPLATE,
+    COMPLETION_REFLECT_PROMPT,
     build_summary_header,
     build_summary_header,
     build_tool_interrupted_message,
     build_tool_interrupted_message,
     build_agent_continue_hint,
     build_agent_continue_hint,
-    build_experience_entry,
 )
 )
 
 
 logger = logging.getLogger(__name__)
 logger = logging.getLogger(__name__)
 
 
 
 
+# ===== 知识管理配置 =====
+
+@dataclass
+class KnowledgeConfig:
+    """知识提取与注入的配置"""
+
+    # 压缩时提取(消息量超阈值触发压缩时,在 Level 1 过滤前用完整 history 反思)
+    enable_extraction: bool = True         # 是否在压缩触发时提取知识
+    reflect_prompt: str = ""               # 自定义反思 prompt;空则使用默认,见 agent/core/prompts/knowledge.py:REFLECT_PROMPT
+
+    # agent运行完成后提取(不代表任务完成,agent 可能中途退出等待人工评估)
+    enable_completion_extraction: bool = True      # 是否在运行完成后提取知识
+    completion_reflect_prompt: str = ""            # 自定义复盘 prompt;空则使用默认,见 agent/core/prompts/knowledge.py:COMPLETION_REFLECT_PROMPT
+
+    # 知识注入(agent切换当前工作的goal时,自动注入相关知识)
+    enable_injection: bool = True          # 是否在 focus goal 时自动注入相关知识
+
+    def get_reflect_prompt(self) -> str:
+        """压缩时反思 prompt"""
+        return self.reflect_prompt if self.reflect_prompt else build_reflect_prompt()
+
+    def get_completion_reflect_prompt(self) -> str:
+        """任务完成后复盘 prompt"""
+        return self.completion_reflect_prompt if self.completion_reflect_prompt else COMPLETION_REFLECT_PROMPT
+
+
 # ===== 运行配置 =====
 # ===== 运行配置 =====
 
 
 @dataclass
 @dataclass
@@ -93,8 +117,8 @@ class RunConfig:
     # --- 额外 LLM 参数(传给 llm_call 的 **kwargs)---
     # --- 额外 LLM 参数(传给 llm_call 的 **kwargs)---
     extra_llm_params: Dict[str, Any] = field(default_factory=dict)
     extra_llm_params: Dict[str, Any] = field(default_factory=dict)
 
 
-    # --- 研究流程控制 ---
-    enable_research_flow: bool = False  # 已废弃,保留字段避免调用方传参报错
+    # --- 知识管理配置 ---
+    knowledge: KnowledgeConfig = field(default_factory=KnowledgeConfig)
 
 
 
 
     # 内置工具列表(始终自动加载)
     # 内置工具列表(始终自动加载)
@@ -685,6 +709,14 @@ class AgentRunner:
             else:
             else:
                 print(f"[压缩评估] ✅ 未超阈值,无需压缩")
                 print(f"[压缩评估] ✅ 未超阈值,无需压缩")
 
 
+            # 知识提取:在任何压缩发生前,用完整 history 做反思
+            if needs_compression and config.knowledge.enable_extraction:
+                await self._run_reflect(
+                    trace_id, history, config,
+                    reflect_prompt=config.knowledge.get_reflect_prompt(),
+                    source_name="compression_reflection",
+                )
+
             if needs_compression and self.trace_store and goal_tree:
             if needs_compression and self.trace_store and goal_tree:
                 # 使用本地 head_seq(store 中的 head_sequence 在 loop 期间未更新,是过时的)
                 # 使用本地 head_seq(store 中的 head_sequence 在 loop 期间未更新,是过时的)
                 if head_seq > 0:
                 if head_seq > 0:
@@ -1009,6 +1041,10 @@ class AgentRunner:
             # 无工具调用,任务完成
             # 无工具调用,任务完成
             break
             break
 
 
+        # 任务完成后复盘提取
+        if config.knowledge.enable_completion_extraction:
+            await self._extract_knowledge_on_completion(trace_id, history, config)
+
         # 更新 head_sequence 并完成 Trace
         # 更新 head_sequence 并完成 Trace
         if self.trace_store:
         if self.trace_store:
             await self.trace_store.update_trace(
             await self.trace_store.update_trace(
@@ -1035,10 +1071,9 @@ class AgentRunner:
         """
         """
         Level 2 压缩:LLM 总结
         Level 2 压缩:LLM 总结
 
 
-        Step 1: 经验提取(reflect)— 纯内存 LLM 调用 + 文件追加,不影响 trace
-        Step 2: 压缩总结 — LLM 生成 summary
-        Step 3: 存储 summary 为新消息,parent_sequence 跳到 system msg
-        Step 4: 重建 history
+        Step 1: 压缩总结 — LLM 生成 summary
+        Step 2: 存储 summary 为新消息,parent_sequence 跳到 system msg
+        Step 3: 重建 history
 
 
         Returns:
         Returns:
             (new_history, new_head_seq, next_sequence)
             (new_history, new_head_seq, next_sequence)
@@ -1071,129 +1106,7 @@ class AgentRunner:
             logger.warning("Level 2 压缩跳过:未找到 system message")
             logger.warning("Level 2 压缩跳过:未找到 system message")
             return history, head_seq, sequence
             return history, head_seq, sequence
 
 
-        # --- Step 1: 经验提取(reflect)---
-        try:
-            # 1. 构造 Reflect Prompt(确保包含格式要求)
-            # 建议在 build_reflect_prompt() 里加入:
-            # "请使用格式:- [intent: 意图, state: 状态描述] 具体的经验内容"
-            reflect_prompt = build_reflect_prompt()
-            reflect_messages = list(history) + [{"role": "user", "content": reflect_prompt}]
-
-            # 应用 Prompt Caching
-            reflect_messages = self._add_cache_control(
-                reflect_messages,
-                config.model,
-                config.enable_prompt_caching
-            )
-
-            reflect_result = await self.llm_call(
-                messages=reflect_messages,
-                model=config.model,
-                tools=[],
-                temperature=0.2, # 略微保持一点发散性
-                **config.extra_llm_params,
-            )
-
-            reflection_text = reflect_result.get("content", "").strip()
-            
-            if reflection_text:
-                import re as _re2
-                import uuid as _uuid2
-
-                pattern = r"-\s*\[(?P<tags>.*?)\]\s*(?P<content>.*)"
-                matches = list(_re2.finditer(pattern, reflection_text))
-
-                structured_entries = []
-                for match in matches:
-                    tags_str = match.group("tags")
-                    content = match.group("content")
-
-                    intent_match = _re2.search(r"intent:\s*(.*?)(?:,|$)", tags_str, _re2.IGNORECASE)
-                    state_match = _re2.search(r"state:\s*(.*?)(?:,|$)", tags_str, _re2.IGNORECASE)
-
-                    intents = [i.strip() for i in intent_match.group(1).split(",")] if intent_match and intent_match.group(1) else []
-                    states = [s.strip() for s in state_match.group(1).split(",")] if state_match and state_match.group(1) else []
-
-                    ex_id = f"ex_{datetime.now().strftime('%m%d%H%M')}_{_uuid2.uuid4().hex[:4]}"
-                    entry = build_experience_entry(
-                        ex_id=ex_id,
-                        trace_id=trace_id,
-                        intents=intents,
-                        states=states,
-                        created_at=datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
-                        content=f"- {content}\n- 经验ID: [{ex_id}]"
-                    )
-                    structured_entries.append(entry)
-
-                if structured_entries:
-                    # 保存经验为知识(strategy 标签)
-                    saved_count = 0
-                    for entry in structured_entries:
-                        try:
-                            # 从 entry 中提取信息
-                            lines = entry.split("\n")
-                            ex_id = ""
-                            intents = []
-                            states = []
-                            content = ""
-
-                            for line in lines:
-                                if line.startswith("id:"):
-                                    ex_id = line.split(":", 1)[1].strip()
-                                elif line.startswith("tags:"):
-                                    tags_match = _re2.search(r"intent:\s*\[(.*?)\].*state:\s*\[(.*?)\]", line)
-                                    if tags_match:
-                                        intents_str = tags_match.group(1).strip("'\"")
-                                        states_str = tags_match.group(2).strip("'\"")
-                                        intents = [i.strip().strip("'\"") for i in intents_str.split(",") if i.strip()]
-                                        states = [s.strip().strip("'\"") for s in states_str.split(",") if s.strip()]
-                                elif line.startswith("- ") and not line.startswith("- 经验ID:"):
-                                    content = line[2:].strip()
-
-                            # 构建 task(从 intent 和 state 生成)
-                            task_parts = []
-                            if intents:
-                                task_parts.append(f"意图: {', '.join(intents)}")
-                            if states:
-                                task_parts.append(f"状态: {', '.join(states)}")
-                            task = " | ".join(task_parts) if task_parts else "通用经验"
-
-                            # 构建 tags(将 intents 和 states 作为业务标签)
-                            tags = {}
-                            if intents:
-                                tags["intent"] = ", ".join(intents)
-                            if states:
-                              tags["state"] = ", ".join(states)
-
-                            # 调用 knowledge_save 保存为 strategy 类型的知识
-                            result = await knowledge_save(
-                                task=task,
-                                content=content,
-                                types=["strategy"],
-                                tags=tags,
-                                urls=[],
-                                agent_id="runner",
-                                source_name="compression_reflection",
-                                source_category="exp",
-                                score=3,
-                                message_id=trace_id  # 使用 trace_id 作为 message_id
-                            )
-                            saved_count += 1
-                        except Exception as e:
-                            logger.warning(f"保存经验失败: {e}")
-                            continue
-
-                    logger.info(f"已提取并保存 {saved_count}/{len(structured_entries)} 条结构化经验到知识库")
-                else:
-                    logger.warning(EXPERIENCE_PARSE_WARNING)
-                    logger.debug(f"LLM Raw Output:\n{reflection_text}")
-            else:
-                logger.warning("LLM 未生成反思内容")
-
-        except Exception as e:
-            logger.error(f"Level 2 经验提取失败: {e}")
-
-        # --- Step 2: 压缩总结 ---
+        # --- Step 1: 压缩总结 ---
         compress_prompt = build_compression_prompt(goal_tree)
         compress_prompt = build_compression_prompt(goal_tree)
         compress_messages = list(history) + [{"role": "user", "content": compress_prompt}]
         compress_messages = list(history) + [{"role": "user", "content": compress_prompt}]
 
 
@@ -1261,6 +1174,91 @@ class AgentRunner:
 
 
         return new_history, new_head_seq, sequence
         return new_history, new_head_seq, sequence
 
 
+    async def _run_reflect(
+        self,
+        trace_id: str,
+        history: List[Dict],
+        config: RunConfig,
+        reflect_prompt: str,
+        source_name: str,
+    ) -> None:
+        """
+        执行反思提取:LLM 对历史消息进行反思,直接调用 knowledge_save 工具保存经验。
+
+        Args:
+            trace_id: Trace ID(作为知识的 message_id)
+            history: 当前对话历史
+            config: 运行配置
+            reflect_prompt: 反思 prompt
+            source_name: 来源名称(用于区分压缩时/完成时)
+        """
+        try:
+            reflect_messages = list(history) + [{"role": "user", "content": reflect_prompt}]
+            reflect_messages = self._add_cache_control(
+                reflect_messages, config.model, config.enable_prompt_caching
+            )
+
+            # 只暴露 knowledge_save 工具,让 LLM 直接调用
+            knowledge_save_schema = self._get_tool_schemas(["knowledge_save"])
+
+            reflect_result = await self.llm_call(
+                messages=reflect_messages,
+                model=config.model,
+                tools=knowledge_save_schema,
+                temperature=0.2,
+                **config.extra_llm_params,
+            )
+
+            tool_calls = reflect_result.get("tool_calls") or []
+            if not tool_calls:
+                logger.info("反思阶段无经验保存 (source=%s)", source_name)
+                return
+
+            saved_count = 0
+            for tc in tool_calls:
+                tool_name = tc.get("function", {}).get("name")
+                if tool_name != "knowledge_save":
+                    continue
+
+                tool_args = tc.get("function", {}).get("arguments") or {}
+                if isinstance(tool_args, str):
+                    tool_args = json.loads(tool_args) if tool_args.strip() else {}
+
+                # 注入来源信息(LLM 不需要填写这些字段)
+                tool_args.setdefault("source_name", source_name)
+                tool_args.setdefault("source_category", "exp")
+                tool_args.setdefault("message_id", trace_id)
+
+                try:
+                    await self.tools.execute(
+                        "knowledge_save",
+                        tool_args,
+                        uid=config.uid or "",
+                        context={"store": self.trace_store, "trace_id": trace_id},
+                    )
+                    saved_count += 1
+                except Exception as e:
+                    logger.warning("保存经验失败: %s", e)
+
+            logger.info("已提取并保存 %d 条经验 (source=%s)", saved_count, source_name)
+
+        except Exception as e:
+            logger.error("知识反思提取失败 (source=%s): %s", source_name, e)
+
+    async def _extract_knowledge_on_completion(
+        self,
+        trace_id: str,
+        history: List[Dict],
+        config: RunConfig,
+    ) -> None:
+        """任务完成后执行全局复盘,提取经验保存到知识库。"""
+        logger.info("任务完成后复盘提取: trace=%s", trace_id)
+        await self._run_reflect(
+            trace_id, history, config,
+            reflect_prompt=config.knowledge.get_completion_reflect_prompt(),
+            source_name="completion_reflection",
+        )
+
     # ===== 回溯(Rewind)=====
     # ===== 回溯(Rewind)=====
 
 
     async def _rewind(
     async def _rewind(

+ 56 - 13
agent/tools/builtin/subagent.py

@@ -14,19 +14,62 @@ from agent.trace.models import Trace, Messages
 from agent.trace.trace_id import generate_sub_trace_id
 from agent.trace.trace_id import generate_sub_trace_id
 from agent.trace.goal_models import GoalTree
 from agent.trace.goal_models import GoalTree
 from agent.trace.websocket import broadcast_sub_trace_started, broadcast_sub_trace_completed
 from agent.trace.websocket import broadcast_sub_trace_started, broadcast_sub_trace_completed
-from agent.core.prompts import (
-    EVALUATE_PROMPT_TEMPLATE,
-    DELEGATE_RESULT_HEADER,
-    DELEGATE_SAVED_KNOWLEDGE_HEADER,
-    DELEGATE_STATS_HEADER,
-    EXPLORE_RESULT_HEADER,
-    EXPLORE_BRANCH_TEMPLATE,
-    EXPLORE_STATUS_SUCCESS,
-    EXPLORE_STATUS_FAILED,
-    EXPLORE_STATUS_ERROR,
-    EXPLORE_SUMMARY_HEADER,
-    build_evaluate_prompt,
-)
+
+
+# ===== prompts =====
+
+# ===== 评估任务 =====
+
+EVALUATE_PROMPT_TEMPLATE = """# 评估任务
+
+请评估以下任务的执行结果是否满足要求。
+
+## 目标描述
+
+{goal_description}
+
+## 执行结果
+
+{result_text}
+
+## 输出格式
+
+## 评估结论
+[通过/不通过]
+
+## 评估理由
+[详细说明通过或不通过原因]
+
+## 修改建议(如果不通过)
+1. [建议1]
+2. [建议2]
+"""
+
+# ===== 结果格式化 =====
+
+DELEGATE_RESULT_HEADER = "## 委托任务完成\n"
+
+DELEGATE_SAVED_KNOWLEDGE_HEADER = "**保存的知识** ({count} 条):"
+
+DELEGATE_STATS_HEADER = "**执行统计**:"
+
+EXPLORE_RESULT_HEADER = "## 探索结果\n"
+
+EXPLORE_BRANCH_TEMPLATE = "### 方案 {branch_name}: {task}"
+
+EXPLORE_STATUS_SUCCESS = "**状态**: ✓ 完成"
+
+EXPLORE_STATUS_FAILED = "**状态**: ✗ 失败"
+
+EXPLORE_STATUS_ERROR = "**状态**: ✗ 异常"
+
+EXPLORE_SUMMARY_HEADER = "## 总结"
+
+def build_evaluate_prompt(goal_description: str, result_text: str) -> str:
+    return EVALUATE_PROMPT_TEMPLATE.format(
+        goal_description=goal_description,
+        result_text=result_text or "(无执行结果)",
+    )
 
 
 
 
 def _make_run_config(**kwargs):
 def _make_run_config(**kwargs):

+ 51 - 0
knowhub/kb_manage_prompts.py

@@ -0,0 +1,51 @@
+"""
+知识库管理相关 Prompt
+
+包含知识库管理 prompt(供 KnowHub Server 调用)。
+"""
+
+KNOWLEDGE_SEMANTIC_ROUTE_PROMPT_TEMPLATE = """你是一个知识检索专家。根据用户的当前任务需求,从下列原子知识元数据中挑选出最相关的最多 {routing_k} 个知识 ID。
+任务需求:"{query_text}"
+
+可选知识列表:
+{routing_data}
+
+请直接输出 ID 列表,用逗号分隔(例如: knowledge-20260302-001, research-20260302-002)。若无相关项请输出 "None"。
+"""
+
+KNOWLEDGE_EVOLVE_PROMPT_TEMPLATE = """你是一个 AI Agent 知识库管理员。请根据反馈建议,对现有的知识内容进行重写进化。
+
+【原知识内容】:
+{old_content}
+
+【实战反馈建议】:
+{feedback}
+
+【重写要求】:
+1. 融合知识:将反馈中的避坑指南、新参数或修正后的选择逻辑融入原知识,使其更具通用性和准确性。
+2. 保持结构:如果原内容有特定格式(如 Markdown、代码示例等),请保持该格式。
+3. 语言:简洁直接,使用中文。
+4. 禁止:严禁输出任何开场白、解释语或额外的 Markdown 标题,直接返回重写后的正文。
+"""
+
+KNOWLEDGE_SLIM_PROMPT_TEMPLATE = """你是一个 AI Agent 知识库管理员。以下是当前知识库的全部条目,请执行瘦身操作:
+
+【任务】:
+1. 识别语义高度相似或重复的知识,将它们合并为一条更精炼、更通用的知识。
+2. 合并时保留 helpful 最高的那条的 ID 和 metrics(metrics 中 helpful/harmful 取各条之和)。
+3. 对于独立的、无重复的知识,保持原样不动。
+4. 保持原有的知识结构和格式。
+
+【当前知识库】:
+{entries_text}
+
+【输出格式要求】:
+严格按以下格式输出每条知识,条目之间用 === 分隔:
+ID: <保留的id>
+TAGS: <yaml格式的tags>
+METRICS: <yaml格式的metrics>
+SCORE: <评分>
+SCENARIO: <场景描述>
+CONTENT: <知识内容>
+===
+"""

+ 1 - 1
knowhub/server.py

@@ -1088,4 +1088,4 @@ REPORT: 原有 X 条,合并后 Y 条,精简了 Z 条。
 
 
 if __name__ == "__main__":
 if __name__ == "__main__":
     import uvicorn
     import uvicorn
-    uvicorn.run(app, host="0.0.0.0", port=8000)
+    uvicorn.run(app, host="0.0.0.0", port=999)