AstroResearch/docs/architecture/agent/system-prompt.md
Asfmq f6df9d8136 feat: Agent 思考模式前端可控、子代理全链路持久化、权限系统、工具 ID 追踪体系、前端面板与文档架构重构
- AgentConfig/LlmClient 新增 enable_thinking 参数,前端 SSE 请求传递 thinking
  开关,仅千问/DashScope 时启用
  - 完善权限系统,支持细粒度的权限控制和用户权限申请
  - delegate_research 工具重命名为 subagent,SubAgentTool/SubAgentRunner 重构
  - 子代理消息(system/user/assistant/tool)持久化到 agent_messages 表,带 agent_name 标识
  - 子代理活动日志(工具调用列表+思考摘要)注入返回结果,Hooks 获得正确 session_id 和 subagent_name
  - LLM 工具调用 ID 回退生成 UUID(llm.rs),ToolCall/ToolResult SSE 事件增加 id/tool_call_id 双字段
  - ToolContext 扩展 session_id/sse_tx/enable_thinking 字段,executor 统一注入而非构造函数传参
  - agent_messages 新增 metadata+raw_json 列,agent_sessions 暴露 summary 字段
  - 删除文件级 transcript 快照(compact.rs),改为依赖 DB 持久化
  - ResearchAgentPanel 重写:TimelineItem 类型替代 StreamStep,支持会话历史回放
  - 新增 AgentMetricsPanel/AskUserQuestionCard/AuditLogViewer 三个前端组件,types.ts 完整类型定义
  - docs/architecture/ 分层重组:概览/核心模块/核心工作流 + agent/ 子目录 11 篇专题文档
  - docs/api.md 补充 RAG/Target/Agent 接口,docs/development.md 新建开发指南
  - .env.example 完全重写,补充 FALLBACK_MODEL 等变量说明
2026-06-18 01:21:02 +08:00

15 KiB
Raw Blame History

系统提示词架构 (System Prompt Architecture)

AstroResearch 的 Agent 系统提示词采用模块化 Section 组装 + 动态注入 + 多层生命周期架构,直接参考 Claude Code 的 System Prompt 设计。

整体分层

graph TB
    subgraph L5["Layer 5: 运行时注入"]
        Nudge["nudge / 任务恢复 / 后台通知"]
    end
    subgraph L4["Layer 4: 提示词压缩"]
        Compress["snip → micro → auto → identity"]
    end
    subgraph L3["Layer 3: Skill 动态加载"]
        Skill["Layer1 提醒 → Layer2 全文注入"]
    end
    subgraph L2["Layer 2: 子代理隔离提示词"]
        SubSP["独立的 system_prompt"]
    end
    subgraph L1["Layer 1: 主代理 SystemPrompt 组装"]
        MainSP["5 个 section 模块化组装"]
    end
    L5 --> L4 --> L3 --> L2 --> L1

核心组装器 (src/agent/runtime/system_prompt.rs)

2.1 数据结构

pub struct SystemPrompt {
    sections: Vec<(&'static str, String)>,
}

简单的有序 section 列表,通过 assemble() 方法用双换行符 "\n\n" 拼接所有 section 内容。section 按添加顺序排列。

2.2 静态常量

两个 &'static str 常量在所有运行时实例间共享内存:

IDENTITY_SECTION身份声明1 行):

你是一位专业的天体物理学研究助手,具备丰富的天文学知识。

PRINCIPLES_SECTION核心行为准则9 条):

核心原则:
1. 主动使用工具搜索最新文献,不要仅凭训练数据回答。
2. 优先使用本地资源get_paper_content / rag_search必要时再检索新文献。
3. 收集到足够信息后立即给出最终答案,避免无意义的重复工具调用。
4. 回答时引用具体文献来源,使用 ADS bibcode 标注。
5. 对于数学公式,使用标准 LaTeX 格式。
6. 用中文回答,保持科学术语的准确性(可附带英文原文)。
7. 对于复杂任务(如文献综述),调用 load_skill 获取方法论指引,再用 todo_write 制定计划。
8. 如果某个工具调用失败,不要用相同参数重试,尝试换一种方式或工具。
9. 任务状态会在每轮开始时从数据库恢复,请基于最新状态继续工作。

2.3 组装顺序

每轮调用 AgentRuntime::system_prompt() 方法(src/agent/runtime/mod.rs:1154-1203),按以下顺序组装 5 个 section

