2026-03-09 17:38:13 +08:00
|
|
|
|
# LaodingBot 技术说明文档(2026-03-09)
|
2026-02-28 17:48:33 +08:00
|
|
|
|
|
2026-03-09 17:38:13 +08:00
|
|
|
|
> 本文档基于当前代码状态(含本次“文档问答 + 模型切换”改造)整理,描述真实可运行架构、能力边界与配置方式。
|
2026-02-28 17:48:33 +08:00
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 1. 项目定位
|
|
|
|
|
|
|
2026-03-09 17:38:13 +08:00
|
|
|
|
LaodingBot 当前架构为:
|
|
|
|
|
|
- 父进程 Agent 编排(技能路由 + 统一 ReAct + 记忆)
|
|
|
|
|
|
- 子进程 ToolHost 执行(JSON-RPC)
|
|
|
|
|
|
- runtime workspace 隔离(配置、数据、技能、工具权限收敛)
|
|
|
|
|
|
- 能力缺口闭环(落库、聚类、自动生成技能、热加载)
|
|
|
|
|
|
- 文档问答链路(飞书文件下载 -> 上传 LLM -> 缓存 file_id -> 下一轮文本问答使用)
|
2026-02-28 17:48:33 +08:00
|
|
|
|
|
2026-03-09 17:38:13 +08:00
|
|
|
|
核心目标:在安全边界内持续扩展能力,并兼容文本问答与文档长上下文问答的混合场景。
|
2026-02-28 17:48:33 +08:00
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
2026-03-09 17:38:13 +08:00
|
|
|
|
## 2. 关键模块
|
2026-02-28 17:48:33 +08:00
|
|
|
|
|
2026-03-09 17:38:13 +08:00
|
|
|
|
- `cmd/bot/main.go`:应用入口、workspace 引导、toolhost 启动、消息通道分发
|
|
|
|
|
|
- `internal/config/config.go`:配置加载、workspace 解析、安全策略归一化、LLM 模型策略
|
|
|
|
|
|
- `internal/runtimews/bootstrap.go`:runtime workspace 初始化与种子复制
|
|
|
|
|
|
- `internal/agent/orchestrator.go`:主编排(路由、ReAct、文件上下文、能力缺口)
|
|
|
|
|
|
- `internal/llm/client.go`:OpenAI 兼容客户端(聊天、文件上传、文件注入模式)
|
|
|
|
|
|
- `internal/transport/feishu/bot.go`:飞书事件接入、文件下载与本地落盘
|
|
|
|
|
|
- `internal/toolhost/*`:工具子进程协议、客户端/服务端、远程工具适配
|
|
|
|
|
|
- `internal/memory/store_sqlite.go`:消息与能力缺口存储
|
|
|
|
|
|
- `internal/knowledge/*`:soul/skills 加载与技能草稿生成
|
2026-02-28 17:48:33 +08:00
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
2026-03-09 17:38:13 +08:00
|
|
|
|
## 3. 启动链路
|
2026-02-28 17:48:33 +08:00
|
|
|
|
|
|
|
|
|
|
`main()` 执行顺序:
|
2026-03-09 17:38:13 +08:00
|
|
|
|
1. 创建 SIGINT/SIGTERM 可取消上下文。
|
2026-02-28 17:48:33 +08:00
|
|
|
|
2. 调用 `runtimews.PrepareFromEnv()`:
|
|
|
|
|
|
- 解析 `AGENT_WORKSPACE_DIR`(默认 `./workspace/agent_runtime`)
|
2026-03-09 17:38:13 +08:00
|
|
|
|
- 复制 `configs/data/skills/bot_context` 种子到 runtime workspace(缺失才复制)
|
|
|
|
|
|
- 注入 `CONFIG_ENV_FILE=<workspace>/configs/env`
|
|
|
|
|
|
3. 调用 `config.Load()`,按优先级读取 env。
|
|
|
|
|
|
4. `--toolhost` 模式下进入子进程工具服务。
|
|
|
|
|
|
5. 父进程初始化日志、SQLite、ToolHost Client、知识、Orchestrator。
|
|
|
|
|
|
6. 按 `MESSAGE_CHANNEL` 启动 Telegram 或 Feishu。
|
2026-02-28 17:48:33 +08:00
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
2026-03-09 17:38:13 +08:00
|
|
|
|
## 4. 配置优先级与关键配置
|
2026-02-28 17:48:33 +08:00
|
|
|
|
|
2026-03-09 17:38:13 +08:00
|
|
|
|
`config.Load()` 的 env 优先级:
|
2026-02-28 17:48:33 +08:00
|
|
|
|
1. `CONFIG_ENV_FILE`(强覆盖)
|
2026-03-09 17:38:13 +08:00
|
|
|
|
2. `<workspace>/configs/env`、`<workspace>/.env`(强覆盖)
|
|
|
|
|
|
3. 根目录 `configs/env`、`.env`(仅兜底)
|
2026-02-28 17:48:33 +08:00
|
|
|
|
|
2026-03-09 17:38:13 +08:00
|
|
|
|
关键配置:
|
|
|
|
|
|
- `REACT_MAX_STEPS`:必须配置(1~8)
|
|
|
|
|
|
- `AGENT_WORKSPACE_DIR`:运行空间根目录
|
2026-02-28 17:48:33 +08:00
|
|
|
|
- `ALLOWED_DIRS` / `ALLOWED_COMMANDS` / `WORK_DIR`:工具安全边界
|
2026-03-09 17:38:13 +08:00
|
|
|
|
- `AUTO_SKILL_DIR`:自动生成 skill 目录
|
|
|
|
|
|
- `GAP_DRAFT_TRIGGER_COUNT` / `GAP_CLUSTER_LOOKBACK_HOURS`:缺口聚类参数
|
|
|
|
|
|
|
|
|
|
|
|
### 新增(文档问答)
|
|
|
|
|
|
- `LLM_MODEL`:常规文本问答模型(路由 + ReAct 默认模型)
|
|
|
|
|
|
- `LLM_FILE_MODEL`:携带 `file_id` 时使用的模型(未设置时回退 `LLM_MODEL`)
|
|
|
|
|
|
- `LLM_FILE_PROMPT_MODE`:文件注入模式
|
|
|
|
|
|
- `user_content_file_parts`(默认):`messages.user.content=[{type:text},{type:file,file_id}]`
|
|
|
|
|
|
- `system_fileid_uri`:按 provider 要求注入 `system: fileid://<id>`
|
|
|
|
|
|
|
|
|
|
|
|
说明:`LLM_FILE_PROMPT_MODE=system_fileid_uri` 可对齐 Qwen/DashScope 兼容模式中常见的 `fileid://` 用法。
|
2026-02-28 17:48:33 +08:00
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 5. workspace 隔离策略
|
|
|
|
|
|
|
2026-03-09 17:38:13 +08:00
|
|
|
|
当前实现中,Agent 与工具默认都在 workspace 内运行:
|
|
|
|
|
|
- 相对路径按 `AGENT_WORKSPACE_DIR` 解析
|
2026-02-28 17:48:33 +08:00
|
|
|
|
- `ALLOWED_DIRS` 强制补齐:
|
|
|
|
|
|
- workspace 根
|
|
|
|
|
|
- `workspace/skills`
|
|
|
|
|
|
- `workspace/data`
|
|
|
|
|
|
- `workspace/workspace`
|
|
|
|
|
|
- `ALLOWED_COMMANDS` 自动补齐:`go`、`curl`、`curl.exe`
|
|
|
|
|
|
|
2026-03-09 17:38:13 +08:00
|
|
|
|
`filetool` 对相对路径优先解析到 workspace 根,避免写到仓库根。
|
2026-02-28 17:48:33 +08:00
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 6. ToolHost 子进程架构
|
|
|
|
|
|
|
2026-03-09 17:38:13 +08:00
|
|
|
|
工具调用通过 JSON-RPC 子进程完成:
|
|
|
|
|
|
- 协议:`ping`、`tool.list`、`tool.call`
|
|
|
|
|
|
- 父进程客户端能力:
|
2026-02-28 17:48:33 +08:00
|
|
|
|
- 调用超时
|
|
|
|
|
|
- 心跳检测
|
|
|
|
|
|
- 失败重启与重试
|
|
|
|
|
|
- 并发限制(信号量)
|
2026-03-09 17:38:13 +08:00
|
|
|
|
- 子进程 stdout 仅输出协议内容,避免日志污染
|
2026-02-28 17:48:33 +08:00
|
|
|
|
|
2026-03-09 17:38:13 +08:00
|
|
|
|
结果:工具崩溃不会直接拖垮 Agent 主编排。
|
2026-02-28 17:48:33 +08:00
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
2026-03-09 17:38:13 +08:00
|
|
|
|
## 7. 文本问答主流程(统一 ReAct)
|
2026-02-28 17:48:33 +08:00
|
|
|
|
|
2026-03-09 17:38:13 +08:00
|
|
|
|
`Orchestrator.HandleMessage*()` 流程:
|
|
|
|
|
|
1. 保存用户消息
|
|
|
|
|
|
2. 加载最近消息并压缩
|
|
|
|
|
|
3. 执行能力路由(Router)
|
|
|
|
|
|
4. 进入统一 ReAct 循环
|
|
|
|
|
|
5. 按决策调用工具并将 Observation 写入 scratchpad
|
|
|
|
|
|
6. 直到 `is_final_answer=true`
|
2026-02-28 17:48:33 +08:00
|
|
|
|
7. 保存 assistant 回复
|
|
|
|
|
|
|
2026-03-09 17:38:13 +08:00
|
|
|
|
注:当前循环有固定安全上限 20 步(代码内硬上限)。
|
2026-02-28 17:48:33 +08:00
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
2026-03-09 17:38:13 +08:00
|
|
|
|
## 8. 文档问答链路(飞书)
|
|
|
|
|
|
|
|
|
|
|
|
### 8.1 接收与下载
|
|
|
|
|
|
`internal/transport/feishu/bot.go` 在 `msg_type=file` 时:
|
|
|
|
|
|
1. 从事件中解析 `file_key`、`file_name`
|
|
|
|
|
|
2. 调用飞书 `message resource` 下载二进制
|
|
|
|
|
|
3. 校验大小(默认上限 20MB)
|
|
|
|
|
|
4. 保存到本地 `files/` 目录
|
|
|
|
|
|
5. 组装 `IncomingMessage{FileBytes, FileMime, FilePath}` 交给主流程
|
|
|
|
|
|
|
|
|
|
|
|
### 8.2 上传与缓存 file_id
|
|
|
|
|
|
`cmd/bot/main.go` 在 Feishu 文件消息分支:
|
|
|
|
|
|
- 调用 `engine.HandleMessageWithFiles(..., text="", files=[...])`
|
|
|
|
|
|
- Orchestrator 识别为 `isFileOnly`:
|
|
|
|
|
|
- 上传文件到 LLM(`UploadFile(..., purpose=file-extract)`)
|
|
|
|
|
|
- 缓存 `pendingFiles[chat_id::user_id]`
|
|
|
|
|
|
- 回复“文件上传完成,等待下一次提问”
|
|
|
|
|
|
|
|
|
|
|
|
### 8.3 下一轮文本提问使用 file_id
|
|
|
|
|
|
当用户随后发送文本:
|
|
|
|
|
|
- Orchestrator 取出 `pendingFiles`
|
|
|
|
|
|
- 构建 `fileCtx.FileIDs`
|
|
|
|
|
|
- 在 ReAct 内通过 `generateWithOptionalFiles()` 调用 LLM
|
|
|
|
|
|
- 回答成功后清空该用户待消费文件缓存
|
|
|
|
|
|
|
|
|
|
|
|
该行为与需求一致:文件上传与提问分离,且 `file_id` 在下一轮问答自动注入。
|
2026-02-28 17:48:33 +08:00
|
|
|
|
|
2026-03-09 17:38:13 +08:00
|
|
|
|
---
|
2026-02-28 17:48:33 +08:00
|
|
|
|
|
2026-03-09 17:38:13 +08:00
|
|
|
|
## 9. LLM 文件能力实现细节
|
2026-02-28 17:48:33 +08:00
|
|
|
|
|
2026-03-09 17:38:13 +08:00
|
|
|
|
`internal/llm/client.go` 当前能力:
|
2026-02-28 17:48:33 +08:00
|
|
|
|
|
2026-03-09 17:38:13 +08:00
|
|
|
|
### 9.1 文件上传
|
|
|
|
|
|
- 接口:`POST /files`
|
|
|
|
|
|
- multipart 字段:`purpose` + `file`
|
|
|
|
|
|
- 目的值尝试顺序:调用方指定 -> `file-extract` -> `batch`
|
|
|
|
|
|
- 兼容返回:`id` 或 `data.id`
|
2026-02-28 17:48:33 +08:00
|
|
|
|
|
2026-03-09 17:38:13 +08:00
|
|
|
|
### 9.2 模型切换策略
|
|
|
|
|
|
- 无文件:使用 `LLM_MODEL`
|
|
|
|
|
|
- 有文件:优先使用 `LLM_FILE_MODEL`
|
2026-02-28 17:48:33 +08:00
|
|
|
|
|
2026-03-09 17:38:13 +08:00
|
|
|
|
### 9.3 文件注入策略
|
|
|
|
|
|
- `user_content_file_parts`:`user.content` 使用 text/file parts
|
|
|
|
|
|
- `system_fileid_uri`:将每个 `file_id` 注入为一条 `system: fileid://<id>` 消息
|
2026-02-28 17:48:33 +08:00
|
|
|
|
|
2026-03-09 17:38:13 +08:00
|
|
|
|
这使同一套 ReAct 编排可适配不同 provider 的文件上下文协议差异。
|
2026-02-28 17:48:33 +08:00
|
|
|
|
|
2026-03-09 17:38:13 +08:00
|
|
|
|
---
|
2026-02-28 17:48:33 +08:00
|
|
|
|
|
2026-03-09 17:38:13 +08:00
|
|
|
|
## 10. 为什么不会破坏 ReAct / tools / skills
|
2026-02-28 17:48:33 +08:00
|
|
|
|
|
2026-03-09 17:38:13 +08:00
|
|
|
|
本次改造仅发生在 LLM I/O 层,不改变编排核心:
|
|
|
|
|
|
- ReAct 决策协议(JSON 输出格式)不变
|
|
|
|
|
|
- ToolHost、工具注册与调用链路不变
|
|
|
|
|
|
- 技能加载、路由与能力缺口闭环不变
|
|
|
|
|
|
- 仅在 `GenerateWithFiles()` 分支切换模型与消息格式
|
2026-02-28 17:48:33 +08:00
|
|
|
|
|
2026-03-09 17:38:13 +08:00
|
|
|
|
因此:文本问答仍走原有路径,文档问答只在“带 file_id 的 LLM 调用”处差异化。
|
2026-02-28 17:48:33 +08:00
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 11. 数据存储
|
|
|
|
|
|
|
|
|
|
|
|
SQLite 表:
|
2026-03-09 17:38:13 +08:00
|
|
|
|
1. `messages`:用户与 assistant 对话
|
2026-02-28 17:48:33 +08:00
|
|
|
|
2. `capability_gaps`:能力缺口事件
|
|
|
|
|
|
|
2026-03-09 17:38:13 +08:00
|
|
|
|
支持查询:最近消息、最近缺口、高频缺口聚类。
|
2026-02-28 17:48:33 +08:00
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
2026-03-09 17:38:13 +08:00
|
|
|
|
## 12. 与旧文档相比的更新点
|
|
|
|
|
|
|
|
|
|
|
|
已补充并对齐代码现状:
|
|
|
|
|
|
- 飞书文件事件下载与本地保存流程
|
|
|
|
|
|
- 文件上传到 LLM 并缓存 `file_id` 的两阶段问答流程
|
|
|
|
|
|
- 双模型配置:`LLM_MODEL` + `LLM_FILE_MODEL`
|
|
|
|
|
|
- 文件注入模式:`LLM_FILE_PROMPT_MODE`
|
|
|
|
|
|
- “不影响 ReAct/tools/skills”的边界说明
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 13. 推荐配置(Qwen Long 场景)
|
|
|
|
|
|
|
|
|
|
|
|
示例:
|
|
|
|
|
|
|
|
|
|
|
|
```env
|
|
|
|
|
|
LLM_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1
|
|
|
|
|
|
LLM_API_KEY=<your_key>
|
2026-02-28 17:48:33 +08:00
|
|
|
|
|
2026-03-09 17:38:13 +08:00
|
|
|
|
LLM_MODEL=qwen-plus
|
|
|
|
|
|
LLM_FILE_MODEL=qwen-long
|
|
|
|
|
|
LLM_FILE_PROMPT_MODE=system_fileid_uri
|
|
|
|
|
|
```
|
2026-02-28 17:48:33 +08:00
|
|
|
|
|
2026-03-09 17:38:13 +08:00
|
|
|
|
解释:
|
|
|
|
|
|
- 普通对话用 `qwen-plus`
|
|
|
|
|
|
- 文档问答自动切到 `qwen-long`
|
|
|
|
|
|
- 文件上下文注入采用 `fileid://` 形式
|
2026-02-28 17:48:33 +08:00
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
2026-03-09 17:38:13 +08:00
|
|
|
|
## 14. 后续建议
|
2026-02-28 17:48:33 +08:00
|
|
|
|
|
2026-03-09 17:38:13 +08:00
|
|
|
|
1. 为 `internal/llm/client.go` 增加表驱动单测,覆盖两种 `LLM_FILE_PROMPT_MODE`。
|
|
|
|
|
|
2. 在能力路由中加入“文档问答意图”标记,优化带文件时的提示词压缩策略。
|
|
|
|
|
|
3. 为 pending file_id 增加 TTL 清理,避免长期未消费堆积。
|
|
|
|
|
|
4. 增加 e2e 用例:飞书文件消息 -> 下一轮提问 -> 产出稳定答案。
|