Files
wehub-resource-sync 1443d3fdf9
Ruff Format Check / Ruff Format & Lint (push) Failing after 7m39s
Deploy VitePress site to Pages / build (push) Failing after 9m11s
Deploy VitePress site to Pages / Deploy (push) Waiting to run
chore: import upstream snapshot with attribution
2026-07-13 12:32:26 +08:00

8.9 KiB
Raw Permalink Blame History

中间件系统

中间件是 Yuxi 扩展智能体运行行为的主要机制。它工作在 LangGraph Agent 的模型调用、工具调用、状态更新和文件系统访问路径上,用来把知识库、Skills、附件、子智能体、上下文压缩和运行观测接入同一条执行链路。

内置 ChatbotAgentSubAgentBackend 都会在 get_graph() 中构建中间件列表。运行前的资源过滤不再依赖旧版运行时配置中间件,而是在创建 Graph 前由 prepare_agent_runtime_context 完成。

运行时准备

运行时准备不是中间件,但它决定后续中间件能看到什么资源。内置 Agent 创建 Graph 前会先执行以下步骤:

  • prepare_agent_runtime_context:按当前用户权限过滤工具、知识库、MCP、Skills 和子智能体,并派生 _visible_knowledge_bases_prompt_skills_readable_skills
  • build_prompt_with_context:基于 Context 生成系统提示词
  • load_chat_model(context.model):加载主模型
  • resolve_configured_runtime_tools(context):加载已配置的内置工具和 MCP 工具

这意味着中间件不负责重新判断“用户是否能访问某个资源”。它们消费的是已经归一化后的 runtime context。

内置中间件链路

当前内置 ChatbotAgent 的中间件顺序如下:

中间件 作用
create_agent_filesystem_middleware 接入沙盒文件系统、用户工作区、线程 uploads/outputs 与只读 Skills 路由,并在工具结果过大时把内容写入 outputs/large_tool_results
save_attachments_to_fs / AttachmentMiddleware 从 LangGraph state 的 uploads 读取附件路径,把可读路径注入系统提示,提示模型按需使用 read_file
SkillsMiddleware 注入可见 Skill 的提示段,监听读取 SKILL.md 后的 Skill 激活,并按依赖追加工具和 MCP 工具;知识库工具由内置 knowledge-base Skill 按需加载
YuxiSubAgentMiddleware 仅主 Agent 在存在可见子智能体时挂载,提供 task 工具调用真实子 Agent graph
YuxiSummarizationMiddleware 基于 DeepAgents SummarizationMiddleware 做长上下文压缩,并清洗被摘要历史里的工具结果
TodoListMiddleware 提供待办状态,让前端状态面板可展示 Agent 运行进度
PatchToolCallsMiddleware 修正部分工具调用消息形态,提升工具调用兼容性
ModelRetryMiddleware 在模型调用失败时按配置重试
TokenUsageMiddleware 在 LangGraph state 写入本轮 token 使用快照,供前端状态面板查看

SubAgentBackend 使用同一组核心能力,但不会挂载 YuxiSubAgentMiddleware,并额外过滤 present_artifactsask_user_questioninstall_skill 等不适合子智能体直接使用的工具。

知识库工具

知识库访问能力沉淀为内置 knowledge-base Skill。Agent 读取 /home/gem/skills/knowledge-base/SKILL.md 激活该 Skill 后,SkillsMiddleware 会按依赖追加 list_kbsquery_kbfind_kb_documentopen_kb_documentget_mindmap 等知识库工具。

实际可见知识库仍由 prepare_agent_runtime_context 根据当前用户和 Agent 配置写入 _visible_knowledge_bases,工具执行时只会在这批知识库中检索。context.knowledges 是资源范围,不是 Skill 本身。

系统不会把知识库文件树挂进沙盒。Agent 访问知识库内容应使用 query_kbfind_kb_documentopen_kb_document,而不是遍历 /home/gem/kbs 这类旧路径。

Skills 注入与激活

SkillsMiddleware 分两步工作:

  1. 模型调用前读取 _prompt_skills,把可见 Skill 的名称、描述和 SKILL.md 路径追加到系统提示。
  2. 工具调用后检查模型是否读取了 /home/gem/skills/<slug>/SKILL.md。如果该 Skill 在 _readable_skills 范围内,就把它写入 activated_skills,并在后续模型调用中追加它声明的工具和 MCP 依赖。

这种设计让 Skill 可以先作为说明可见,只有模型真正读取并激活后才扩展工具集,避免一开始就把所有依赖工具塞进上下文。

附件与文件系统

