AstroResearch/docs/architecture/agent/system-prompt.md
Asfmq cec4b8cf7b feat: Docker 容器化、Cookie 鉴权、Coordinator 编排、FTS5 搜索与 P1-P3 全面收尾
Docker 容器化部署
  - 提供 Mode A (Alpine musl, ~23MB) 和 Mode B (Distroless glibc, ~87MB)
    两种镜像,Docker Compose 一键启动
  - build.rs 支持 SKIP_DASHBOARD_BUILD 跳过前端构建
  - 国内镜像加速 (npm/apt/apk) 通过 USE_MIRRORS build-arg 控制

  安全:Cookie-Based 鉴权系统
  - HttpOnly/SameSite=Strict Cookie 会话管理(24h 过期自动清理)
  - 登录/登出/验证接口 + 中间件注入
  - 前端登录页面 + 退出按钮
  - 三层 CORS:localhost 鉴权 / 全放通 bookmarklet / 受保护路由
  - 书签脚本 fetch 添加 credentials:'include'

  Coordinator 模式 (P2)
  - 4 个 meta-tool (delegate_task/check_task/task_stop/synthesize)
  - WorkerPool + Semaphore 并发控制 + 超时保护
  - 前端协调者模式开关

  Hook 系统:UserPromptSubmit 事件 (P2)
  - 第 13 个生命周期事件,fire-and-forget 审计

  FTS5 全文搜索 (P3)
  - agent_sessions_fts + agent_messages_fts 虚拟表
  - search_history Agent 工具 + /api/search/history HTTP 接口
  - 前端防抖搜索框 + 仅当前会话筛选

  工具加载优化 (P3)
  - defer_loading 延迟加载 (7 个重型工具)
  - is_readonly 只读标记 (9 个查询工具)
  - classifier_summary 工具目录供 LLM 按需判断

  模型回退策略 (P3)
  - LLM_FALLBACK_MODEL 优先回退 + LLM_FALLBACK_CHAIN 链式轮换
  - LlmClient model 改为 Arc<RwLock> 支持运行时切换
  - 连续 3 次过载后自动切换

  压缩记忆桥接 (P3)
  - 压缩丢弃消息 → 子代理提取持久记忆 (extract_memories_from_compaction)

  git2 依赖修复
  - 切换到 vendored-libgit2,消除 OpenSSL 系统依赖
2026-06-23 20:22:06 +08:00