Section 1: identity      静态 — 最大化 Anthropic prompt cache 命中率
Section 2: tools         动态 — 从 ToolRegistry 生成工具名称+摘要列表
Section 3: skills        动态 — 从 SkillRegistry.build_reminder() 生成(<system-reminder> XML
Section 4: memory        动态 — 从 MemoryManager.build_system_reminder(5) 生成(<project-memory-context> XML
Section 5: principles    静态 — 核心原则(放在最后 — 若需调整仅影响最后一个 cache segment

缓存策略:静态 section 固定且不变化,放在 prompt 头部以最大化 Anthropic prompt cache 命中率。动态 sectiontools、skills、memory因内容较少对 cache 影响可控。principles 虽然静态但放在最后,当需要调优时仅破坏最后一个 cache segment。


动态 Section 详解

3.1 工具列表 (tools section)

let mut tools_desc = String::from("你可以使用以下工具:\n");
for def in self.tool_registry.definitions() {
    let short_desc = def.function.description
        .split('。').next()
        .unwrap_or(&def.function.description)
        .chars().take(80)
        .collect();
    tools_desc.push_str(&format!("- {}: {}\n", def.function.name, short_desc));
}
  • 19 个默认工具:search_papers, download_paper, parse_paper, get_paper_content, rag_search, query_target, save_note, read_file, grep_files, glob_files, run_bash, file_write, file_edit, todo_write, compress_context, load_skill, subagent, save_memory, bg_task_run
  • 描述仅取第一句 + 前 80 字符作为功能摘要
  • 完整的参数 JSON Schema 通过 API 的 tools 参数单独传递,不在 system prompt 中重复

3.2 技能列表 (skills section) — 两层加载

参考 Claude Code 的两层技能设计,定义在 src/agent/skills.rs

Layer 1 (system-reminder)SkillRegistry.build_reminder() 在 system prompt 中注入 <system-reminder> XML 块。列出所有 user_invocable=truedisable_model_invocation=false 的技能名称 + 描述。每个 skill 约消耗 ~20 tokens。

<system-reminder>
The following skills are available for use with the Skill tool:
- methodology: 天体物理研究方法论指南 - When user asks about research methodology
- plotting: 数据可视化与科学绘图 - When user wants to create plots
- presentation: 学术幻灯片制作 - When user needs to prepare a presentation
When a skill matches the user's request, invoke load_skill BEFORE generating any other response about the task.
If you see a <command-name> tag in the current conversation turn, the skill has ALREADY been loaded - follow the instructions directly instead of calling load_skill again.
</system-reminder>

Layer 2 (load_skill 工具)LLM 按需调用 load_skill(skill_name) 工具,从 skills/{name}/SKILL.md 加载完整内容YAML frontmatter + Markdown body注入到消息上下文。完整 skill 约 ~2000 tokens。

SKILL.md 格式:

---
name: methodology
description: 天体物理研究方法论指南
context: inline        # inline | fork
when_to_use: When user asks about research methodology
allowed-tools:
  - search_papers
  - rag_search
model: inherit
user-invocable: true
---

# Skill 正文
详细内容...

热重载SkillRegistry 通过 notify crate 监听 skills 目录的文件变更300ms debounce 后自动刷新。Skill 按使用频率排序指数衰减评分7 天半衰期)。

条件激活Skill 可通过 paths frontmatter 声明 glob 模式。Agent 访问匹配文件时自动将 disable_model_invocation 设为 false激活条件 skill。

3.3 项目记忆 (memory section)

MemoryManager.build_system_reminder(5){library_dir}/memory/ 目录加载最近 5 条记忆,生成 <project-memory-context> XML 块:

<project-memory-context>
[PROJECT MEMORY]
[偏好] memory-slug: 一句话描述
  内容预览前三行
  [时效提示: 此记忆已超过N天可能已过时]
[反馈] another-memory: 描述 [已更新→new-slug]
  内容预览...
使用 save_memory 工具保存重要信息。记忆内容可能过时,请在使用前验证。
</project-memory-context>

关键特性:

  • 按 mtime 排序(最新在前),支持语义选择 + 指数衰减排序
  • 按类型标注:[偏好] / [反馈] / [项目] / [参考]
  • 过期记忆标记为 [已更新][已更新→new-slug](归档为 {slug}_v1.md
  • 超过 1 天的记忆注入时效警告
  • 索引文件 MEMORY.md 限制 200 行 / 25KB

上下文初始化与运行时注入 (src/agent/runtime/context.rs)

4.1 上下文构建流程

build_initial_context() 在每轮开始时构建完整的消息列表:

1. 从数据库加载历史消息agent_messages 表)
2. 如果历史第一条不是 system 角色 → 在位置 0 插入系统提示词
3. 追加当前用户问题
4. [可选] 追加任务状态恢复提醒(从 agent_tasks 表读取)

4.2 任务状态恢复

agent_tasks 表恢复未完成的任务,格式化注入 user 消息:

[当前任务状态]
以下是上次会话中持久化的任务计划,请基于最新状态继续工作:

⏳ [task-1] 搜索相关文献...
🔄 [task-2] 分析论文数据... (依赖: task-1)
✅ [task-3] 格式化引用... (指派: lead)

使用 todo_write 工具更新任务进度。

4.3 运行时 Nudge 注入

在 ReAct 循环中system prompt 组装后不再修改。运行时干预通过注入 user 消息实现(开闭原则):

触发条件 Nudge 内容
TodoWrite 连续 3 步未更新 "提醒:你已经连续多步未更新任务计划。建议调用 todo_write 工具…"
Token 预算 diminishing returns "检测到你的后续步骤未产生新信息…请基于已收集的全部信息直接给出最终答案"
达到最大步数 (max_steps) "你已经执行了 N 步(最大 M 步)。请根据已有信息直接给出最终答案"
后台任务完成 "[后台任务完成] tool_name: bibcode: summary"

子代理的独立系统提示词 (src/agent/tools/subagent.rs)

子代理拥有独立的消息上下文,通过 SubAgentRunner::run() 接收一个硬编码的简化版系统提示词

你是一位专业的天体物理学研究助手,在一个独立的子任务上下文中工作。
你可以使用文献搜索、下载、RAG检索等工具。
请高效完成任务,然后直接给出最终答案。不要进行不必要的重复操作。
用中文回答,引用具体文献来源。

特点:

  • 不继承父代理的 tools/skills/memory sections
  • 共享父代理的 ToolRegistry
  • 通过 PermissionChecker 可在特定场景下限制工具访问
  • 独立的 ReAct 循环(步数上限通过参数传入,默认 5最大 10
  • 完整的 Hook 管道PreToolUse/PostToolUse/SubagentStart/SubagentStop
  • 包含活跃度日志activity log返回给父代理时附带工具调用统计

5.2 团队成员的独立提示词 (src/agent/team/teammate.rs)

队友的 system_prompttask_promptteam/manager.rslead 的委托逻辑)在运行时构造并传入 run_teammate_loop()

  • prompt 内容完全由 lead 的决定
  • 队友不包含 subagent 工具(防止无限委托链)
  • 更轻量的 ReAct 循环(无 SSE、无 DB 持久化、无 hooks
  • 步数上限更严格min(max_steps, 5)
  • 通过文件收件箱与 lead 通信(每 5 秒 poll最长 60 秒)

上下文压缩中的独立提示词 (src/agent/compact.rs)

6.1 四层压缩策略

方法 API 调用 行为
Layer 0 snip_compact 消息数超过 50 时截断中间段,保留头 3 + 尾 47
Layer 1 micro_compact 将较早的工具结果替换为 [Previous: used {tool_name}] 占位符
Layer 2 auto_compact 1 次 LLM 摘要对话历史(见下),注入 [历史对话摘要]
Layer 3 aggressive_micro 保留最近 2 条工具结果,其余替换为占位符

6.2 LLM 摘要 Prompt

Layer 2 中调用 LLM 生成摘要时,使用独立的系统提示词:

系统: "你是一个对话摘要助手。请提取对话的关键信息和结论。"
用户: "请用简洁的中文总结以下对话历史的要点不超过500字

      [用户] ...
      [助手] ...
      [工具] ..."

6.3 身份再注入

如果压缩后消息过少≤4 条),注入身份确认块防止模型丢失上下文认知:

[身份确认] 你是一位专业的天体物理学研究助手。以上是历史对话的压缩摘要。
你正在进行的研究任务是回答用户的问题。请基于摘要中的关键信息继续工作,
需要更多信息时主动使用工具搜索。

6.4 安全切割

find_safe_cut_point() 确保压缩时不会破坏 assistant(tool_calls) / tool_result 配对关系,向前追溯找到完整工具交互的边界。

6.5 熔断器

CompactionCircuitBreaker 防止连续压缩失败时的无限循环。连续 3 次压缩后消息数未减少 → 打开熔断器,后续跳过自动压缩。


Hook 系统与提示词的交互 (src/agent/hooks.rs)

Hook 系统定义 9 个生命周期事件,其中与提示词相关的交互:

Hook 与提示词的关系
OnSessionStart 在提示词组装前触发,可影响任务状态恢复逻辑
PreToolUse::MutateInput 可向工具执行注入 additional_context(作为 user 消息追加)
PreToolUse::Block 阻止特定工具的执行(如取消检查)
PostToolUse::MutateOutput 可修改工具输出内容(影响后续 LLM 看到的 context
OnStepComplete 每步结束记录 token 估算、消息数等指标
PreCompact 压缩前记录消息数和 token 估算
PostCompact 压缩后记录最终消息数和压缩方法
OnSubagentStart/Stop 子代理启动/停止时传递 prompt 和结果摘要
OnSessionStop 会话终止时清理取消状态并记录终止原因

完整数据流

flowchart TD
    RT["AgentRuntime 创建<br/>system_prompt() 调用"]
    
    RT --> S1["Section 1: identity<br/>(静态常量)"]
    RT --> S2["Section 2: tools<br/>(ToolRegistry definitions)"]
    RT --> S3["Section 3: skills<br/>(SkillRegistry.build_reminder)"]
    
    S1 --> S4
    S2 --> S4
    S3 --> S4["Section 4: memory (可选)<br/>(MemoryManager.build_reminder, 5 entries)"]
    
    S4 --> S5["Section 5: principles<br/>(静态常量)"]
    S5 --> ASM["assemble()<br/>join('\n\n')"]
    
    ASM --> Main["主 Agent 上下文<br/>build_initial_context()<br/>+ nudge 注入 + 任务恢复 + 后台通知"]
    ASM --> Sub["子 Agent 上下文<br/>SubAgentRunner.run()<br/>(独立 system_prompt)"]
    
    Main --> React["ReAct 循环"]
    Main --> NudgeInj["Nudge 消息注入 (user)"]
    Main --> Compact["压缩层<br/>generate_summary()<br/>+ identity re-injection"]

相关文件

文件 职责
src/agent/runtime/system_prompt.rs SystemPrompt 组装器 + 静态常量
src/agent/runtime/mod.rs:1154-1203 system_prompt() 方法 — 5 section 拼装
src/agent/runtime/context.rs build_initial_context() — 上下文初始化 + 任务恢复
src/agent/skills.rs SkillRegistry — 两层技能加载 + 热重载
src/agent/memory/mod.rs MemoryManager — 记忆加载 + system reminder 构建
src/agent/compact.rs 四层压缩 + LLM 摘要 prompt + 身份再注入
src/agent/tools/subagent.rs 子代理系统提示词(硬编码)
src/agent/subagent.rs SubAgentRunner — 子代理 ReAct 循环
src/agent/team/teammate.rs 队友 ReAct 循环(外部传入 system_prompt
src/agent/hooks.rs 9 个生命周期 hook + 提示词交互

设计要点

优势

  1. 模块化 section 组装:各 section 独立管理,便于调试和迭代
  2. 静态 section 前置:最大化 Anthropic prompt cache 命中率,降低延迟和成本
  3. 两层 skill 加载:避免一次性注入所有 skill 的 token 浪费
  4. 压缩时身份再注入:防止激进压缩后模型丢失角色认知
  5. 安全切割点find_safe_cut_point 确保压缩不破坏 tool_call/tool_result 配对
  6. 运行时 nudge 而非 system prompt 编辑遵循开闭原则system prompt 保持稳定

潜在改进方向

  1. 子代理系统提示词继承:当前子代理的 system prompt 是硬编码的,可考虑让子代理也接收 section 组装器,选择性继承 skills/memory
  2. 压缩 prompt 外部化:摘要生成和身份确认的 prompt 可配置化,便于独立调优
  3. 记忆注入锁竞争memory_manager.try_lock() 在高并发下可能静默失败,考虑使用 RwLock::read()
  4. 工具描述摘要策略80 字符截断可能丢失关键语义,可考虑 LLM 预生成工具描述摘要