7.8 KiB
7.8 KiB
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() 执行顺序:
- 创建 SIGINT/SIGTERM 可取消上下文。
- 调用
runtimews.PrepareFromEnv():- 解析
AGENT_WORKSPACE_DIR(默认./workspace/agent_runtime) - 复制
configs/data/skills/bot_context种子到 runtime workspace(缺失才复制) - 注入
CONFIG_ENV_FILE=<workspace>/configs/env
- 解析
- 调用
config.Load(),按优先级读取 env。 --toolhost模式下进入子进程工具服务。- 父进程初始化日志、SQLite、ToolHost Client、知识、Orchestrator。
- 按
MESSAGE_CHANNEL启动 Telegram 或 Feishu。
4. 配置优先级与关键配置
config.Load() 的 env 优先级:
CONFIG_ENV_FILE(强覆盖)<workspace>/configs/env、<workspace>/.env(强覆盖)- 根目录
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/skillsworkspace/dataworkspace/workspace
ALLOWED_COMMANDS自动补齐:go、curl、curl.exe
filetool 对相对路径优先解析到 workspace 根,避免写到仓库根。
6. ToolHost 子进程架构
工具调用通过 JSON-RPC 子进程完成:
- 协议:
ping、tool.list、tool.call - 父进程客户端能力:
- 调用超时
- 心跳检测
- 失败重启与重试
- 并发限制(信号量)
- 子进程 stdout 仅输出协议内容,避免日志污染
结果:工具崩溃不会直接拖垮 Agent 主编排。
7. 文本问答主流程(统一 ReAct)
Orchestrator.HandleMessage*() 流程:
- 保存用户消息
- 加载最近消息并压缩
- 执行能力路由(Router)
- 进入统一 ReAct 循环
- 按决策调用工具并将 Observation 写入 scratchpad
- 直到
is_final_answer=true - 保存 assistant 回复
注:当前循环有固定安全上限 20 步(代码内硬上限)。
8. 文档问答链路(飞书)
8.1 接收与下载
internal/transport/feishu/bot.go 在 msg_type=file 时:
- 从事件中解析
file_key、file_name - 调用飞书
message resource下载二进制 - 校验大小(默认上限 20MB)
- 保存到本地
files/目录 - 组装
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] - 回复“文件上传完成,等待下一次提问”
- 上传文件到 LLM(
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 partssystem_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 表:
messages:用户与 assistant 对话capability_gaps:能力缺口事件
支持查询:最近消息、最近缺口、高频缺口聚类。
12. 与旧文档相比的更新点
已补充并对齐代码现状:
- 飞书文件事件下载与本地保存流程
- 文件上传到 LLM 并缓存
file_id的两阶段问答流程 - 双模型配置:
LLM_MODEL+LLM_FILE_MODEL - 文件注入模式:
LLM_FILE_PROMPT_MODE - “不影响 ReAct/tools/skills”的边界说明
13. 推荐配置(Qwen Long 场景)
示例:
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. 后续建议
- 为
internal/llm/client.go增加表驱动单测,覆盖两种LLM_FILE_PROMPT_MODE。 - 在能力路由中加入“文档问答意图”标记,优化带文件时的提示词压缩策略。
- 为 pending file_id 增加 TTL 清理,避免长期未消费堆积。
- 增加 e2e 用例:飞书文件消息 -> 下一轮提问 -> 产出稳定答案。