358 lines
14 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 系统提示词架构 (System Prompt Architecture)
AstroResearch Agent 系统提示词采用**模块化 Section 组装 + 简单首次缓存**架构,参考 Claude Code 的 System Prompt 设计并针对实际场景裁剪。
## 整体分层
```mermaid
graph TB
subgraph L5["Layer 5: 运行时注入"]
Nudge["nudge / 任务恢复 / 后台通知"]
end
subgraph L4["Layer 4: 提示词压缩 + CollapseLog"]
Compress["snip → micro → auto → aggressive_micro"]
Collapse["CollapseLog commit / overflow"]
end
subgraph L3["Layer 3: Skill 动态加载"]
Skill["Layer1 提醒 → Layer2 全文注入"]
end
subgraph L2["Layer 2: 子代理模块化提示词"]
SubSP["6 section 组装器"]
end
subgraph L1["Layer 1: 主代理 SystemPrompt 组装"]
MainSP["9 个 section 模块化组装<br/>静态(5) → 动态(4)"]
Cache["SystemPromptCache<br/>首次计算,永久复用"]
end
L5 --> L4 --> L3 --> L2 --> L1
L1 --> Cache
```
---
## 核心组装器 (`src/agent/runtime/system_prompt.rs`)
### 数据结构
```rust
pub struct SystemPrompt {
sections: Vec<(&'static str, String)>,
}
```
有序 section 列表。`assemble()` 用 `"\n\n"` 拼接所有 section。顺序即最终 prompt 中出现的顺序——静态内容在前,动态内容在后。
### Section 缓存 (`SystemPromptCache`)
简化设计:**首次计算,永久缓存**。因为 session 生命周期内 CWD、platform、OS、model、工具注册表均不变不需要 TTL 过期机制。仅在 `/clear``/compact` 事件时调用 `invalidate_all()` 全局失效。
```rust
pub struct SystemPromptCache {
entries: HashMap<&'static str, String>,
}
impl SystemPromptCache {
// 首次计算,后续命中缓存
pub fn get_or_compute(&mut self, name: &str, compute: impl FnOnce() -> String) -> String;
// compute 返回 OptionSome 缓存并返回None 不缓存
pub fn get_or_compute_optional(&mut self, name: &str, compute: impl FnOnce() -> Option<String>) -> Option<String>;
// 显式失效
pub fn invalidate(&mut self, name: &str);
pub fn invalidate_all(&mut self);
}
```
缓存策略:静态 section + environment + tools 全部通过 `get_or_compute` 缓存。skills 和 memory 不缓存——前者通过文件监听热更新,后者受 `save_memory` 工具实时影响。
### 静态 Section 常量6 个)
| 常量 | 内容 | 行数 |
|------|------|------|
| `IDENTITY_SECTION` | 身份声明 | 1 |
| `PRINCIPLES_SECTION` | 核心行为准则9 条) | 9 |
| `SYSTEM_CONTEXT_SECTION` | system-reminder 标签说明 + 自动压缩 | 3 |
| `TOOL_USAGE_SECTION` | 专用工具优先、并行调用、todo_write | 5 |
| `SAFETY_SECTION` | 可逆性、影响范围、确认机制 | 5 |
### 组装顺序
`AgentRuntime::system_prompt()` 组装 9 个 section
```
[1] identity ← 静态(首次计算后永久缓存)
[2] principles ← 静态
[3] system_context ← 静态
[4] tool_usage ← 静态
[5] safety ← 静态
[6] environment ← 动态首次计算后缓存session 内不变)
[7] tools ← 动态首次计算后缓存ToolRegistry session 内不变)
[8] skills ← 动态(不缓存,文件监听热更新)
[9] memory ← 动态不缓存save_memory 实时更新)
```
**为什么没有 TTL**CWD、platform、OS、model、tools 在 session 生命周期内全部不变。首次计算即永久正确TTL 是多余的复杂度。
**为什么没有 cache_control 边界标记**`cache_control: {"type": "ephemeral"}` 是 Anthropic API 专有特性。我们的模型DeepSeek/Qwen 等 OpenAI 兼容 API不支持。静态内容前置的顺序本身已足够让服务端按内容哈希自然缓存。
---
## 动态 Section 详解
### environment section
```rust
fn build_environment_section(&self) -> String {
// 包含工作目录、Git 仓库状态、平台、OS 版本、日期、模型名称
// 以及 Agent 配置摘要(最大步数、工具超时)
}
```
示例输出:
```
# 环境信息
- 工作目录: /home/user/project
- Git 仓库: 是
- 平台: linux
- OS 版本: Linux 7.0.0-22-generic
- 日期: 2026-06-22
- 当前模型: deepseek-v4-pro
- 最大推理步数: 8
- 工具超时: 120 秒
```
### tools section
```rust
// 从 ToolRegistry.definitions() 生成工具名称 + 80 字符摘要
// ToolRegistry 内部有 schema_cache工具注册表不变时复用上次计算结果
for def in self.tool_registry.definitions() {
let short_desc = def.function.description
.split('。').next() // 取第一句
.chars().take(80) // 截断 80 字符
.collect();
tools_desc.push_str(&format!("- {}: {}\n", def.function.name, short_desc));
}
```
完整 JSON Schema 通过 API `tools` 参数单独传递,不在 system prompt 中重复。
### skills section — 两层加载
**Layer 1 (system-reminder)**`SkillRegistry.build_reminder()` 列出所有 `user_invocable=true` 的技能名称 + 描述 + when_to_use每个约 20 tokens。
```xml
<system-reminder>
The following skills are available for use with the Skill tool:
- methodology: 天体物理研究方法论指南 - When user asks about research methodology
When a skill matches the user's request, invoke load_skill BEFORE generating any other response...
</system-reminder>
```
**Layer 2 (load_skill 工具)**LLM 按需调用,从 `skills/{name}/SKILL.md` 加载完整内容(~2000 tokens支持 `${SKILL_DIR}` / `${SESSION_ID}` 变量替换和 fork 执行模式。
详见 [skills.md](skills.md)。
### memory section
`{library_dir}/memory/` 加载,**按 mtime 降序排列**(最新在前),取前 5 条,注入 `<project-memory-context>` XML 块。支持时效警告、过期标记、语义选择。详见 [memory.md](memory.md)。
---
## 上下文初始化 (`src/agent/runtime/context.rs`)
`build_initial_context()` 流程:
```
1. 加载历史消息agent_messages 表)
2. 如果第一条不是 system 角色 → 插入系统提示词
3. 追加当前用户问题
4. [可选] 追加任务状态恢复提醒agent_tasks 表)
```
运行时干预通过**注入 user 消息**实现(不修改 system prompt
| 触发条件 | 注入内容 |
|:---|:---|
| TodoWrite 3 步未更新 | "提醒:建议调用 todo_write 工具复盘进度" |
| Token 预算 diminishing returns | "检测到重复操作模式,请直接给出最终答案" |
| 达到最大步数 | "已执行 N 步(最大 M 步),请直接给出最终答案" |
| 后台任务完成 | "[后台任务完成] ✅ tool_name: summary" |
---
## 子代理模块化系统提示词 (`src/agent/tools/subagent.rs`)
子代理使用完整模块化系统提示词(不再硬编码 96 字符):
```
[1] identity ← 与父代理相同
[2] subagent_context ← "在独立的子任务上下文中工作,请专注于完成这项任务"
[3] principles ← 与父代理相同
[4] system_context ← 与父代理相同
[5] tool_usage ← 与父代理相同
[6] safety ← 与父代理相同
[7] tools ← 运行时从 ToolRegistry 生成
```
预构建 `ToolRegistry`,在构造系统提示词前获取 `definitions()`。`SubAgentRunner` 新增 `new_with_registry_and_hooks()` 构造函数。详见 [subagent.md](subagent.md)。
---
## 上下文压缩 + CollapseLog (`src/agent/compact.rs`)
### 四层压缩策略
| 层 | 方法 | API | 行为 |
|:---|:---|:---|:---|
| 0 | `snip_compact` | 无 | 消息 >50 时截断中间段,保留头 3 + 尾 |
| 1 | `micro_compact` | 无 | 较早工具结果替换为 `[Previous: used {name}]` 占位符 |
| 2 | `auto_compact` | 1 次 | LLM 摘要对话历史 |
| 3 | `aggressive_micro` | 无 | 保留最近 2 条工具结果 |
### CollapseLog
`compress_context_with_hooks_and_log()` 在每次压缩后记录结构化 commit
```rust
log.commit(CollapseMethod::LlmSummary, (after_count, before_count), summary);
```
超 5 条 commits 时触发**溢出合并**,将最早的 commits 合并为摘要注入消息列表。详见 `collapse.rs`
---
## Hook 系统与提示词交互
13 个生命周期事件:
| Hook | 与提示词的关系 |
|:---|:---|
| `OnSessionStart` | 提示词组装前触发 |
| `UserPromptSubmit` | 用户提交提示词后、context building 前触发 (P2) |
| `PreToolUse::MutateInput` | 注入 `additional_context`(追加为 user 消息) |
| `PostToolUse::MutateOutput` | 修改工具输出(影响 LLM 看到的 context |
| `PostToolUseFailure` | 工具执行失败后修改输出 (P1) |
| `PreCompact / PostCompact` | 压缩前后记录指标 + CollapseLog commit |
| `OnSubagentStart/Stop` | 传递子代理 prompt 和结果摘要 |
| `PermissionRequest / PermissionDenied` | 权限决策覆盖和审计 (P1) |
详见 [hooks.md](hooks.md)。
---
## Claude Code 工具按需发现机制(参考分析)
> 当前项目工具数量较少(~25 个),尚未实现此机制。以下为 Claude Code 的设计分析,作为未来工具增长时的参考。
### 要解决的问题
当接入大量 MCP 工具30+ 服务器100+ 工具)时,所有工具的完整 JSON Schema 在每轮 API 调用中占据大量上下文——绝大多数工具从未被调用,却每轮都在消耗 token。Claude Code 的解决方案:**延迟加载 + 按需发现**。
### 两层架构
**Layer 1: `defer_loading` 标记**
工具分为两类:
| 类型 | 行为 | 示例 |
|------|------|------|
| 常驻工具 | 始终在 `tools` 数组中,立即可用 | Read、Write、Bash、Glob、Grep、Task、TodoWrite |
| 延迟工具 | 标记 `defer_loading: true`,仅在模型主动发现后才加入 `tools` 数组 | MCP 工具、EnterPlanMode、NotebookEdit、LSP 工具 |
判断逻辑(`isDeferredTool()`
```
1. alwaysLoad == true → 永不延迟
2. isMcp == true → 始终延迟(除非 MCP server 设 _meta['anthropic/alwaysLoad']
3. shouldDefer == true → 延迟
```
**Layer 2: ToolSearch 工具 + tool_reference 块**
模型通过 `ToolSearch` 工具按需发现延迟工具:
```
模型: ToolSearch(query: "github create PR")
系统: 返回 tool_reference 块 → [mcp__github__createPullRequest, mcp__github__listPRs]
下次 API 调用: 这两个工具的完整 schema 加入 tools 数组
```
`tool_reference` 是一个特殊的 content block 类型API 服务端收到后会展开为完整工具定义,模型可以在后续 turn 直接调用。
### 消息流转
```
Turn N:
tools 数组 = 常驻工具 + ToolSearch + 之前发现过的延迟工具
<available-deferred-tools> 块列出所有可发现的延迟工具名称
→ 模型调用 ToolSearch(query: "slack")
→ tool_result 包含 tool_reference 块: [mcp__slack__sendMessage]
Turn N+1:
tools 数组 = 常驻工具 + ToolSearch + [mcp__slack__sendMessage, ...]
→ 模型可以直接调用 mcp__slack__sendMessage
```
### 发现状态持久化
`extractDiscoveredToolNames()` 从消息历史中扫描所有 `tool_reference` 块,提取已发现的工具名。压缩时通过 compact boundary marker 的 `preCompactDiscoveredTools` 字段保存已发现集合,防止压缩丢失发现状态。
### 工具发现状态变更通知
两个机制告诉模型有哪些可发现工具:
**Legacy**`<available-deferred-tools>` 块作为 user 消息前置注入(每次工具池变更都会 bust prompt cache
**Modern**`deferred_tools_delta` 附件——diff 上次通知的工具池仅发送增量变更added/removed避免 bust cache。
### ToolSearch 搜索方式
- **精确选择** `select:ToolA,ToolB` — 按名称直接取工具
- **关键词搜索** `github create PR` — 搜索工具名 + searchHint + description加权评分
- **必选词** `+slack send``+` 前缀表示必须匹配
### 对项目的适用性
| 当前状态 | 是否需要 |
|---------|---------|
| ~25 个工具schema 总共 ~8K tokens | **暂不需要** |
| 无 MCP 工具接入 | 不需要 |
| 工具数量稳定 | 不需要 |
**触发条件**:当工具数量超过 ~50 或接入 MCP 服务器时,可以按以下步骤接入:
1. 为 MCP 工具设置 `shouldDefer: true` / `isMcp: true`
2. 注册 `ToolSearch` 工具
3. 在 ToolRegistry 中维护 `deferred_tool_names` 集合
4. API 调用时过滤 tools 数组 + 注入 `<available-deferred-tools>`
5. 压缩时保存 `preCompactDiscoveredTools` 快照
---
## 相关文件
| 文件 | 职责 |
|:---|:---|
| `src/agent/runtime/system_prompt.rs` | SystemPrompt 组装器 + 6 个静态常量 + SystemPromptCache |
| `src/agent/runtime/mod.rs` | `system_prompt()` + `build_environment_section()` |
| `src/agent/runtime/context.rs` | `build_initial_context()` — 上下文初始化 + 任务恢复 |
| `src/agent/skills.rs` | SkillRegistry — 两层技能加载 + 热重载 |
| `src/agent/memory/mod.rs` | MemoryManager — 按 recency 排序的记忆注入 |
| `src/agent/tools/mod.rs` | ToolRegistry — schema_cache + definition_filter |
| `src/agent/tools/subagent.rs` | 子代理模块化系统提示词构建 |
| `src/agent/subagent.rs` | SubAgentRunner — new_with_registry_and_hooks |
| `src/agent/compact.rs` | 四层压缩 + CollapseLog 集成 |
| `src/agent/compact/collapse.rs` | CollapseLog — 压缩历史记录 + 溢出合并 |
| `src/agent/hooks/` | 生命周期 hook + 提示词交互 |
| `docs/architecture/agent/system-prompt-optimization-plan.md` | 优化计划文档(背景、方案、对比分析) |
---
## 设计要点
1. **静态前置,动态后置**:内容不变的 section 先出现,服务端自然按哈希缓存
2. **首次计算,永久缓存**session 内一切不变,不需要 TTL仅在 /clear 时全部失效
3. **模块化 section**:各 section 独立管理便于调试、增删、A/B 测试
4. **两层 skill 加载**20 tokens 列表 vs 2000 tokens 全文,按需加载
5. **子代理完整提示词**:共享主代理的静态常量 + 独立 tools 列表
6. **CollapseLog 追踪**:每次压缩记录结构化 commit溢出自动合并
7. **运行时 nudge**:干预通过 user 消息注入,不修改 system prompt开闭原则