项目结构分析
This commit is contained in:
parent
30155c1c59
commit
84a52428b5
1 changed files with 901 additions and 0 deletions
901
docs/architecture-flow.md
Normal file
901
docs/architecture-flow.md
Normal file
|
|
@ -0,0 +1,901 @@
|
||||||
|
# PicoClaw 代码执行流程分析
|
||||||
|
|
||||||
|
## 项目概述
|
||||||
|
|
||||||
|
PicoClaw 是一个用 Go 编写的超轻量级个人 AI 助手,由 Sipeed 发起。核心内存占用 <10MB,可在 $10 硬件上运行。
|
||||||
|
|
||||||
|
## 项目结构
|
||||||
|
|
||||||
|
```
|
||||||
|
picoclaw/
|
||||||
|
├── cmd/
|
||||||
|
│ ├── picoclaw/ # 主 CLI 入口
|
||||||
|
│ │ ├── main.go # 程序入口,Cobra 命令注册
|
||||||
|
│ │ └── internal/ # CLI 子命令
|
||||||
|
│ │ ├── agent/ # picoclaw agent(交互式/单次对话)
|
||||||
|
│ │ ├── gateway/ # picoclaw gateway(长期运行网关)
|
||||||
|
│ │ ├── onboard/ # picoclaw onboard(初始化配置)
|
||||||
|
│ │ ├── cron/ # picoclaw cron(定时任务管理)
|
||||||
|
│ │ ├── skills/ # picoclaw skills(技能管理)
|
||||||
|
│ │ ├── auth/ # picoclaw auth(Provider 认证)
|
||||||
|
│ │ ├── migrate/ # picoclaw migrate(配置迁移)
|
||||||
|
│ │ ├── model/ # picoclaw model(模型管理)
|
||||||
|
│ │ ├── status/ # picoclaw status(状态查看)
|
||||||
|
│ │ └── version/ # picoclaw version
|
||||||
|
│ └── picoclaw-launcher-tui/ # TUI 启动器(终端 UI + WebUI)
|
||||||
|
├── pkg/ # 核心库
|
||||||
|
│ ├── agent/ # Agent 运行时(核心中的核心)
|
||||||
|
│ │ ├── loop.go # AgentLoop 主循环
|
||||||
|
│ │ ├── turn.go # 单轮对话状态管理
|
||||||
|
│ │ ├── instance.go # AgentInstance 实例创建
|
||||||
|
│ │ ├── context.go # 上下文构建(系统提示词)
|
||||||
|
│ │ ├── hooks.go # Hook 系统
|
||||||
|
│ │ ├── steering.go # 消息注入/转向
|
||||||
|
│ │ ├── subturn.go # 子 Agent 并发执行
|
||||||
|
│ │ ├── eventbus.go # 事件总线
|
||||||
|
│ │ ├── registry.go # Agent 注册表
|
||||||
|
│ │ ├── definition.go # AGENT.md/SOUL.md 解析
|
||||||
|
│ │ ├── memory.go # 记忆存储
|
||||||
|
│ │ └── model_resolution.go # 模型解析
|
||||||
|
│ ├── bus/ # 消息总线
|
||||||
|
│ │ ├── types.go # InboundMessage/OutboundMessage
|
||||||
|
│ │ └── bus.go # 消息传递
|
||||||
|
│ ├── channels/ # 聊天平台集成(17+)
|
||||||
|
│ │ ├── manager.go # ChannelManager 统一管理
|
||||||
|
│ │ ├── telegram/
|
||||||
|
│ │ ├── discord/
|
||||||
|
│ │ ├── weixin/ # 微信
|
||||||
|
│ │ ├── wecom/ # 企业微信
|
||||||
|
│ │ ├── qq/
|
||||||
|
│ │ ├── feishu/ # 飞书
|
||||||
|
│ │ ├── slack/
|
||||||
|
│ │ ├── whatsapp/
|
||||||
|
│ │ └── ... # 更多平台
|
||||||
|
│ ├── providers/ # LLM Provider 适配器(30+)
|
||||||
|
│ │ ├── types.go # LLMProvider 接口定义
|
||||||
|
│ │ ├── fallback.go # 降级链
|
||||||
|
│ │ ├── cooldown.go # 冷却追踪
|
||||||
|
│ │ ├── openai/
|
||||||
|
│ │ ├── anthropic/
|
||||||
|
│ │ ├── gemini/
|
||||||
|
│ │ ├── deepseek/
|
||||||
|
│ │ └── ... # 更多 Provider
|
||||||
|
│ ├── gateway/ # HTTP 网关服务
|
||||||
|
│ │ └── gateway.go # Gateway 启动与服务编排
|
||||||
|
│ ├── config/ # 配置管理
|
||||||
|
│ │ └── config.go # Config 结构与加载
|
||||||
|
│ ├── tools/ # 工具注册与执行
|
||||||
|
│ ├── session/ # 会话持久化(JSONL)
|
||||||
|
│ ├── memory/ # 对话记忆
|
||||||
|
│ ├── skills/ # 技能系统
|
||||||
|
│ ├── routing/ # 模型路由
|
||||||
|
│ ├── cron/ # 定时任务服务
|
||||||
|
│ ├── heartbeat/ # 心跳服务
|
||||||
|
│ ├── media/ # 媒体文件存储
|
||||||
|
│ ├── voice/ # 语音转录
|
||||||
|
│ ├── health/ # 健康检查 HTTP 服务
|
||||||
|
│ └── commands/ # 斜杠命令系统
|
||||||
|
├── docker/ # Docker 部署
|
||||||
|
│ ├── docker-compose.yml # 三种 profile 部署
|
||||||
|
│ ├── entrypoint.sh # 容器入口脚本
|
||||||
|
│ └── Dockerfile # 多种构建方式
|
||||||
|
├── workspace/ # 默认工作空间
|
||||||
|
│ ├── AGENT.md # Agent 身份与工具定义
|
||||||
|
│ ├── SOUL.md # Agent 人格设定
|
||||||
|
│ └── USER.md # 用户偏好
|
||||||
|
└── docs/ # 文档
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 一、程序启动流程
|
||||||
|
|
||||||
|
### 1.1 CLI 入口 (`cmd/picoclaw/main.go`)
|
||||||
|
|
||||||
|
```
|
||||||
|
main()
|
||||||
|
│
|
||||||
|
├── 打印 ASCII Banner
|
||||||
|
├── 创建 Cobra Root Command
|
||||||
|
├── 注册子命令:
|
||||||
|
│ ├── onboard → 初始化 ~/.picoclaw/ 目录与配置
|
||||||
|
│ ├── agent → 单次/交互式对话
|
||||||
|
│ ├── gateway → 启动长期运行网关
|
||||||
|
│ ├── cron → 管理定时任务
|
||||||
|
│ ├── skills → 安装/搜索技能
|
||||||
|
│ ├── auth → Provider OAuth 认证
|
||||||
|
│ ├── migrate → 配置版本迁移
|
||||||
|
│ ├── model → 模型管理
|
||||||
|
│ ├── status → 运行状态查看
|
||||||
|
│ └── version → 版本信息
|
||||||
|
└── cmd.Execute()
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.2 Gateway 启动流程 (`pkg/gateway/gateway.go:Run()`)
|
||||||
|
|
||||||
|
这是最核心的运行模式,完整启动流程如下:
|
||||||
|
|
||||||
|
```
|
||||||
|
gateway.Run(debug, homePath, configPath, allowEmptyStartup)
|
||||||
|
│
|
||||||
|
├── 1. 初始化日志系统
|
||||||
|
│ ├── logger.InitPanic() → Panic 日志
|
||||||
|
│ └── logger.EnableFileLogging() → 文件日志
|
||||||
|
│
|
||||||
|
├── 2. 加载配置
|
||||||
|
│ └── config.LoadConfig(configPath) → ~/.picoclaw/config.json
|
||||||
|
│
|
||||||
|
├── 3. 创建 LLM Provider
|
||||||
|
│ └── createStartupProvider(cfg) → OpenAI/Anthropic/Gemini 等
|
||||||
|
│
|
||||||
|
├── 4. 创建 MessageBus
|
||||||
|
│ └── bus.NewMessageBus() → 消息总线(Inbound/Outbound 通道)
|
||||||
|
│
|
||||||
|
├── 5. 创建 AgentLoop
|
||||||
|
│ └── agent.NewAgentLoop(cfg, msgBus, provider)
|
||||||
|
│ ├── NewAgentRegistry() → 创建 Agent 注册表
|
||||||
|
│ ├── NewCooldownTracker() → 冷却追踪器
|
||||||
|
│ ├── NewFallbackChain() → 降级链
|
||||||
|
│ ├── NewEventManager() → 事件总线
|
||||||
|
│ ├── NewHookManager() → Hook 管理器
|
||||||
|
│ ├── configureHookManager() → 从配置加载 Hooks
|
||||||
|
│ └── registerSharedTools() → 注册共享工具(web/message/spawn 等)
|
||||||
|
│
|
||||||
|
├── 6. 启动后台服务
|
||||||
|
│ └── setupAndStartServices()
|
||||||
|
│ ├── CronService.Start() → 定时任务服务
|
||||||
|
│ ├── HeartbeatService.Start() → 心跳监控
|
||||||
|
│ ├── MediaStore.Start() → 媒体文件管理(含清理)
|
||||||
|
│ ├── ChannelManager.Start() → 聊天平台管理器
|
||||||
|
│ ├── DeviceService → 设备管理
|
||||||
|
│ └── HealthServer.Start() → HTTP 健康检查
|
||||||
|
│
|
||||||
|
├── 7. 启动 AgentLoop(goroutine)
|
||||||
|
│ └── go agentLoop.Run(ctx)
|
||||||
|
│
|
||||||
|
├── 8. 配置热重载(可选)
|
||||||
|
│ └── setupConfigWatcherPolling() → 监听 config.json 变化
|
||||||
|
│
|
||||||
|
└── 9. 主循环等待信号
|
||||||
|
├── <-sigChan (Ctrl+C) → 优雅关闭
|
||||||
|
├── <-configReloadChan → 配置热重载
|
||||||
|
└── <-manualReloadChan → /reload 端点触发
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 二、核心数据结构
|
||||||
|
|
||||||
|
### 2.1 AgentLoop(`pkg/agent/loop.go`)
|
||||||
|
|
||||||
|
AgentLoop 是整个系统的核心调度器:
|
||||||
|
|
||||||
|
```go
|
||||||
|
type AgentLoop struct {
|
||||||
|
bus *bus.MessageBus // 消息总线
|
||||||
|
cfg *config.Config // 全局配置
|
||||||
|
registry *AgentRegistry // Agent 注册表(多 Agent 支持)
|
||||||
|
state *state.Manager // 状态管理
|
||||||
|
eventBus *EventBus // 事件总线
|
||||||
|
hooks *HookManager // Hook 管理器
|
||||||
|
fallback *providers.FallbackChain // LLM 降级链
|
||||||
|
channelManager *channels.Manager // 聊天平台管理器
|
||||||
|
mediaStore media.MediaStore // 媒体存储
|
||||||
|
transcriber voice.Transcriber // 语音转录
|
||||||
|
cmdRegistry *commands.Registry // 斜杠命令注册表
|
||||||
|
mcp mcpRuntime // MCP 协议运行时
|
||||||
|
steering *steeringQueue // 消息转向队列
|
||||||
|
activeTurnStates sync.Map // 活跃 Turn 状态表
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 AgentInstance(`pkg/agent/instance.go`)
|
||||||
|
|
||||||
|
每个 Agent 的完整配置实例:
|
||||||
|
|
||||||
|
```go
|
||||||
|
type AgentInstance struct {
|
||||||
|
ID string // Agent 标识
|
||||||
|
Name string // Agent 名称
|
||||||
|
Model string // 主模型
|
||||||
|
Fallbacks []string // 降级模型列表
|
||||||
|
Workspace string // 工作目录
|
||||||
|
MaxIterations int // 最大工具调用迭代次数
|
||||||
|
MaxTokens int // 最大输出 Token
|
||||||
|
ContextWindow int // 上下文窗口大小
|
||||||
|
Provider providers.LLMProvider // LLM Provider 实例
|
||||||
|
Sessions session.SessionStore // 会话存储
|
||||||
|
ContextBuilder *ContextBuilder // 上下文构建器
|
||||||
|
Tools *tools.ToolRegistry // 工具注册表
|
||||||
|
Router *routing.Router // 模型路由器
|
||||||
|
LightProvider providers.LLMProvider // 轻量模型 Provider
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.3 TurnState(`pkg/agent/turn.go`)
|
||||||
|
|
||||||
|
单轮对话的完整状态:
|
||||||
|
|
||||||
|
```go
|
||||||
|
type TurnPhase string
|
||||||
|
const (
|
||||||
|
TurnPhaseSetup = "setup" // 初始化阶段
|
||||||
|
TurnPhaseRunning = "running" // LLM 调用中
|
||||||
|
TurnPhaseTools = "tools" // 工具执行中
|
||||||
|
TurnPhaseFinalizing = "finalizing" // 收尾阶段
|
||||||
|
TurnPhaseCompleted = "completed" // 已完成
|
||||||
|
TurnPhaseAborted = "aborted" // 已中止
|
||||||
|
)
|
||||||
|
|
||||||
|
type turnState struct {
|
||||||
|
turnID string
|
||||||
|
agent *AgentInstance
|
||||||
|
phase TurnPhase
|
||||||
|
iteration int // 当前迭代次数
|
||||||
|
userMessage string // 用户消息
|
||||||
|
media []string // 媒体附件
|
||||||
|
finalContent string // 最终回复
|
||||||
|
// 中断控制
|
||||||
|
gracefulInterrupt bool
|
||||||
|
hardAbort bool
|
||||||
|
providerCancel context.CancelFunc
|
||||||
|
// SubTurn 支持
|
||||||
|
depth int
|
||||||
|
parentTurnState *turnState
|
||||||
|
childTurnIDs []string
|
||||||
|
pendingResults chan *ToolResult
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.4 消息结构(`pkg/bus/types.go`)
|
||||||
|
|
||||||
|
```go
|
||||||
|
type InboundMessage struct {
|
||||||
|
Channel string // 平台: "telegram", "discord", "cli" 等
|
||||||
|
SenderID string // 发送者 ID
|
||||||
|
Sender SenderInfo // 结构化发送者信息
|
||||||
|
ChatID string // 会话 ID
|
||||||
|
Content string // 消息文本
|
||||||
|
Media []string // 媒体附件 (media:// refs)
|
||||||
|
Peer Peer // 路由对等体 (direct/group/channel)
|
||||||
|
SessionKey string // 会话标识
|
||||||
|
Metadata map[string]string // 自定义元数据
|
||||||
|
}
|
||||||
|
|
||||||
|
type OutboundMessage struct {
|
||||||
|
Channel string // 平台
|
||||||
|
ChatID string // 会话 ID
|
||||||
|
Content string // 回复文本
|
||||||
|
ReplyToMessageID string // 引用回复的消息 ID
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.5 LLM Provider 接口(`pkg/providers/types.go`)
|
||||||
|
|
||||||
|
```go
|
||||||
|
type LLMProvider interface {
|
||||||
|
Chat(ctx context.Context, messages []Message, tools []ToolDefinition,
|
||||||
|
model string, options map[string]any) (*LLMResponse, error)
|
||||||
|
GetDefaultModel() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// 可选接口
|
||||||
|
type StreamingProvider interface {
|
||||||
|
ChatStream(ctx context.Context, ..., onChunk func(accumulated string)) (*LLMResponse, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ThinkingCapable interface {
|
||||||
|
SupportsThinking() bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type NativeSearchCapable interface {
|
||||||
|
SupportsNativeSearch() bool
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 三、消息处理完整流程
|
||||||
|
|
||||||
|
### 3.1 AgentLoop 主循环
|
||||||
|
|
||||||
|
```
|
||||||
|
AgentLoop.Run(ctx) [loop.go:380]
|
||||||
|
│
|
||||||
|
├── ensureHooksInitialized() → 初始化 Hook 系统
|
||||||
|
├── ensureMCPInitialized() → 初始化 MCP 运行时
|
||||||
|
│
|
||||||
|
└── for { select {
|
||||||
|
│
|
||||||
|
├── <-ctx.Done() → 退出
|
||||||
|
│
|
||||||
|
└── msg := <-bus.InboundChan() → 收到入站消息
|
||||||
|
│
|
||||||
|
├── resolveSteeringTarget() → 检查是否有活跃 Turn 需要转向
|
||||||
|
│ └── 若有 → drainBusToSteering() → 排水到转向队列
|
||||||
|
│
|
||||||
|
├── processMessage(msg) → 处理消息(核心)
|
||||||
|
│
|
||||||
|
├── buildContinuationTarget() → 构建延续目标
|
||||||
|
│
|
||||||
|
└── 处理转向队列中的排队消息
|
||||||
|
└── Continue() → 继续对话
|
||||||
|
}}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 processMessage 详细流程
|
||||||
|
|
||||||
|
```
|
||||||
|
processMessage(ctx, msg) [loop.go:1255]
|
||||||
|
│
|
||||||
|
├── 1. 日志记录
|
||||||
|
│
|
||||||
|
├── 2. 语音转录(如有音频)
|
||||||
|
│ └── transcribeAudioInMessage()
|
||||||
|
│
|
||||||
|
├── 3. 系统消息路由
|
||||||
|
│ └── if channel == "system" → processSystemMessage()
|
||||||
|
│
|
||||||
|
├── 4. 消息路由解析
|
||||||
|
│ └── resolveMessageRoute(msg) → 确定 Agent 和会话
|
||||||
|
│ ├── registry.ResolveRoute() → 根据 channel/peer/guild 路由
|
||||||
|
│ └── 返回 (route, agentInstance, error)
|
||||||
|
│
|
||||||
|
├── 5. 重置 message 工具状态
|
||||||
|
│
|
||||||
|
├── 6. 解析 SessionKey
|
||||||
|
│ └── resolveScopeKey(route, sessionKey)
|
||||||
|
│
|
||||||
|
├── 7. 构建 processOptions
|
||||||
|
│ ├── SessionKey
|
||||||
|
│ ├── Channel / ChatID / SenderID
|
||||||
|
│ ├── UserMessage
|
||||||
|
│ ├── Media
|
||||||
|
│ └── DefaultResponse
|
||||||
|
│
|
||||||
|
├── 8. 斜杠命令检查
|
||||||
|
│ └── handleCommand() → 如匹配则直接返回
|
||||||
|
│
|
||||||
|
├── 9. 挂起技能检查
|
||||||
|
│ └── takePendingSkills() → 应用技能覆盖
|
||||||
|
│
|
||||||
|
└── 10. runAgentLoop(ctx, agent, opts) → 执行 Agent 循环
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.3 runAgentLoop → runTurn(核心执行循环)
|
||||||
|
|
||||||
|
```
|
||||||
|
runAgentLoop(ctx, agent, opts) [loop.go:1467]
|
||||||
|
│
|
||||||
|
├── RecordLastChannel() → 记录最后活跃频道
|
||||||
|
├── newTurnState(agent, opts, scope) → 创建 Turn 状态
|
||||||
|
└── runTurn(ctx, ts) → 执行 Turn
|
||||||
|
│
|
||||||
|
├── 注册活跃 Turn → registerActiveTurn()
|
||||||
|
├── 发射 TurnStart 事件
|
||||||
|
│
|
||||||
|
├── 加载会话历史
|
||||||
|
│ ├── Sessions.GetHistory(sessionKey)
|
||||||
|
│ └── Sessions.GetSummary(sessionKey)
|
||||||
|
│
|
||||||
|
├── 构建消息列表
|
||||||
|
│ └── ContextBuilder.BuildMessages(history, summary, userMessage, ...)
|
||||||
|
│ ├── 构建 System Prompt(含缓存)
|
||||||
|
│ │ ├── AGENT.md → Agent 身份、工具、技能
|
||||||
|
│ │ ├── SOUL.md → Agent 人格
|
||||||
|
│ │ ├── USER.md → 用户偏好
|
||||||
|
│ │ ├── Skills → 已安装技能
|
||||||
|
│ │ └── 动态上下文(时间、会话信息)
|
||||||
|
│ ├── 拼接对话历史
|
||||||
|
│ └── 添加当前用户消息 + 媒体
|
||||||
|
│
|
||||||
|
├── 解析媒体引用
|
||||||
|
│ └── resolveMediaRefs(messages, mediaStore)
|
||||||
|
│
|
||||||
|
├── 上下文预算检查
|
||||||
|
│ └── isOverContextBudget() → 若超限则 forceCompression()
|
||||||
|
│
|
||||||
|
├── 保存用户消息到会话
|
||||||
|
│
|
||||||
|
├── 模型选择
|
||||||
|
│ └── selectCandidates(agent, userMessage) → 主模型/轻量模型
|
||||||
|
│
|
||||||
|
└── ═══════════════════════════════════════════
|
||||||
|
║ Turn 迭代循环 ║
|
||||||
|
═══════════════════════════════════════════
|
||||||
|
for iteration < maxIterations {
|
||||||
|
│
|
||||||
|
├── 检查硬中止 → hardAbortRequested()
|
||||||
|
├── 检查父 Turn 状态(SubTurn)
|
||||||
|
├── 轮询 SubTurn 结果
|
||||||
|
├── 注入 steering 消息
|
||||||
|
│
|
||||||
|
├── 调用 LLM ────────────────────────
|
||||||
|
│ ├── 流式: provider.ChatStream()
|
||||||
|
│ │ └── onChunk → 实时推送到 Channel
|
||||||
|
│ └── 非流式: provider.Chat()
|
||||||
|
│
|
||||||
|
├── Hook: Observer 回调
|
||||||
|
├── Hook: Interceptor 拦截
|
||||||
|
├── Hook: Approval 审批
|
||||||
|
│
|
||||||
|
├── 解析 LLM 响应
|
||||||
|
│ ├── 提取 finalContent(文本回复)
|
||||||
|
│ └── 提取 toolCalls(工具调用)
|
||||||
|
│
|
||||||
|
├── if 无 toolCalls → 跳出循环
|
||||||
|
│
|
||||||
|
├── 执行工具调用
|
||||||
|
│ for _, toolCall := range toolCalls {
|
||||||
|
│ │
|
||||||
|
│ ├── 工具查找 → Tools.Get(toolName)
|
||||||
|
│ ├── 工具执行 → tool.Execute(params)
|
||||||
|
│ ├── 结果添加到 messages
|
||||||
|
│ └── Hook 回调
|
||||||
|
│ }
|
||||||
|
│
|
||||||
|
├── 模型降级检查
|
||||||
|
│ └── fallbackChain.Execute() → 如失败则切换 Provider
|
||||||
|
│
|
||||||
|
└── 继续迭代 → 再次调用 LLM
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.4 Turn 完成后的处理
|
||||||
|
|
||||||
|
```
|
||||||
|
runTurn() 返回 turnResult
|
||||||
|
│
|
||||||
|
├── 保存 assistant 回复到会话
|
||||||
|
├── 发射 TurnEnd 事件
|
||||||
|
│
|
||||||
|
└── 回到 runAgentLoop()
|
||||||
|
├── 发布 followUp 消息
|
||||||
|
├── 若 SendResponse → 发送到 OutboundMessage
|
||||||
|
└── 返回 finalContent
|
||||||
|
|
||||||
|
回到 AgentLoop.Run() 的消息处理
|
||||||
|
│
|
||||||
|
├── publishResponseIfNeeded() → 通过 Bus 发送回复
|
||||||
|
│ └── bus.PublishOutbound()
|
||||||
|
│ └── ChannelManager 接收
|
||||||
|
│ └── channelWorker 发送到平台
|
||||||
|
│
|
||||||
|
├── 处理排队的 steering 消息
|
||||||
|
│ └── Continue() → 新一轮 Turn
|
||||||
|
│
|
||||||
|
└── 最终回复发布
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 四、Channel 系统架构
|
||||||
|
|
||||||
|
### 4.1 ChannelManager(`pkg/channels/manager.go`)
|
||||||
|
|
||||||
|
```
|
||||||
|
ChannelManager
|
||||||
|
├── channels map[string]Channel → 已注册的平台通道
|
||||||
|
├── workers map[string]*channelWorker → 每个 Channel 一个 Worker
|
||||||
|
├── mediaStore → 媒体存储
|
||||||
|
│
|
||||||
|
├── 启动流程:
|
||||||
|
│ ├── 遍历配置中的 Channel
|
||||||
|
│ ├── 创建 Channel 实例
|
||||||
|
│ ├── channel.Start(ctx) → 启动平台连接
|
||||||
|
│ └── 启动 channelWorker goroutine
|
||||||
|
│
|
||||||
|
├── 入站流程:
|
||||||
|
│ Channel 收到消息
|
||||||
|
│ └── 转换为 InboundMessage
|
||||||
|
│ └── bus.PublishInbound() → AgentLoop 消费
|
||||||
|
│
|
||||||
|
└── 出站流程:
|
||||||
|
channelWorker 监听 queue chan
|
||||||
|
└── 收到 OutboundMessage
|
||||||
|
└── channel.SendMessage()
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.2 支持的 Channel 平台
|
||||||
|
|
||||||
|
| Channel | 包名 | 协议 |
|
||||||
|
|---------|------|------|
|
||||||
|
| Telegram | `telegram` | Long Polling / Webhook |
|
||||||
|
| Discord | `discord` | WebSocket Gateway |
|
||||||
|
| 微信 | `weixin` | iLink API |
|
||||||
|
| 企业微信 | `wecom` | WebSocket |
|
||||||
|
| QQ | `qq` | WebSocket |
|
||||||
|
| 飞书 | `feishu` | WebSocket / SDK |
|
||||||
|
| Slack | `slack` | Socket Mode |
|
||||||
|
| WhatsApp | `whatsapp` | Bridge 协议 |
|
||||||
|
| WhatsApp Native | `whatsapp_native` | 原生协议 |
|
||||||
|
| 钉钉 | `dingtalk` | Stream Mode |
|
||||||
|
| Matrix | (内置) | Sync API |
|
||||||
|
| IRC | `irc` | IRC 协议 |
|
||||||
|
| LINE | `line` | Messaging API |
|
||||||
|
| MaixCam | `maixcam` | 设备集成 |
|
||||||
|
| OneBot | `onebot` | OneBot 协议 |
|
||||||
|
| Pico | `pico` | 硬件设备 |
|
||||||
|
|
||||||
|
### 4.3 Channel 扩展接口
|
||||||
|
|
||||||
|
```go
|
||||||
|
type Channel interface {
|
||||||
|
Start(ctx context.Context) error
|
||||||
|
HandleMessage(ctx context.Context, msg InboundMessage) error
|
||||||
|
Stop(ctx context.Context) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// 可选能力接口
|
||||||
|
type TypingCapable interface {
|
||||||
|
StartTyping(ctx context.Context, chatID string) (stop func(), error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type StreamingCapable interface {
|
||||||
|
BeginStream(ctx context.Context, chatID string) (Streamer, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type PlaceholderCapable interface {
|
||||||
|
SendPlaceholder(ctx context.Context, chatID string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type MessageEditor interface {
|
||||||
|
EditMessage(ctx context.Context, chatID, msgID, newContent string) error
|
||||||
|
DeleteMessage(ctx context.Context, chatID, msgID string) error
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 五、LLM Provider 系统
|
||||||
|
|
||||||
|
### 5.1 Provider 架构
|
||||||
|
|
||||||
|
```
|
||||||
|
LLMProvider 接口
|
||||||
|
│
|
||||||
|
├── OpenAI (gpt-4, gpt-4o, o1, o3...)
|
||||||
|
├── Anthropic (Claude 系列)
|
||||||
|
├── Google (Gemini)
|
||||||
|
├── DeepSeek
|
||||||
|
├── Zhipu (GLM)
|
||||||
|
├── OpenRouter
|
||||||
|
├── Azure OpenAI
|
||||||
|
├── AWS Bedrock
|
||||||
|
├── xAI (Grok)
|
||||||
|
├── Ollama (本地模型)
|
||||||
|
└── 30+ 更多 Provider
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.2 降级链(Fallback Chain)
|
||||||
|
|
||||||
|
```
|
||||||
|
selectCandidates(agent, userMessage)
|
||||||
|
│
|
||||||
|
├── 模型路由(如启用)
|
||||||
|
│ ├── Router.Score(message) → 复杂度评分
|
||||||
|
│ ├── 低复杂度 → LightProvider(轻量模型)
|
||||||
|
│ └── 高复杂度 → 主 Provider
|
||||||
|
│
|
||||||
|
└── 降级策略
|
||||||
|
├── 主 Provider 调用失败
|
||||||
|
├── FailoverError 分类:
|
||||||
|
│ ├── auth → 认证错误
|
||||||
|
│ ├── rate_limit → 限流
|
||||||
|
│ ├── billing → 计费问题
|
||||||
|
│ ├── timeout → 超时
|
||||||
|
│ └── context_overflow → 上下文溢出
|
||||||
|
├── CooldownTracker 记录失败 Provider
|
||||||
|
├── FallbackChain 尝试下一个候选
|
||||||
|
└── 所有候选失败 → 返回错误
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 六、工具系统
|
||||||
|
|
||||||
|
### 6.1 内置工具
|
||||||
|
|
||||||
|
| 工具名 | 功能 | 说明 |
|
||||||
|
|--------|------|------|
|
||||||
|
| `read_file` | 读取文件 | 支持行号范围,有大小限制 |
|
||||||
|
| `write_file` | 写入文件 | 创建或覆盖文件 |
|
||||||
|
| `edit_file` | 编辑文件 | 字符串替换 |
|
||||||
|
| `append_file` | 追加文件 | 追加内容到文件末尾 |
|
||||||
|
| `list_dir` | 列出目录 | 目录内容浏览 |
|
||||||
|
| `exec` | 执行命令 | 带沙箱限制的命令执行 |
|
||||||
|
| `message` | 发送消息 | 通过 Bus 发送到其他 Channel |
|
||||||
|
| `send_file` | 发送文件 | 文件附件发送 |
|
||||||
|
| `web_search` | 网页搜索 | Brave/Tavily/DuckDuckGo/Perplexity/SearXNG/百度 |
|
||||||
|
| `web_fetch` | 网页抓取 | HTTP 请求获取网页内容 |
|
||||||
|
| `spawn` | 子 Agent | 创建并发 SubTurn |
|
||||||
|
| `skills_search` | 搜索技能 | 从技能仓库搜索 |
|
||||||
|
| `skills_install` | 安装技能 | 安装技能到工作空间 |
|
||||||
|
| `mcp_tool` | MCP 工具 | MCP 协议工具调用 |
|
||||||
|
|
||||||
|
### 6.2 工具注册流程
|
||||||
|
|
||||||
|
```
|
||||||
|
NewAgentInstance()
|
||||||
|
│
|
||||||
|
├── 创建 ToolRegistry
|
||||||
|
│
|
||||||
|
├── 注册基础工具(根据配置开关):
|
||||||
|
│ ├── read_file → NewReadFileTool(workspace, restrict, maxSize, allowPaths)
|
||||||
|
│ ├── write_file → NewWriteFileTool(workspace, restrict, allowPaths)
|
||||||
|
│ ├── edit_file → NewEditFileTool(workspace, restrict)
|
||||||
|
│ ├── append_file→ NewAppendFileTool(workspace, restrict)
|
||||||
|
│ ├── list_dir → NewListDirTool(workspace, restrict)
|
||||||
|
│ └── exec → NewExecTool(workspace, restrict, timeout)
|
||||||
|
│
|
||||||
|
└── registerSharedTools() (AgentLoop 级别):
|
||||||
|
├── web_search → NewWebSearchTool(brave, tavily, duckduckgo, ...)
|
||||||
|
├── web_fetch → NewWebFetchTool()
|
||||||
|
├── message → NewMessageTool(bus)
|
||||||
|
├── send_file → NewSendFileTool(bus)
|
||||||
|
└── spawn → NewSpawnTool()
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 七、上下文构建与缓存
|
||||||
|
|
||||||
|
### 7.1 ContextBuilder(`pkg/agent/context.go`)
|
||||||
|
|
||||||
|
```
|
||||||
|
ContextBuilder
|
||||||
|
│
|
||||||
|
├── workspace → 工作目录
|
||||||
|
├── skillsLoader → 技能加载器
|
||||||
|
├── memory → 记忆存储
|
||||||
|
│
|
||||||
|
├── 系统提示词缓存:
|
||||||
|
│ ├── cachedSystemPrompt → 缓存的完整 System Prompt
|
||||||
|
│ ├── cachedAt → 缓存构建时各文件的 mtime
|
||||||
|
│ ├── existedAtCache → 缓存时存在的文件路径集合
|
||||||
|
│ └── skillFilesAtCache → 缓存时技能文件的 mtime 快照
|
||||||
|
│
|
||||||
|
├── BuildMessages():
|
||||||
|
│ ├── 构建 System Prompt(有缓存则复用)
|
||||||
|
│ │ ├── AGENT.md 内容
|
||||||
|
│ │ ├── SOUL.md 内容
|
||||||
|
│ │ ├── USER.md 内容
|
||||||
|
│ │ ├── 已激活技能描述
|
||||||
|
│ │ ├── 工具发现提示(BM25/Regex)
|
||||||
|
│ │ ├── 动态上下文(当前时间、会话信息)
|
||||||
|
│ │ └── 记忆上下文
|
||||||
|
│ │
|
||||||
|
│ ├── 拼接 System Message
|
||||||
|
│ ├── 拼接对话历史(如有摘要则先放摘要)
|
||||||
|
│ └── 拼接当前用户消息(含媒体)
|
||||||
|
│
|
||||||
|
└── 缓存失效检测:
|
||||||
|
├── sourceFilesChanged() → 检查 mtime 变化
|
||||||
|
├── 检查文件新增/删除
|
||||||
|
└── 检查技能文件变化
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 八、Hook 与事件系统
|
||||||
|
|
||||||
|
### 8.1 事件类型
|
||||||
|
|
||||||
|
```
|
||||||
|
EventBus
|
||||||
|
│
|
||||||
|
├── EventKindTurnStart → Turn 开始
|
||||||
|
├── EventKindTurnEnd → Turn 结束
|
||||||
|
├── EventKindToolCall → 工具调用
|
||||||
|
├── EventKindToolResult → 工具结果
|
||||||
|
├── EventKindLLMRequest → LLM 请求
|
||||||
|
├── EventKindLLMResponse → LLM 响应
|
||||||
|
├── EventKindSteeringInjected → Steering 消息注入
|
||||||
|
├── EventKindContextCompress → 上下文压缩
|
||||||
|
└── EventKindError → 错误
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.2 Hook 类型
|
||||||
|
|
||||||
|
```
|
||||||
|
HookRegistration
|
||||||
|
│
|
||||||
|
├── Observer Hook(观察者)
|
||||||
|
│ └── 只读回调,不影响执行流程
|
||||||
|
│
|
||||||
|
├── Interceptor Hook(拦截器)
|
||||||
|
│ └── 可修改消息内容
|
||||||
|
│
|
||||||
|
└── Approval Hook(审批)
|
||||||
|
└── 可阻断执行,等待人工审批
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 九、SubTurn 并发子 Agent
|
||||||
|
|
||||||
|
### 9.1 SubTurn 流程
|
||||||
|
|
||||||
|
```
|
||||||
|
spawn 工具被调用
|
||||||
|
│
|
||||||
|
├── 创建子 turnState
|
||||||
|
│ ├── depth = parent.depth + 1
|
||||||
|
│ ├── parentTurnState = 当前 Turn
|
||||||
|
│ ├── pendingResults channel
|
||||||
|
│ └── concurrencySem 信号量(控制并发数)
|
||||||
|
│
|
||||||
|
├── goroutine 启动子 Turn
|
||||||
|
│ └── runTurn(ctx, childTurnState)
|
||||||
|
│ └── 独立的 LLM 调用循环
|
||||||
|
│
|
||||||
|
├── 结果通过 pendingResults 传回父 Turn
|
||||||
|
│
|
||||||
|
└── 父 Turn 在每次迭代开始时轮询结果
|
||||||
|
└── select { case result := <-pendingResults }
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 十、会话与记忆管理
|
||||||
|
|
||||||
|
### 10.1 会话存储(JSONL)
|
||||||
|
|
||||||
|
```
|
||||||
|
SessionStore (JSONL 后端)
|
||||||
|
│
|
||||||
|
├── AddMessage(sessionKey, role, content)
|
||||||
|
├── AddFullMessage(sessionKey, message) → 含媒体的消息
|
||||||
|
├── GetHistory(sessionKey) []Message
|
||||||
|
├── GetSummary(sessionKey) string
|
||||||
|
│
|
||||||
|
├── 上下文压缩:
|
||||||
|
│ ├── 检查触发条件:
|
||||||
|
│ │ ├── 消息数 > summarizeMessageThreshold
|
||||||
|
│ │ └── Token 使用 > contextWindow * summarizeTokenPercent%
|
||||||
|
│ ├── 调用 LLM 生成摘要
|
||||||
|
│ ├── 替换历史为摘要 + 最近消息
|
||||||
|
│ └── 发射 ContextCompress 事件
|
||||||
|
│
|
||||||
|
└── 主动压缩:
|
||||||
|
└── forceCompression() → 在 LLM 调用前检查预算
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 十一、配置管理
|
||||||
|
|
||||||
|
### 11.1 配置文件路径
|
||||||
|
|
||||||
|
```
|
||||||
|
~/.picoclaw/
|
||||||
|
├── config.json → 主配置文件
|
||||||
|
├── workspace/
|
||||||
|
│ ├── AGENT.md → Agent 定义
|
||||||
|
│ ├── SOUL.md → Agent 人格
|
||||||
|
│ └── USER.md → 用户偏好
|
||||||
|
├── skills/ → 全局技能目录
|
||||||
|
├── logs/ → 日志目录
|
||||||
|
└── .security.yml → 安全过滤配置
|
||||||
|
```
|
||||||
|
|
||||||
|
### 11.2 关键配置项
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"agents": {
|
||||||
|
"defaults": {
|
||||||
|
"model_name": "gpt-4o",
|
||||||
|
"max_tool_iterations": 20,
|
||||||
|
"max_tokens": 8192,
|
||||||
|
"context_window": 32768,
|
||||||
|
"steering_mode": "async",
|
||||||
|
"restrict_to_workspace": true
|
||||||
|
},
|
||||||
|
"list": [{ "id": "main", "workspace": "~/.picoclaw/workspace" }]
|
||||||
|
},
|
||||||
|
"model_list": [
|
||||||
|
{ "model_name": "gpt-4o", "model": "openai/gpt-4o", "api_key": "sk-..." }
|
||||||
|
],
|
||||||
|
"gateway": { "host": "127.0.0.1", "port": 18790 },
|
||||||
|
"routing": { "enabled": true, "light_model": "gpt-4o-mini" },
|
||||||
|
"tools": { "web": { "brave": { "enabled": true } }, "mcp": { "enabled": true } }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 十二、端到端消息流示例
|
||||||
|
|
||||||
|
以 Telegram 用户发送消息为例:
|
||||||
|
|
||||||
|
```
|
||||||
|
1. Telegram Bot API → telegram channel 收到 Update
|
||||||
|
↓
|
||||||
|
2. telegram 转换为 InboundMessage:
|
||||||
|
{Channel:"telegram", ChatID:"123", SenderID:"alice", Content:"今天天气怎样?"}
|
||||||
|
↓
|
||||||
|
3. ChannelManager → bus.PublishInbound()
|
||||||
|
↓
|
||||||
|
4. AgentLoop.Run() 从 bus.InboundChan() 收到消息
|
||||||
|
↓
|
||||||
|
5. processMessage()
|
||||||
|
├── transcribeAudioInMessage() → 无音频,跳过
|
||||||
|
├── resolveMessageRoute() → 路由到 "main" Agent
|
||||||
|
└── runAgentLoop()
|
||||||
|
↓
|
||||||
|
6. runTurn()
|
||||||
|
├── 加载会话历史
|
||||||
|
├── ContextBuilder.BuildMessages()
|
||||||
|
│ ├── 读取 AGENT.md + SOUL.md + USER.md
|
||||||
|
│ ├── 生成 System Prompt
|
||||||
|
│ └── 拼接历史 + 当前消息
|
||||||
|
↓
|
||||||
|
7. selectCandidates() → 选择主模型 gpt-4o
|
||||||
|
↓
|
||||||
|
8. provider.Chat(messages, toolDefs) → 调用 OpenAI API
|
||||||
|
│
|
||||||
|
返回: { toolCalls: [{name: "web_search", params: {query: "今天天气"}}] }
|
||||||
|
↓
|
||||||
|
9. 执行 web_search 工具 → 获取搜索结果
|
||||||
|
↓
|
||||||
|
10. 将工具结果添加到 messages → 再次调用 LLM
|
||||||
|
│
|
||||||
|
返回: { content: "根据搜索结果,今天北京晴,气温15°C..." }
|
||||||
|
↓
|
||||||
|
11. 保存 assistant 回复到会话
|
||||||
|
↓
|
||||||
|
12. runTurn() 返回 → runAgentLoop() 返回
|
||||||
|
↓
|
||||||
|
13. publishResponseIfNeeded()
|
||||||
|
└── bus.PublishOutbound({Channel:"telegram", ChatID:"123", Content:"..."})
|
||||||
|
↓
|
||||||
|
14. ChannelManager 分发给 telegram channelWorker
|
||||||
|
↓
|
||||||
|
15. telegram channel 发送回复消息给用户
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 十三、Docker 部署
|
||||||
|
|
||||||
|
### 13.1 三种运行模式
|
||||||
|
|
||||||
|
| Profile | 命令 | 用途 |
|
||||||
|
|---------|------|------|
|
||||||
|
| `agent` | `docker compose run --rm picoclaw-agent -m "Hello"` | 单次查询 |
|
||||||
|
| `gateway` | `docker compose --profile gateway up` | 长期运行网关(无 WebUI) |
|
||||||
|
| `launcher` | `docker compose --profile launcher up` | 网关 + WebUI |
|
||||||
|
|
||||||
|
### 13.2 容器内流程
|
||||||
|
|
||||||
|
```
|
||||||
|
entrypoint.sh
|
||||||
|
│
|
||||||
|
├── 检查 ~/.picoclaw/ 是否存在
|
||||||
|
│ └── 不存在 → picoclaw onboard(自动初始化)
|
||||||
|
│
|
||||||
|
├── 检查 config.json 是否存在
|
||||||
|
│ └── 不存在 → 从环境变量生成配置
|
||||||
|
│
|
||||||
|
└── 执行传入的命令
|
||||||
|
├── picoclaw gateway → 启动网关
|
||||||
|
└── picoclaw agent -m "..." → 单次对话
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 十四、关键设计亮点
|
||||||
|
|
||||||
|
1. **系统提示词缓存**: 通过 mtime 检测文件变化,避免每次请求重建完整 System Prompt
|
||||||
|
2. **JSONL 会话存储**: 比 JSON 更高效的日志式存储,适合追加写入
|
||||||
|
3. **Steering 消息注入**: 允许在 Turn 执行过程中动态注入新消息,实现对话中续
|
||||||
|
4. **SubTurn 并发**: 通过信号量控制并发子 Agent 数量,结果异步传回父 Turn
|
||||||
|
5. **Fallback 降级链**: 自动分类错误原因,智能切换备用 Provider
|
||||||
|
6. **模型路由**: 根据消息复杂度动态选择轻量/重量模型,优化成本
|
||||||
|
7. **插件化架构**: Channel 和 Provider 均通过接口实现,支持热插拔
|
||||||
|
8. **配置热重载**: 支持不重启服务更新配置
|
||||||
Loading…
Add table
Reference in a new issue