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

7.8 KiB
Raw Blame History

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.goruntime workspace 初始化与种子复制
  • internal/agent/orchestrator.go主编排路由、ReAct、文件上下文、能力缺口
  • internal/llm/client.goOpenAI 兼容客户端(聊天、文件上传、文件注入模式)
  • 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 自动补齐:gocurlcurl.exe

filetool 对相对路径优先解析到 workspace 根,避免写到仓库根。


6. ToolHost 子进程架构

工具调用通过 JSON-RPC 子进程完成:

  • 协议:pingtool.listtool.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.gomsg_type=file 时:

  1. 从事件中解析 file_keyfile_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
    • 上传文件到 LLMUploadFile(..., 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
  • 兼容返回:iddata.id

9.2 模型切换策略

  • 无文件:使用 LLM_MODEL
  • 有文件:优先使用 LLM_FILE_MODEL

9.3 文件注入策略

  • user_content_file_partsuser.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 场景)

示例:

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 用例:飞书文件消息 -> 下一轮提问 -> 产出稳定答案。