附件上传后会先落盘到线程文件系统,并在 LangGraph state 中记录 uploadsAttachmentMiddleware 只把文件名和可读路径注入提示词,不会把文件内容整体塞进模型上下文。模型需要查看附件时,应通过 read_file 读取对应路径。

文件系统中间件负责把 sandbox backend、线程 uploads/outputs、用户工作区和只读 Skills 组合成 Agent 可访问的虚拟文件系统。普通 Agent 默认使用当前 thread_id 作为文件作用域;子智能体使用 child thread_id 做 checkpoint,同时沿用父线程的 uploads/outputs,并使用子 Agent 自己的 Skills 作用域。

子智能体任务

主 Agent 如果配置了可见子智能体,会挂载 YuxiSubAgentMiddleware 并获得 task 工具。这个工具不会调用旧版独立 SubAgents 表,而是查找 agents.is_subagent=true 且后端为 SubAgentBackend 的真实 Agent 配置,然后启动对应子 Agent graph。

子智能体执行时会获得独立 child thread、独立 checkpoint 和 agent_runs(run_type=subagent) 记录;工具结果会返回 child thread ID,后续可以把该 ID 传回 task 继续同一个子任务。子智能体自身不会再挂载下一层 task 中间件,避免形成嵌套子智能体链路。

Summary 上下文压缩

长对话压缩由 Yuxi 封装的 YuxiSummarizationMiddleware 负责。它基于 DeepAgents 的 SummarizationMiddleware,但针对 Yuxi 的知识库检索和工具调用结果做了额外处理。

触发条件来自 Agent Context

字段 说明
summary_threshold 上下文超过该 K token 阈值后触发摘要;L2 摘要模型的待摘要历史输入上限也使用同一阈值
summary_keep_messages 摘要后保留最近消息数
summary_prompt 摘要模型使用的提示词
summary_tool_result_token_limit 工具结果 offload 阈值和预览 token 上限
summary_l2_trigger_ratio L1 后进入 L2 summary 的触发比例,建议 0.1~1.0,默认 0.4

触发判断使用 Yuxi 自己的近似 token 计算结果,不使用模型返回的 usage_metadata.total_tokens 作为触发依据,避免 provider 的计费口径、累计口径或异常上报导致短对话过早压缩。

触发后,中间件先执行 L1 结构精简:在本次模型调用的临时消息视图里截断旧 write_file/edit_file 工具调用的大参数;ToolMessage.content 估算 token 数超过 summary_tool_result_token_limit 时,会写入当前 Agent 可见的 outputs/large_tool_results,消息内替换为工具名、近似 token 数、完整结果路径和不超过同一 token 上限的预览。未超过该上限的工具结果保持原样。这个步骤不修改 LangGraph state 中的原始消息。

L1 后会重新计算上下文大小;如果仍超过入口阈值乘以 summary_l2_trigger_ratio,才进入 L2 summary,把较早的 L1 视图消息压缩成一条 summary message,并保留最近窗口内的原始消息。比例越小越容易进入 L2;1.0 表示 L1 后仍超过原始触发阈值才进入 L2。L2 传给摘要模型的待摘要历史上限等于 summary_threshold 对应的 token 数,避免用过小的固定窗口丢掉早期关键信息。L2 不再对工具结果做第二轮 offload,只写入 _summarization_event,后续调用仍由 DeepAgents 的 cutoff 语义重建 effective messages。

这对知识库检索尤其重要:query_kbopen_kb_documentfind_kb_document 等工具可能返回较长的片段、引用和文档内容。Summary 阶段保留“查过什么、结果在哪里、关键预览是什么”,同时避免把大量检索原文反复卷入摘要,减少上下文污染和 token 压力。

未达到入口阈值的常规模型调用不会额外清洗工具结果;达到入口阈值但 L1 后低于 L2 门槛时,会直接用 L1 精简后的临时视图调用模型,不生成 summary event。

自定义中间件

新增中间件时,将实现放入 backend/package/yuxi/agents/middlewares,再在具体 Agent 的 get_graph() 中加入 middleware 列表。新增前先确认它属于哪一种职责:

  • 资源过滤、权限收敛和默认资源选择应放在 prepare_agent_runtime_context 一类的 Graph 创建前逻辑中。
  • 模型提示注入、工具动态追加、工具结果处理和 state 更新适合做成 LangChain Agent middleware。
  • 文件读写、工具结果卸载和 artifacts 展示应优先复用 create_agent_filesystem_middleware 与沙盒 backend。

仓库中仍保留 DynamicToolMiddleware,但当前内置 Agent 的工具和 MCP 加载已经由 resolve_configured_runtime_tools(context)SkillsMiddleware 承担。新增功能时不要默认复用旧的动态工具中间件,除非确实需要“预注册后按请求筛选”的模式。