Files
LaodingBot/doc/技术说明文档.md

229 lines
7.8 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.

# LaodingBot 技术说明文档2026-03-09
> 本文档基于当前代码状态(含本次“文档问答 + 模型切换”改造)整理,描述真实可运行架构、能力边界与配置方式。
---
## 1. 项目定位
LaodingBot 当前架构为:
- 父进程 Agent 编排(技能路由 + 统一 ReAct + 记忆)
- 子进程 ToolHost 执行JSON-RPC
- runtime workspace 隔离(配置、数据、技能、工具权限收敛)
- 能力缺口闭环(落库、聚类、自动生成技能、热加载)
- 文档问答链路(飞书文件下载 -> 上传 LLM -> 缓存 file_id -> 下一轮文本问答使用)
核心目标:在安全边界内持续扩展能力,并兼容文本问答与文档长上下文问答的混合场景。
---
## 2. 关键模块
- `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 加载与技能草稿生成
---
## 3. 启动链路
`main()` 执行顺序:
1. 创建 SIGINT/SIGTERM 可取消上下文。
2. 调用 `runtimews.PrepareFromEnv()`
- 解析 `AGENT_WORKSPACE_DIR`(默认 `./workspace/agent_runtime`
- 复制 `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。
---
## 4. 配置优先级与关键配置
`config.Load()` 的 env 优先级:
1. `CONFIG_ENV_FILE`(强覆盖)
2. `<workspace>/configs/env``<workspace>/.env`(强覆盖)
3. 根目录 `configs/env``.env`(仅兜底)
关键配置:
- `REACT_MAX_STEPS`必须配置1~8
- `AGENT_WORKSPACE_DIR`:运行空间根目录
- `ALLOWED_DIRS` / `ALLOWED_COMMANDS` / `WORK_DIR`:工具安全边界
- `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://` 用法。
---
## 5. workspace 隔离策略
当前实现中Agent 与工具默认都在 workspace 内运行:
- 相对路径按 `AGENT_WORKSPACE_DIR` 解析
- `ALLOWED_DIRS` 强制补齐:
- workspace 根
- `workspace/skills`
- `workspace/data`
- `workspace/workspace`
- `ALLOWED_COMMANDS` 自动补齐:`go``curl``curl.exe`
`filetool` 对相对路径优先解析到 workspace 根,避免写到仓库根。
---
## 6. ToolHost 子进程架构
工具调用通过 JSON-RPC 子进程完成:
- 协议:`ping``tool.list``tool.call`
- 父进程客户端能力:
- 调用超时
- 心跳检测
- 失败重启与重试
- 并发限制(信号量)
- 子进程 stdout 仅输出协议内容,避免日志污染
结果:工具崩溃不会直接拖垮 Agent 主编排。
---
## 7. 文本问答主流程(统一 ReAct
`Orchestrator.HandleMessage*()` 流程:
1. 保存用户消息
2. 加载最近消息并压缩
3. 执行能力路由Router
4. 进入统一 ReAct 循环
5. 按决策调用工具并将 Observation 写入 scratchpad
6. 直到 `is_final_answer=true`
7. 保存 assistant 回复
注:当前循环有固定安全上限 20 步(代码内硬上限)。
---
## 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` 在下一轮问答自动注入。
---
## 9. LLM 文件能力实现细节
`internal/llm/client.go` 当前能力:
### 9.1 文件上传
- 接口:`POST /files`
- multipart 字段:`purpose` + `file`
- 目的值尝试顺序:调用方指定 -> `file-extract` -> `batch`
- 兼容返回:`id``data.id`
### 9.2 模型切换策略
- 无文件:使用 `LLM_MODEL`
- 有文件:优先使用 `LLM_FILE_MODEL`
### 9.3 文件注入策略
- `user_content_file_parts``user.content` 使用 text/file parts
- `system_fileid_uri`:将每个 `file_id` 注入为一条 `system: fileid://<id>` 消息
这使同一套 ReAct 编排可适配不同 provider 的文件上下文协议差异。
---
## 10. 为什么不会破坏 ReAct / tools / skills
本次改造仅发生在 LLM I/O 层,不改变编排核心:
- ReAct 决策协议JSON 输出格式)不变
- ToolHost、工具注册与调用链路不变
- 技能加载、路由与能力缺口闭环不变
- 仅在 `GenerateWithFiles()` 分支切换模型与消息格式
因此:文本问答仍走原有路径,文档问答只在“带 file_id 的 LLM 调用”处差异化。
---
## 11. 数据存储
SQLite 表:
1. `messages`:用户与 assistant 对话
2. `capability_gaps`:能力缺口事件
支持查询:最近消息、最近缺口、高频缺口聚类。
---
## 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>
LLM_MODEL=qwen-plus
LLM_FILE_MODEL=qwen-long
LLM_FILE_PROMPT_MODE=system_fileid_uri
```
解释:
- 普通对话用 `qwen-plus`
- 文档问答自动切到 `qwen-long`
- 文件上下文注入采用 `fileid://` 形式
---
## 14. 后续建议
1.`internal/llm/client.go` 增加表驱动单测,覆盖两种 `LLM_FILE_PROMPT_MODE`
2. 在能力路由中加入“文档问答意图”标记,优化带文件时的提示词压缩策略。
3. 为 pending file_id 增加 TTL 清理,避免长期未消费堆积。
4. 增加 e2e 用例:飞书文件消息 -> 下一轮提问 -> 产出稳定答案。