中间件系统
中间件是 ARS 扩展智能体运行行为的主要机制。它工作在 LangGraph Agent 的模型调用、工具调用、状态更新和文件系统访问路径上,用来把知识库、Skills、子智能体、上下文压缩和运行观测接入同一条执行链路。
内置 ChatbotAgent 与 SubAgentBackend 都会在 get_graph() 中构建中间件列表。prepare_agent_runtime_context 在 Graph 创建前完成资源过滤,中间件随后消费已经归一化的运行时配置。
运行时准备
运行时准备发生在中间件装配之前,负责确定后续中间件可见的资源。内置 Agent 创建 Graph 前依次执行:
prepare_agent_runtime_context:按当前用户权限过滤工具、知识库、MCP、Skills 和子智能体,并派生_visible_knowledge_bases、_effective_skill_slugs与_runtime_skillsbuild_prompt_with_context:基于 Context 生成系统提示词load_chat_model(context.model):加载主模型resolve_configured_runtime_tools(context):加载已配置的内置/MCP 工具,并把可读 Skill 的本地依赖注册进静态 ToolNode;Skill MCP 依赖由 Middleware 在激活轮次动态绑定
中间件直接消费归一化后的 runtime context。资源授权和可见性过滤由前置准备阶段完成,产生副作用的工具仍需在执行边界校验具体目标。
内置中间件链路
当前内置 ChatbotAgent 的中间件顺序如下:
SubAgentBackend 使用同一组核心能力,但不会挂载 ArsSubAgentMiddleware,并额外过滤 present_artifacts、ask_user_question、install_skill 等不适合子智能体直接使用的工具。
知识库工具
知识库访问能力沉淀为内置 knowledge-base Skill。Agent 读取 /home/gem/skills/knowledge-base/SKILL.md 激活该 Skill 后,SkillsMiddleware 会按依赖追加 list_kbs、query_kb、find_kb_document、open_kb_document、get_mindmap、search_file 和 download_kb_file 七个工具。
prepare_agent_runtime_context 根据当前用户权限和 Agent 配置生成 _visible_knowledge_bases,工具执行范围受该集合限制。context.knowledges 只定义资源范围,Skill 激活状态由 SkillsMiddleware 单独维护。
知识库文件树不挂载到沙盒。Agent 通过知识库工具访问内容,/home/gem/kbs 等旧路径不属于当前接口。状态、权限、存储和检索边界见知识库机制详解。
Skills 注入与激活
SkillsMiddleware 支持渐进加载和显式预加载:
- 模型调用前读取
_effective_skill_slugs。普通 Skill 注入名称、描述和SKILL.md路径;preload_skills及其依赖闭包直接注入根级完整说明。 - 工具调用后检查模型是否读取了共享投影
/home/gem/skills/<slug>/SKILL.md或个人 UserWorkspace/home/gem/user-data/agents/skills/<slug>/SKILL.md。如果该 Skill 在_effective_skill_slugs范围内,就把它写入activated_skills,并在后续模型调用中追加它声明的工具和 MCP 依赖。
普通 Skill 在读取并激活后开放依赖;预加载 Skill 从首轮模型请求开始开放依赖。默认空配置保持初始工具 schema 不变。
附件与文件系统
附件确认后会落盘到当前 Workdir。每个新 Run 都把线程全部历史附件的文件名和实时路径追加到本轮模型可见的 HumanMessage,不会修改系统提示词,也不会把文件内容复制进模型上下文。数据库 Message、流式 init 事件和历史接口仍保留原始用户文本,因此前端不渲染这段模型专用上下文。模型需要查看附件时,应通过 read_file 读取对应路径;中断恢复沿用 checkpoint 中原有的本轮消息。
文件系统中间件通过 Sandbox 暴露当前用户的整个 UserWorkspace 与只读共享 Skills。普通 Agent 与子智能体使用根 Conversation 的同一个 runtime_scope_id 和 workdir_path;child thread_id 只隔离 LangGraph checkpoint。Workdir 选择 cwd,不形成同一 uid 内的文件隔离;各 Agent 的 Skill 选中列表只影响 Prompt 和工具激活。
子智能体任务
主 Agent 配置了可见子智能体时,会挂载 ArsSubAgentMiddleware 并获得 task 工具。该工具从 agents 表查找 is_subagent=true 且后端为 SubAgentBackend 的 Agent 配置,随后启动对应子 Agent graph。旧版独立 SubAgents 表不参与当前链路。
子智能体执行时会获得独立 child thread、独立 checkpoint 和 agent_runs(run_type=subagent) 记录;工具结果会返回 child thread ID,后续可以把该 ID 传回 task 继续同一个子任务。子智能体自身不会再挂载下一层 task 中间件,避免形成嵌套子智能体链路。
Summary 上下文压缩
ArsSummarizationMiddleware 在主模型调用前按近似上下文大小决定是否进入压缩:L1 只生成临时精简视图并卸载长工具结果,L2 才把较早历史写入文件、调用摘要模型并更新 checkpoint 中的 _summarization_event。它不删除 PostgreSQL 聊天消息,内部摘要模型的 token 也不进入当前主模型用量口径。
配置字段及默认值见智能体配置,完整触发条件、文件 Owner、流事件、恢复边界与测试入口见Summary 上下文压缩机制。中间件总览只维护装配位置与职责,不复制实现细节。
Token 用量统计
TokenUsageMiddleware 同时维护两种用途不同的口径:
- 近似上下文统计通过
count_tokens_approximately计算,用于上下文窗口、摘要阈值和消息构成展示。 - 实际用量直接保留主 Agent 模型调用返回的
AIMessage.usage_metadata,包括input_tokens、output_tokens、total_tokens、缓存和推理 token 明细。当前不包含 Summary 中间件内部直接发起的 L2 摘要模型调用,因此尚不能作为完整账单口径。
state 中的实际用量分为 latest、run 和 thread:分别表示最近一次模型调用、当前 AgentRun 累计和当前 LangGraph 线程累计。前端状态面板只读取 state;流式终态 chunk 不携带用量。worker 从当前父线程、current_run_id 匹配的 AgentState 提取 run,在 Run 进入终态时将该快照写入 AgentRun.token_usage。run.models 与 thread.models 使用 ARS provider_id:model_id 配置 spec 分桶,并记录 Provider 响应中的实际模型 ID,避免模型切换后把不同模型的 token 混为一组,也避免把 OpenAI 兼容协议类型误当作业务供应商。
每个模型桶独立计算缓存 token 命中率:使用 Provider 明确上报的 input_token_details.cache_read / input_tokens;OpenAI priority / flex service tier 对应识别 priority_cache_read / flex_cache_read。累计时先汇总该模型已上报缓存明细的输入 token,再计算 sum(cache_read) / sum(input_tokens);缺少缓存读取字段表示 Provider 未上报,不按 0 命中处理。当前 Run 的按模型聚合会在终态事务中写入 AgentRun.token_usage,父 Run 与 SubAgent Run 分别保存,不重复合并。
siliconflow-cn 与 siliconflow 当前返回的 usage 格式与统计契约不一致,暂列入 Token 用量 Provider 黑名单。黑名单模型仍计算近似上下文占用,但不写入最近调用、Run 或线程的 Provider 实际用量聚合。
Summary 触发使用近似上下文统计。Provider 返回的 usage_metadata.total_tokens 用于记录实际主模型用量。完整关系见Summary 上下文压缩机制。
自定义中间件
新增中间件时,将实现放入 backend/package/ars/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 承担。新增功能时不要默认复用旧的动态工具中间件,除非确实需要“预注册后按请求筛选”的模式。