feat: v2.3.0 流式输出 + 日志系统 + 会议室架构全面升级
- 流式输出: SSE 逐 token 接收, \\n\n\ 段落缓冲后 mdprint 彩色渲染 - 日志系统: charmbracelet/log v2 双写(stderr + log.yml), yunshu log 命令 - 会议室架构: dialog(main) + weather/profile/note(sub) 多 Agent 编排 - 泛型工具注册: NewTool[T] 反射推导 JSON Schema, 类型安全 - 安全加固: safeMemoryPath 三段校验(EvalSymlinks+Rel), maxToolCalls=2 - 性能优化: sync.Once 延迟加载, note 一步完成, obs/summary 合并 - Prompt 适配: 流式输出原则(先调工具不说话), 单 Agent 查询跳过 obs+summary - 文档: AGENTS.md + architecture.md + changelog.md 全部同步至 v2.3.0
This commit is contained in:
250
docs/会议室架构计划书.md
250
docs/会议室架构计划书.md
@@ -1,8 +1,18 @@
|
||||
# 云枢·Agent 会议室架构计划书
|
||||
|
||||
> **生成日期**:2026-05-11
|
||||
> **最后更新**:2026-05-16
|
||||
> **目的**:从单 Agent 架构升级为"会议室模式"(1 主持 + N 领域专家 + 共享黑板)
|
||||
> **最终目标**:在云枢上验证通过后,移植到 HxClaw(河虾 Claw)
|
||||
>
|
||||
> **实现状态**:
|
||||
> - ✅ 核心引擎(registry + runtime + cache + session)
|
||||
> - ✅ 7 个工具(task, memory.read/write, http-get, skill, read-file, geocode)
|
||||
> - ✅ 泛型+反射工具注册(NewTool[T])
|
||||
> - ✅ 多步骤编排(主 Agent 连续调多个子 Agent)
|
||||
> - ✅ weather-sub.md 天气子 Agent
|
||||
> - ❌ memory-sub.md 记忆管理员子 Agent
|
||||
> - ❌ earthquake / train / hotel 等扩展子 Agent
|
||||
|
||||
---
|
||||
|
||||
@@ -11,21 +21,27 @@
|
||||
```
|
||||
用户
|
||||
│
|
||||
┌──────▼──────────────────────────────────────┐
|
||||
│ 主持者(dialog-agent)type: main │
|
||||
│ 人格 + 调度规则 + task + memory 工具 │
|
||||
│ 唯一入口,用户只和它对话 │
|
||||
└──────┬───────────────────────────────────────┘
|
||||
│ task("weather", {city: "北京"})
|
||||
│ task("earthquake", {region: "通州"})
|
||||
│ task("memory", {action: "read", ...})
|
||||
▼
|
||||
┌───────────────────────────────────────────┐
|
||||
│ 发言人(领域子 Agent)type: sub │
|
||||
│ weather / earthquake / memory / narrator │
|
||||
│ 被调才说话,返回文本 + 可选缓存数据 │
|
||||
│ 各自的 cache / skills / tools 互相隔离 │
|
||||
└───────────────────────────────────────────┘
|
||||
┌──────▼──────────────────────────────────────────┐
|
||||
│ 主持者(dialog-agent)type: main │
|
||||
│ 人格 + 调度规则 + task + memory 工具 │
|
||||
│ 唯一入口,用户只和它对话 │
|
||||
│ ✨ 可以连续多次调不同子 Agent,综合数据后回答 │
|
||||
└──────┬───────────────────────────────────────────┘
|
||||
│ task("weather", {city: "北京"}) ← 第一步
|
||||
│ ← 北京明天 5°C 晴
|
||||
│ task("train", {city: "北京", date: "明天"}) ← 第二步(看到天气后决定)
|
||||
│ ← G102 08:00 ¥680
|
||||
│ task("hotel", {city: "北京", nights: 3}) ← 第三步
|
||||
│ ← 建国饭店 ¥500/晚
|
||||
│ → 综合: "明天北京5°C…G102早8点…建国饭店…" ← 最终回答
|
||||
▼
|
||||
┌───────────────────────────────────────────────┐
|
||||
│ 发言人(领域子 Agent)type: sub │
|
||||
│ weather / earthquake / memory / narrator │
|
||||
│ 被调才说话,返回文本 + 可选缓存数据 │
|
||||
│ 各自的 cache / skills / tools 互相隔离 │
|
||||
│ 不感知其他子 Agent 存在,结果由主 Agent 整合 │
|
||||
└───────────────────────────────────────────────┘
|
||||
│ 读写
|
||||
▼
|
||||
┌───────────────────────────────────────────┐
|
||||
@@ -45,7 +61,9 @@
|
||||
**职责**:
|
||||
- 用户的唯一入口
|
||||
- 有血有肉的个人助理,能闲聊
|
||||
- 识别用户意图,调度对应的子 Agent
|
||||
- 识别用户意图,**可以连续多次**调度不同的子 Agent
|
||||
- 每次 task() 返回后,决定继续调下一个还是综合回答(多步骤编排)
|
||||
- 把上一步子 Agent 的结果作为上下文,传递给下一次 task() 的参数
|
||||
- 读/写记忆(用户画像、上下文摘要)
|
||||
|
||||
**工具列表**:
|
||||
@@ -205,7 +223,26 @@ task(agent_name, arguments)
|
||||
- `raw` 存原始参数,方便调试和遍历
|
||||
- 每次读缓存时惰性清理过期条目
|
||||
|
||||
### 3.3 传给子 Agent 的参数
|
||||
### 3.3 子 Agent 返回协议
|
||||
|
||||
子 Agent 返回分两段,由 `task` 工具解析:
|
||||
|
||||
```
|
||||
---RESULT---
|
||||
{结构化 JSON 数据(进缓存,不进 dialog 上下文)}
|
||||
---TEXT---
|
||||
子 Agent 想要对用户说的陈述文本(进 dialog 上下文)
|
||||
```
|
||||
|
||||
- `---RESULT---`:原始 API 数据,task 写入缓存文件,**不传给 dialog**
|
||||
- `---TEXT---`:子 Agent 已经组织好的陈述文本,task 返回给 dialog 的 LLM
|
||||
|
||||
**为什么分成两段**:
|
||||
- RESULT 保持子 Agent 的领域数据干净,不进主上下文
|
||||
- TEXT 给 dialog 一个"素材",dialog 用自己的语气说出来,不会产生"复述感"
|
||||
- 如果子 Agent 这次没有更新数据(比如 cache 命中后直接回答),可以只带 `---TEXT---`
|
||||
|
||||
### 3.4 传给子 Agent 的参数
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -288,30 +325,28 @@ task(agent_name, arguments)
|
||||
```
|
||||
yunshu/
|
||||
├── main.go # CLI 入口
|
||||
├── types.go # 核心类型(AgentDef, ToolDef 等)
|
||||
├── types.go # 核心类型(AgentDef, Schema, ToolDef, Message…)
|
||||
├── loader.go # .md 解析(Frontmatter + Body)
|
||||
├── catalog.go # CatalogAgent 生成 + tools.yml 输出
|
||||
├── registry.go # Agent 注册中心(扫描 + 按 type 分类)
|
||||
├── llm.go # LLM API 封装
|
||||
├── tool.go # 工具注册表 + ExecuteTool
|
||||
├── runtime.go # RunAgent 主循环
|
||||
├── tool.go # 工具注册 + 7 个工具 handler
|
||||
├── toolschema.go # 泛型+反射 Schema 生成(NewTool[T], structToSchema)
|
||||
├── runtime.go # RunAgent + RunSubAgent + cache + session
|
||||
│
|
||||
├── agents/
|
||||
│ ├── dialog-agent.md # type: main — 主持者
|
||||
│ ├── weather-sub.md # type: sub — 天气
|
||||
│ ├── earthquake-sub.md # type: sub — 地震(预留)
|
||||
│ ├── memory-sub.md # type: sub — 记忆管理员
|
||||
│ └── narrator-sub.md # type: sub — 汇报员(成熟期)
|
||||
│ ├── weather-sub.md # type: sub — 天气 ✅
|
||||
│ ├── earthquake-sub.md # type: sub — 地震(预留)❌
|
||||
│ ├── memory-sub.md # type: sub — 记忆管理员 ❌
|
||||
│ └── narrator-sub.md # type: sub — 汇报员(成熟期)❌
|
||||
│
|
||||
├── skills/
|
||||
│ ├── msn-weather-api/SKILL.md
|
||||
│ └── geocoding/SKILL.md
|
||||
│
|
||||
├── docs/
|
||||
│ ├── taolun.md
|
||||
│ ├── 会议室架构计划书.md
|
||||
│ ├── changelog.md
|
||||
│ ├── architecture.md
|
||||
│ └── AGENTS.md
|
||||
│ └── 此目录
|
||||
│
|
||||
└── pkg/
|
||||
├── mdprint/
|
||||
@@ -323,12 +358,16 @@ yunshu/
|
||||
|
||||
```
|
||||
~/.config/yunshu/
|
||||
├── config.yaml # LLM 配置
|
||||
├── session.json # 对话历史(仅 user ↔ dialog)
|
||||
├── agents/
|
||||
│ ├── dialog-agent.md # 用户可覆盖对话 Agent
|
||||
│ └── weather-sub.md # 用户可覆盖天气子 Agent
|
||||
├── skills/ # 用户可扩展知识
|
||||
├── config/
|
||||
│ ├── config.yml # LLM 配置
|
||||
│ ├── user.md # 用户画像(## 画像 / ## AI观察到)
|
||||
│ └── soul.md # AI 灵魂(用户可编辑)
|
||||
├── session/
|
||||
│ ├── session.json # 对话历史(仅 user ↔ dialog)
|
||||
│ └── dialog.yml # 对话摘要(每轮覆写)
|
||||
├── notes.md # 备忘录列表
|
||||
├── notes/ # 独立笔记文件
|
||||
├── log.yml # API 异常记录
|
||||
├── cache/
|
||||
│ ├── weather.json
|
||||
│ ├── earthquake.json
|
||||
@@ -342,6 +381,8 @@ yunshu/
|
||||
|
||||
## 六、调用流程示例
|
||||
|
||||
### 6.1 单子 Agent 查询
|
||||
|
||||
```
|
||||
用户: "北京明天多少度?"
|
||||
|
||||
@@ -351,66 +392,121 @@ yunshu/
|
||||
3. 调 LLM(session + system + tools)
|
||||
4. LLM 返回 tool_call: task("weather", {city: "北京", forecast_type: "tomorrow"})
|
||||
|
||||
task 工具:
|
||||
task 工具(子 Agent 调用):
|
||||
1. 加载 weather-sub.md Frontmatter
|
||||
→ cache.keys: ["city", "forecast_type"], ttl: 7200
|
||||
2. 拼 key → "city=北京&forecast_type=tomorrow" → hash
|
||||
3. 查 weather.json → MISS(首次查明天)
|
||||
4. 调子 Agent LLM:
|
||||
→ cache.keys: ["city", "forecast_type"], ttl: 1800
|
||||
2. 拼 key → "city=北京&forecast_type=tomorrow" → sha256[:6]
|
||||
3. 查 cache/weather.json → MISS
|
||||
4. 调子 Agent LLM(RunSubAgent,隔离的循环)
|
||||
system = weather-sub.md
|
||||
user = {args: {city: "北京", forecast_type: "tomorrow"}, cache_data: null}
|
||||
5. 子 Agent:
|
||||
├── geocode("北京") → (39.9, 116.4)
|
||||
5. 子 Agent 工具链:
|
||||
├── skill("msn-weather-api") → 接口参数
|
||||
├── http-get(URL) → JSON
|
||||
└── 返回: "北京明天 18-31°C,晴"
|
||||
---CACHE---
|
||||
{temp_lo: 18, temp_hi: 31, condition: "晴"}
|
||||
6. task 提取 CACHE → 写 weather.json
|
||||
7. 返回 "北京明天 18-31°C,晴" 给 dialog
|
||||
├── geocode("北京") → (39.9, 116.4)
|
||||
├── http-get(URL) → JSON
|
||||
└── 返回:
|
||||
---RESULT---
|
||||
{temp: {lo:18, hi:31}, condition: "晴"}
|
||||
---TEXT---
|
||||
▪ 北京明天天气
|
||||
...
|
||||
6. task 提取 RESULT → 写 cache/weather.json
|
||||
7. 返回 TEXT 给 HOST
|
||||
|
||||
HOST(runtime.go):
|
||||
1. tool 返回 → LLM 继续
|
||||
2. LLM 生成最终回答:
|
||||
"北京明天 18到31度,大晴天,适合出去浪~"
|
||||
3. dialog: task("memory", {action: "update_context", agent: "weather", city: "北京"})
|
||||
4. 追加 session.json
|
||||
5. 输出给用户
|
||||
1. tool 结果 → 追加到对话 → LLM 再次推理
|
||||
2. LLM 根据 prompt 指令"子 Agent 输出就是答案"→ 直接输出 TEXT
|
||||
3. 追加 session.json
|
||||
4. 显示给用户
|
||||
```
|
||||
|
||||
### 6.2 多步骤编排(新增能力)
|
||||
|
||||
```
|
||||
用户: "去北京出差,明天走,待三天"
|
||||
|
||||
HOST(runtime.go):
|
||||
1. 加载 dialog-agent.md → system prompt
|
||||
2. 读 session → 恢复上下文
|
||||
3. 调 LLM(session + system + tools)
|
||||
|
||||
┌─ 第 1 轮 LLM 推理 ──────────────────────────────┐
|
||||
│ LLM 决定: 先查天气 │
|
||||
│ tool_call: task("weather", {city:"北京", │
|
||||
│ forecast_type:"tomorrow"})
|
||||
│ → 子 Agent 返回: 北京明天 5°C 晴 │
|
||||
│ → 工具结果追加到对话 │
|
||||
└──────────────────────────────────────────────────┘
|
||||
|
||||
┌─ 第 2 轮 LLM 推理 ──────────────────────────────┐
|
||||
│ LLM 看到天气结果, 决定查火车票 │
|
||||
│ tool_call: task("train", {city:"北京", date:"明天"})│
|
||||
│ → 子 Agent 返回: G102 08:00 ¥680 │
|
||||
│ → 工具结果追加到对话 │
|
||||
└──────────────────────────────────────────────────┘
|
||||
|
||||
┌─ 第 3 轮 LLM 推理 ──────────────────────────────┐
|
||||
│ LLM 看到天气+车次, 决定查酒店 │
|
||||
│ tool_call: task("hotel", {city:"北京", nights:3}) │
|
||||
│ → 子 Agent 返回: 建国饭店 ¥500/晚 │
|
||||
│ → 工具结果追加到对话 │
|
||||
└──────────────────────────────────────────────────┘
|
||||
|
||||
┌─ 第 4 轮 LLM 推理 ──────────────────────────────┐
|
||||
│ LLM 觉得信息够了 → 返回文本 │
|
||||
│ "明天北京5°C记得带外套。G102早8点¥680。 │
|
||||
│ 建国饭店3晚¥1500。总预算约¥2180。" │
|
||||
│ → 追加 session.json → 显示给用户 │
|
||||
└──────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 七、实施阶段
|
||||
## 七、实施阶段 — 当前状态
|
||||
|
||||
### 阶段一:基础架构(当前 → 1周)
|
||||
### 阶段一:基础架构(已完成,超计划完成)
|
||||
|
||||
| 任务 | 说明 |
|
||||
|------|------|
|
||||
| 1.1 Frontmatter 扩展 | 解析 `type: main\|sub`、`cache` 字段 |
|
||||
| 1.2 Agent 注册中心 | `registry.go` 扫描 `agents/` 和 `~/.config/yunshu/agents/`,按 type 分类 |
|
||||
| 1.3 `task` 工具 | 实现子 Agent 加载、LLM 调用、缓存读写 |
|
||||
| 1.4 Cache 系统 | `cache/` 目录管理、JSON 文件读写、过期清理 |
|
||||
| 1.5 `memory.read/write` 工具 | 简单的 JSON 文件读写 |
|
||||
| 1.6 dialog-agent.md | 重写为主持者(极薄:人格 + 调度规则) |
|
||||
| 1.7 weather-sub.md | 从旧 weather-agent.md 改造 |
|
||||
| 步骤 | 文件 | 状态 | 说明 |
|
||||
|------|------|------|------|
|
||||
| 1.1 | `types.go` | ✅ | `AgentDef` 加 `Type string`、`Cache *CacheDef`;`Schema` 替代 `ToolParameter` |
|
||||
| 1.2 | `loader.go` | ✅ | Frontmatter 解析加 `type`、`cache` 字段 |
|
||||
| 1.3 | `registry.go` | ✅ | `ScanAgents()` 扫描按 type 分类,同名覆盖 |
|
||||
| 1.4 | `tool.go` | ✅ | `task` / `memory.read` / `memory.write` + 4 个原有工具 |
|
||||
| 1.5 | `runtime.go` | ✅ | `RunSubAgent` + `RunAgent` + cache + session |
|
||||
| 1.6 | `toolschema.go` | ✅ ✨ | **新增(计划外)** — 泛型+反射 `NewTool[T]` 替代手写 Schema |
|
||||
| 1.7 | `main.go` | ✅ | `ScanAgents().GetMain("dialog")` 动态注入子 Agent 列表 |
|
||||
| 1.8 | `agents/dialog-agent.md` | ✅ | 主持者,含多步骤编排指令 |
|
||||
| 1.9 | `agents/weather-sub.md` | ✅ | 天气子 Agent,Markdown 输出 + 生活建议 |
|
||||
| 1.10 | — | ✅ ✨ | **多步骤编排(计划外)** — 砍掉 `capturedOutput`,主 Agent 连续调多个子 Agent |
|
||||
|
||||
#### 计划外新增内容
|
||||
|
||||
### 阶段二:记忆系统(阶段一完成后)
|
||||
1. **泛型+反射工具注册**(`toolschema.go`):
|
||||
- `NewTool[T any]()` 泛型构造函数,自动反射推导 JSON Schema
|
||||
- 输入结构体 + struct tags → 零模板代码的工具注册
|
||||
- handler 内参数为类型安全的结构体字段,无需 `args["x"].(string)`
|
||||
|
||||
| 任务 | 说明 |
|
||||
|------|------|
|
||||
| 2.1 memory-sub.md | 记忆管理员 Agent(从对话提取画像) |
|
||||
| 2.2 记忆数据库 | 结构化存储(画像、偏好、异常记录) |
|
||||
| 2.3 画像自动提取 | memory Agent 定期从对话中提取有用信息 |
|
||||
2. **多步骤编排**(`runtime.go` 改造):
|
||||
- `capturedOutput` 覆写机制已移除
|
||||
- 子 Agent 结果作为普通工具响应留在对话上下文
|
||||
- LLM 可以连续多次调 `task()`,直到信息收集完毕再回答
|
||||
|
||||
### 阶段二:记忆系统(待开始)
|
||||
|
||||
### 阶段三:扩展(可选)
|
||||
| 任务 | 文件 | 状态 |
|
||||
|------|------|------|
|
||||
| 2.1 memory-sub.md | 记忆管理员 Agent(从对话提取画像) | ❌ |
|
||||
| 2.2 记忆数据库 | 结构化存储(画像、偏好、异常记录) | ❌ |
|
||||
| 2.3 画像自动提取 | memory Agent 定期从对话中提取有用信息 | ❌ |
|
||||
|
||||
| 任务 | 说明 |
|
||||
|------|------|
|
||||
| 3.1 earthquake-sub | 地震信息查询 |
|
||||
| 3.2 narrator-sub | 个性化回答生成 |
|
||||
| 3.3 更多数据源 | 台风、核电、火山... |
|
||||
### 阶段三:扩展(待开始)
|
||||
|
||||
| 任务 | 说明 | 状态 |
|
||||
|------|------|------|
|
||||
| 3.1 earthquake-sub | 地震信息查询 | ❌ |
|
||||
| 3.2 train-sub | 火车票查询 | ❌ |
|
||||
| 3.3 hotel-sub | 住宿查询 | ❌ |
|
||||
| 3.4 narrator-sub | 个性化回答生成(成熟期) | ❌ |
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user