最后更新:2026-07-13
本文档解释 cloud-agent 的整体架构:各模块负责什么、数据如何流转、人设/记忆/技能如何工作,以及你可以如何扩展系统。
适合读者:想理解系统、想改人设、想加技能、想接新 Provider 的开发者。
tiny-bot-cloud-agent 是 ai-tiny-bot 陪伴机器人的云端 Agent 服务,用 Go 编写,为 ESP32 固件提供语音对话能力。
| 维度 | 说明 |
|---|---|
| 客户端 | ESP32 固件(../tiny-bot-firmware/)、tools/fake-device 模拟器、demo/ 浏览器调试器 |
| 传输 | WebSocket ws://<host>:5678/ws,双向原始 PCM |
| 音频格式 | 16 kHz / 16 bit / mono(固件 I2S 直放,无需解码器) |
| 目标部署 | 2 vCPU / 2 GB RAM VPS,单二进制 ~15 MB,docker-compose 优先 |
| 核心链路 | PCM 上行 → ASR → LLM 流式 → 句子切分 → TTS → PCM 下行 |
一句话概括:云端收到用户说完的一句话,转成文字,结合人设和记忆让 LLM 生成回复,再合成语音发回设备。
flowchart TB
subgraph client [客户端]
ESP32["ESP32固件 / fake-device / demo"]
end
subgraph transport [传输层]
WS[internal/ws]
Server[internal/server]
Auth[internal/auth]
end
subgraph core [编排层]
Agent[internal/agent]
History[HistoryRegistry]
end
subgraph cognition [认知层]
Persona[internal/persona]
Memory[internal/memory]
Skills[internal/skills]
LLM[internal/llm]
end
subgraph perception [感知与表达层]
ASR[internal/asr]
TTS[internal/tts]
Audio[internal/audio]
Lang[internal/lang]
end
subgraph data [数据层]
Workspace["workspace/"]
SQLite[internal/store]
end
ESP32 --> WS
WS --> Agent
Agent --> ASR
Agent --> LLM
Agent --> TTS
Agent --> Persona
Agent --> Memory
Agent --> Skills
Agent --> History
Persona --> Workspace
Memory --> Workspace
Skills --> Workspace
Auth --> SQLite
| 层 | 包 | 做什么 |
|---|---|---|
| 传输层 | internal/ws, internal/server, internal/auth |
HTTP 路由、WebSocket 协议、设备鉴权 |
| 编排层 | internal/agent |
把 ASR/LLM/TTS/人设/记忆/技能串成一轮对话(Turn) |
| 认知层 | internal/persona, internal/memory, internal/skills, internal/llm |
决定「它是谁、记得什么、会什么、怎么思考」 |
| 感知/表达层 | internal/asr, internal/tts, internal/audio, internal/lang |
听、说、切句、检语种 |
| 数据层 | workspace/, internal/store |
人设/记忆/技能文件 + SQLite 设备数据 |
tiny-bot-cloud-agent/
├── cmd/
│ ├── tiny-bot-cloud-agent/main.go # 主服务入口
│ ├── seed/main.go # 初始化 SQLite 用户/设备
│ ├── echo-agent/main.go # 文本 pipeline 自检
│ └── asr-test / llm-test / tts-test # 各 Provider 独立调试
├── tools/fake-device/ # 模拟 ESP32 WebSocket 客户端
├── internal/ # 业务包(见下文模块表)
├── workspace/ # 运行时数据
│ ├── SOUL.md, IDENTITY.md, AGENT.md, USER.md
│ ├── memory/<device_id>/YYYY-MM-DD.md
│ ├── memory/<device_id>/facts.md
│ └── skills/<name>/SKILL.md + scripts/
├── demo/ # 浏览器单页调试器
├── deploy/ # systemd / Caddy / 部署脚本
├── docs/
│ ├── human-docs/ # 给人看的文档(本文档所在目录)
│ └── firmware-integration/ # 机器可读的 WS/HTTP Schema
└── test/ # 集成测试
主进程 cmd/tiny-bot-cloud-agent/main.go 按以下顺序初始化:
- 加载 config(
.env+ 环境变量) - 打开 SQLite
- 加载 persona(SOUL/IDENTITY/AGENT/USER)
- 初始化 memory(MarkdownStore)
- 加载 skills(扫描
workspace/skills/) - 创建 HistoryRegistry(内存对话历史)
- 按配置构造 ASR / LLM / TTS Provider
- 启动 HTTP 服务(含
/ws) - 监听 SIGHUP → 热重载 persona 和 skills(不重载
.env) - 监听 SIGINT/SIGTERM → 优雅退出
每一轮对话从用户说完一句话(WebSocket end 消息)开始,到服务端发完 TTS 音频并 done 结束。
sequenceDiagram
participant Client as ESP32
participant WS as ws.Session
participant AG as agent.Agent
participant ASR as asr.Transcriber
participant MEM as memory.Store
participant LLM as llm.Chat
participant TTS as tts.Synthesizer
Client->>WS: audio chunks + end
WS->>AG: OnTurn(pcm)
AG->>ASR: Transcribe(pcm)
ASR-->>AG: text + lang
AG->>WS: SendSTT(text)
AG->>MEM: Record(user)
AG->>AG: History.Append(user)
AG->>MEM: Recall(query, 7d, top-5)
AG->>AG: BuildSystemPrompt
AG->>LLM: Chat(msgs, tools, stream)
loop 每个 token
LLM-->>AG: token
AG->>AG: thinking filter + sentence agg
AG->>WS: SendText(token)
AG->>TTS: Synthesize(sentence)
TTS-->>AG: PCM stream
AG->>WS: SendPCMBytes(seq, pcm)
end
AG->>MEM: Record(assistant)
AG->>AG: History.Append(assistant)
AG->>WS: SendDone()
实现位于 internal/agent/agent.go 的 Turn() 方法:
| 步骤 | 操作 | 关键调用 |
|---|---|---|
| 1 | ASR 转写用户语音 | a.ASR.Transcribe() → send.SendSTT() |
| 2 | 写入用户记忆 | a.Memory.Record(deviceID, "user", userText) |
| 3 | 追加对话历史 | a.History.Get(deviceID).Append(user msg) |
| 4 | 召回相关记忆 | a.Memory.Recall(deviceID, userText, 7, 5) |
| 5 | 组装 system prompt | llm.BuildSystemPrompt() + 语种/长度指令 |
| 6 | LLM 流式生成 | a.LLM.Chat() + thinking filter + 句子聚合 |
| 7 | 逐句 TTS 合成 | 单 worker 串行 synthAndSend() → SendPCMBytes |
| 8 | 写入助手记忆 | a.Memory.Record(deviceID, "assistant", fullText) |
| 9 | 结束本轮 | send.SendDone() |
- 每 WebSocket 连接一个 Agent 实例:在
internal/server/server.go的handleWS中创建,连接断开即销毁。 - Turn 异步执行:在 goroutine 中跑,不阻塞 WebSocket read loop。
- TTS 串行合成:用 channel + 单 worker,避免多句 PCM chunk 交错下发。
- 对话历史在内存:
HistoryRegistry按 device_id 隔离,服务重启后丢失。
各 internal/ 包的公开接口、依赖与「改代码该动哪」见 09-internal-packages.md。下表为速查:
| 包/目录 | 职责 | 关键文件 |
|---|---|---|
cmd/tiny-bot-cloud-agent |
启动、依赖注入、SIGHUP 热重载 | main.go |
internal/server |
HTTP 路由(/ws, /provision, /healthz) |
server.go |
internal/ws |
WebSocket 协议、Session 状态机 | session.go, protocol.go |
internal/agent |
Turn 编排,组合所有依赖 | agent.go, history.go |
internal/persona |
加载 SOUL/IDENTITY/AGENT/USER | persona.go |
internal/memory |
Markdown 长期记忆读写与召回 | memory.go, markdown.go, recall.go |
internal/skills |
SKILL.md 加载、注册、脚本执行 | loader.go, registry.go, runner.go |
internal/llm |
LLM 抽象、prompt 组装、thinking 过滤 | llm.go, prompt.go, openai_compat.go |
internal/asr |
语音转文字(可插拔 provider) | asr.go, aliyun.go, mock.go |
internal/tts |
文字转语音(可插拔 provider) | tts.go, minimax.go, aliyun.go |
internal/audio |
PCM 编解码、句子聚合 | pcm.go, sentence.go |
internal/lang |
语种检测(zh / yue / en) | detect.go |
internal/auth |
设备配网、token 校验 | auth.go |
internal/store |
SQLite 持久化 | store.go, schema.sql |
internal/config / logging / aliyunauth |
配置、日志、阿里云鉴权 | 见 09 文档 |
workspace/ |
运行时数据:人设、记忆、技能 | SOUL.md 等 |
代码里没有 internal/soul 包。Soul(灵魂)是人设四层模型的第一层,对应 workspace/SOUL.md,由 internal/persona 统一加载和管理。
四层文件共同决定 Agent「是谁、怎么说话、面对谁」:
| 文件 | 角色 | 控制什么 | 现有示例 |
|---|---|---|---|
| SOUL.md | 灵魂 / 性格 | 声音、语气、性格基调、话题边界 | 名字「艾希」、温热住家管家、大人简洁/小孩软一点、实事求是、不主动提 AI |
| IDENTITY.md | 身份 | 它是什么、能做什么、不能做什么 | 家里管家(ESP32 盒子)、能聊天/记事/查天气、没 skill 不假装能做 |
| AGENT.md | 行为规则 | 怎么说话、何时停、工具/记忆策略 | 日常 1-2 句、讲故事 3-8 句、必要才调工具、不知就直说 |
| USER.md | 用户画像 | 默认家庭画像 | 一家子(大人+小孩)、务实短答、可被设备 user_md 覆盖 |
SOUL.md — 决定「听起来像谁」:
- 名字、形象、声音特质
- 语气规则(短句、口语;大人简洁 / 小孩软一点)
- 实事求是与身份口径(不主动提 AI)
- 话题边界(不讨论什么、如何退场)
IDENTITY.md — 决定「它知道自己是什么」:
- 角色与物理形态(住家管家,住在 ESP32 盒子里)
- 能力清单(聊天、记事、查天气……)
- 能力边界(没 skill 不假装能做;重大决定找家长)
AGENT.md — 决定「它怎么行动」:
- 回复长度策略(闲聊 1-2 句 vs 讲故事 3-8 句)
- 工具调用时机(用户明确要查天气才调,不主动)
- 记忆策略(用户说「记住 XXX」才存;对不上就承认)
- 出错处理(听不清、不会、网络问题)
USER.md — 决定「它面对谁」:
- 称呼、家庭对象、关系
- 偏好(喜欢什么、不喜欢什么)
internal/llm/prompt.go 的 BuildSystemPrompt() 按固定顺序拼接:
flowchart LR
Soul[SOUL.md] --> Prompt[System Prompt]
Identity[IDENTITY.md] --> Prompt
AgentRules[AGENT.md] --> Prompt
User[USER.md] --> Prompt
Facts[已知事实] --> Prompt
MemorySnippets[情节记忆片段] --> Prompt
SkillList[可用技能列表] --> Prompt
LangDir[语种指令] --> Prompt
LenDir[长度指令] --> Prompt
拼接顺序:
SOUL → IDENTITY → AGENT → USER → 已知事实 → 情节记忆片段 → 技能列表 → 语种指令 → 长度指令
其中 SOUL / IDENTITY / AGENT / USER 四段在跨 turn 之间是静态的,有利于 LLM Provider 的 prompt 缓存。
运行时还会追加两条动态指令:
- 语种指令(
LanguageDirective):根据 ASR 结果或文本检测,要求用普通话/粤语/英语回复 - 长度指令(
LengthDirective):根据用户词检测短答/长答模式(如用户说「详细」「继续」则允许更长回复)
- 启动加载:
persona.NewReloader(workspaceRoot)读取四个 Markdown 文件,四个文件缺一不可 - 热重载:
kill -HUP <pid>触发persona.Reloader.Reload()和skills.Reloader.Reload(),失败时保留旧值,服务不中断 - 不重载的内容:
.env/ 进程配置——改 Provider、端口、密钥必须重启服务
已接入:users.user_md未接入agent.resolveUserMD覆盖 USER 段users.soul_md:对话可通过persona.save_soul写入;resolveSoulMD覆盖 SOUL 段(不改 workspace 文件,避免 deploy 冲掉、多设备互踩)- IDENTITY / AGENT 首期仍只读 workspace,不可对话改写
- 热重载 workspace 人设仍用
kill -HUP;DB 覆盖不需要 HUP,下轮 Turn 即生效
Memory 提供 Agent 的长期记忆能力,设计目标是轻量、无外部依赖、适合低配 VPS。拆成两层:
| 层 | 路径 | 作用 | 时效 |
|---|---|---|---|
| 情节日志 | workspace/memory/<device_id>/YYYY-MM-DD.md |
每轮对话原文 | 召回受 lookbackDays 约束 |
| 事实巩固 | workspace/memory/<device_id>/facts.md |
重要要点(偏好、禁忌等) | 不受 lookback 限制,始终可注入 prompt |
- 无向量库:情节召回用关键词匹配 + 时间衰减打分,不引入 embedding 服务
- 接口抽象:
memory.Store接口,当前实现为MarkdownStore,可替换
# 2026-07-11
15:04:05 [user] 今天天气怎么样
15:04:12 [assistant] 今天杭州晴天,25度。- 第一行是日期标题
- 之后每行:
HH:MM:SS [kind] content kind为user/assistant/system
# Facts
- [2026-07-16] 小主人喜欢恐龙
- [2026-07-16] 睡觉前要听故事- 一行一条;
memory.save写入此处(不再写入当日日志的[system]行) - 规范化全文去重;超出
TB_AGENT_FACTS_MAX(默认 80)删最旧
在 agent.Turn 中:
- ASR 完成后,立即写入用户说的话到情节日志(
Record(user)) - LLM 回复完成后,写入完整助手回复(
Record(assistant)) - 若 LLM 调用
memory.save,写入事实层(SaveFact)
情节文件不存在时自动创建目录和文件,追加写入。
情节召回(internal/memory/recall.go):
分词(tokenize):
- 中文:按单字切分
- 英文/数字:≥2 字符的词
打分(scoreLine):
- 关键词重合度 × 0.6
- 时间衰减(30 天线性衰减)× 0.4
流程(MarkdownStore.Recall):
- 若注入了
MemorySearcher(生产路径为 SQLitememory_index):按 query 关键词取候选行,只对这些行打分 - 索引未注入、查询失败或命中为空时:扫描
workspace/memory/<device_id>/下最近 N 天的日期.md(不含facts.md) - 对候选行计算相关度分数,排序取 top-K
- 注入 system prompt 的
## 历史记忆片段(按相关度)段
事实注入:每轮 ListFacts → system prompt 的 ## 已知事实 段(在情节片段之前)。
配置项:TB_AGENT_MEMORY_LOOKBACK_DAYS / TB_AGENT_MEMORY_RECALL_K / TB_AGENT_FACTS_MAX(默认 7 / 5 / 80)。
注意:lookback 只约束情节召回;磁盘上的按天文件不会因过期自动删除。
| History(对话历史) | 情节日志 | 事实巩固 | |
|---|---|---|---|
| 存储 | 内存 ring buffer | 按天 Markdown | facts.md |
| 范围 | 最近 N 轮完整对话(默认 20 轮) | lookback 内关键词 Top-K | 最多 FactsMax 条,始终注入 |
| 生命周期 | 重启后可从 SQLite 恢复 | 永久保留 | 永久保留(超限裁旧) |
| 用途 | 维持当前会话上下文 | 近几天聊过什么 | 跨周重要要点 |
| 包 | internal/agent/history.go |
internal/memory/ |
internal/memory/ |
举例:用户说「记住我喜欢恐龙」→ memory.save 写入 facts.md;即使超过 lookback 天数,之后问「我喜欢什么」仍能从「已知事实」答对。近几天闲聊原文仍走情节召回。
SQLite已接入:memory_index表未同步MarkdownStore.Record同步IndexMemoryLine;Recall优先MemorySearchQuery候选行,无命中再扫文件已实现:agent 每轮注册为内置工具;memory.save/memory.recall工具尚未实现save写事实层,recall搜情节日志- 异步「梦境」巩固(扫日志抽事实)为二期,首期仅显式
memory.save
Skill 是 Agent 的可调用能力,以目录 + Markdown + 脚本的形式组织。每个 skill 对应一个外部能力(查天气、讲笑话、控制设备等)。
workspace/skills/<name>/
├── SKILL.md # YAML frontmatter + 说明正文
├── scripts/run.sh # 默认执行脚本(stdin JSON → stdout 结果)
├── references/ # 可选参考文本
└── assets/ # 可选静态资源
---
name: weather
description: 查询某城市的实时天气。返回 1 行简短结果。
parameters:
city:
type: string
description: 城市名,例如"杭州"
required: true
script: scripts/run.sh
---
# Weather skill
读 stdin 收到的 JSON,取 `city` 字段,返回 1 行字符串。frontmatter 字段:
| 字段 | 含义 |
|---|---|
name |
技能名,LLM tool 的 function name |
description |
技能描述,告诉 LLM 什么时候该用 |
parameters |
参数 schema(类似 OpenAI function parameters) |
script |
相对 skill 目录的执行脚本路径 |
启动时 skills.LoadFromDir(workspaceRoot):
- 扫描
workspace/skills/*/目录 - 解析每个
SKILL.md的 YAML frontmatter - 注册到
Registry(name → Skill 查找表) Registry.ToolDefs()转为llm.ToolDef[],供 LLM API 使用
关键类型:
Skill(loader.go):Name, Description, Parameters, Script, Body, DirRegistry(registry.go):线程安全的 name → Skill 映射Run()(runner.go):在 skill 目录下执行脚本,5 秒超时,stdout 截断 8 KB
| 能力 | 状态 |
|---|---|
| SKILL.md 加载与解析 | 已实现 |
| 注册为 LLM tool schema | 已实现 |
| tools 参数传给 LLM API | 已实现 |
| 解析 LLM 返回的 tool_calls | 已实现(openai_compat.go 增量拼接) |
调用 skills.Run() 执行脚本 |
已实现(agent.runToolLoop) |
SendTool() 通知客户端 |
已实现 |
SendStatus() 静默步骤进度 |
已实现(tool 前后 start/`done |
memory.save / memory.recall 内置工具 |
已实现 |
persona.save_soul / persona.save_user |
已实现(写 SQLite per-user 覆盖) |
code.write / code.run 受限 Python |
已实现(workspace/scratch/<device_id>/,python3 -I,超时与清环境) |
- 在
workspace/skills/<name>/创建SKILL.md和scripts/run.sh - SIGHUP 热重载 skills(
kill -HUP <pid>),或重启服务 - 技能会自动出现在 system prompt 和 LLM tools 列表中
脚本约定:run.sh 从 stdin 读 JSON 参数,结果写到 stdout。
| 组件 | 职责 |
|---|---|
Chat 接口 |
统一的 LLM 调用抽象 |
BuildSystemPrompt |
拼接人设 + 记忆 + 技能 |
OpenAICompat |
OpenAI 兼容 SSE 流式(MiniMax M3 等) |
StreamThinkingFilter |
剥离 `` / <think> 推理块,只展示可见回复 |
LengthDirective |
根据用户词检测短答/长答模式 |
mock |
开发用回显实现 |
| Provider | 实现 | 协议 |
|---|---|---|
mock |
mock.go |
返回固定长度静音 PCM |
aliyun |
aliyun.go |
阿里云 CosyVoice REST |
minimax |
minimax.go |
WebSocket wss://api.minimaxi.com/ws/v1/t2a_v2 |
多语种音色路由:agent.synthAndSend() 对每句话检测语种 → VoiceProfiles.VoiceProfileFor(lang) 选音色。
文本清洗:tts.CleanText() 移除 LLM 幻觉出的方括号乱码(如 [e~[),在 thinking filter 之后、TTS 之前调用。
| Provider | 实现 |
|---|---|
mock |
读 TB_FAKE_ASR_TEXT 或返回固定文本 |
aliyun |
阿里云 Paraformer 一句话识别 REST |
音频契约:PCM 16 kHz / 16 bit / mono。
pcm.go:PCM ↔ base64 转换sentence.go:Aggregator按句末标点(。!?)或 80 字上限切分,供 TTS 逐句合成
Detect(text)→zh/yue/en- 优先级:英语 > 粤语特征词 > 普通话
- 用于:回复语种指令、TTS 音色路由
protocol.go:JSON 消息类型定义(hello / audio / end / stt / text / audio / done / error)session.go:连接状态机(hello 鉴权 → audio 缓冲 → end 触发 turn)- 每连接新建 Agent;Turn 在 goroutine 异步执行
POST /provision?device_id=&code=→ 签发 32 字节 hex token- WS hello 时 SHA-256 比对
devices.token_hash - SQLite 表:
users,devices,conversations,messages,memory_index - 当前实际使用:主要是
users+devices(鉴权);conversations/messages表已建但 agent 未写入
单一配置源,加载优先级:进程环境变量 > .env 文件 > 代码 defaults
实现:internal/config/config.go,模板:.env.example
| 变量 | 默认 | 含义 |
|---|---|---|
TB_AGENT_ADDR |
:5678 |
HTTP 监听地址 |
TB_DB_PATH |
./data/tiny-bot.db |
SQLite 路径 |
TB_WORKSPACE_ROOT |
./workspace |
人设/记忆/技能根目录 |
TB_PROVIDER_ASR |
mock |
ASR Provider |
TB_PROVIDER_LLM |
mock |
LLM Provider |
TB_PROVIDER_TTS |
mock |
TTS Provider |
TB_OPENAI_COMPAT_* |
— | LLM 配置(MiniMax/OpenAI 等) |
TB_ALIYUN_* |
— | 阿里云 ASR/TTS |
TB_MINIMAX_TTS_* |
— | MiniMax TTS 音色/模型 |
TB_AGENT_MAX_RESPONSE_CHARS |
600 |
回复字符上限 |
TB_AGENT_HISTORY_TURNS |
20 |
内存对话轮数 |
TB_AGENT_MEMORY_RECALL_K |
5 |
情节记忆召回条数 |
TB_AGENT_MEMORY_LOOKBACK_DAYS |
7 |
情节记忆回溯天数(不约束事实层) |
TB_AGENT_FACTS_MAX |
80 |
巩固事实条数上限 |
TB_AGENT_TURN_TIMEOUT |
120s |
单轮 turn 超时(含多步 tool) |
TB_AGENT_MAX_TOOL_ROUNDS |
8 |
tool 循环最大轮数 |
TB_CODE_RUN_TIMEOUT |
15s |
code.run 超时 |
TB_CODE_PYTHON_BIN |
python3 |
Python 解释器路径 |
TB_AGENT_DAILY_TOKEN_CAP |
200000 |
每设备日 token 上限(in+out);0 = 不限制 |
workspace/
├── SOUL.md # 人设:灵魂/性格
├── IDENTITY.md # 人设:身份/能力
├── AGENT.md # 人设:行为规则
├── USER.md # 人设:默认用户画像
├── memory/
│ └── <device_id>/
│ ├── 2026-07-11.md
│ └── 2026-07-12.md
└── skills/
└── example-weather/
├── SKILL.md
└── scripts/run.sh
docker-compose 部署时 workspace/ 可写挂载(情节日志 / facts.md);entrypoint 会 chown 给 tinybot。发版时线上已有 workspace 文件优先保留,包内仅追加缺失路径(见 deploy/update-tiny-bot-cloud-agent.sh)。
| 操作 | 方式 | 生效方式 |
|---|---|---|
| 修改人设 | 编辑 workspace/SOUL.md 等四文件,或打开 /demo/workspace.html(需 TB_WORKSPACE_EDITOR_TOKEN) |
保存人设文件会 Persona.Reload;也可 kill -HUP |
| 添加技能 | 新建 workspace/skills/<name>/ |
重启服务 |
| 查看记忆 | 直接读 workspace/memory/<device_id>/ 下的 .md,或 Workspace 编辑页 |
自动增长,无需维护 |
| 切换 Provider | 改 .env 中 TB_PROVIDER_* |
重启服务 |
| 添加设备 | bin/seed -device-id xxx -pairing-code yyy |
立即生效 |
新增 ASR / LLM / TTS Provider 只需实现对应接口并在 main.go 的 buildASR() / buildLLM() / buildTTS() 中注册:
internal/asr/asr.go → Transcriber 接口
internal/llm/llm.go → Chat 接口
internal/tts/tts.go → Synthesizer 接口
internal/memory/memory.go → Store 接口
步骤:
- 在对应
internal/包下新建实现文件 - 实现接口的所有方法
- 在
cmd/tiny-bot-cloud-agent/main.go的 build 函数中加 switch case - 在
.env.example中补充相关配置项
| 扩展方向 | 改动位置 | 说明 |
|---|---|---|
| Tool calling 闭环 | agent.Turn + openai_compat.go |
解析 tool_calls → skills.Run() → 回填 tool message → 二次 LLM |
| per-device 用户画像 | agent.Turn |
读 store.GetUser(device.UserID).UserMD 覆盖 USER 段 |
| 对话持久化 | store/conversations.go + agent.Turn |
turn 前后写入 SQLite conversations/messages |
| 记忆索引加速 | memory/markdown.go + store.MemorySearchQuery |
Record 写索引;Recall 先按关键词过滤候选再打分 |
| 向量记忆 | 新 Store 实现 |
替换关键词召回为 embedding 检索 |
| Token 日限额 | agent.Turn |
使用 cfg.Agent.DailyTokenCapPerDevice |
| 精细打断 | agent.Bind + context cancel |
interrupt 时取消 LLM/TTS goroutine |
| Skills 热重载 | main.go SIGHUP handler |
已实现:与 persona 一并 Reload(),不重载 .env |
| 配置项接入 | agent.Turn |
用 cfg.Agent.MemoryRecallK/LookbackDays 替换硬编码 7, 5 |
| 内置 memory 工具 | internal/skills/ 或 agent 内置 |
实现 memory.save / memory.recall 作为内置 tool |
| 新 HTTP 端点 | internal/server/server.go |
如管理后台、记忆浏览 API |
| 多 Agent 人设 | persona + workspace/ |
按 device 或 user 加载不同 SOUL/IDENTITY 文件 |
如果你刚开始扩展,建议按这个顺序:
- 改人设文件 — 零代码,立刻改变 Agent 性格和行为
- 接真实 Provider — 把 ASR/LLM/TTS 从 mock 切到阿里云/MiniMax
- 添加 Skill 目录 — 即使 tool calling 未闭环,文字描述已能影响 LLM 行为
- 实现 Tool calling 闭环 — 让技能真正可执行,收益最大
- per-device 用户画像 — 多设备/多用户场景必需
- 对话持久化 — 服务重启不丢上下文
| 文档 | 路径 | 内容 |
|---|---|---|
| 数据库设计 | 01-database.md | SQLite 表结构、存储策略 |
| 固件对接 | 02-firmware-integration.md | ESP32 如何连 WebSocket |
| internal 包手册 | 09-internal-packages.md | 各 Go 包职责、接口、依赖 |
| 部署指南 | 05-deployment.md | VPS docker-compose 部署 |
| WS JSON Schema | ../firmware-integration/ws-protocol.schema.json | 机器可读协议 |
| HTTP OpenAPI | ../firmware-integration/http-api.openapi.yaml | HTTP 端点定义 |
| 项目规范 | ../../CLAUDE.md | 命名约定、配置原则 |
| 快速开始 | ../../README.md | 构建、运行、调试 |
2026-07-16 更新:下列缺口已在本轮扩展中补齐。保留本表作历史对照。
| 限制 | 状态 | 实现位置 |
|---|---|---|
| Tool calling 未闭环 | 已实现 | openai_compat 解析 tool_calls + agent.runToolLoop |
user_md 未接入 |
已实现 | agent.resolveUserMD 覆盖 USER 段 |
| 对话未持久化到 SQLite | 已实现 | Turn 写 messages;hello 时恢复 History |
memory_index 未接入 |
已实现 | Record 同步索引;Recall 优先按关键词过滤候选 |
| 记忆参数硬编码 | 已实现 | AgentCfg.MemoryLookbackDays/RecallK |
| Token 日限额未强制 | 已实现 | Turn 开头 SumDeviceTokensToday |
| Interrupt 未精细取消 | 已实现 | session turnCancel |
| SIGHUP 不重载 skills | 已实现 | skills.Reloader + main SIGHUP |