Studio RuntimeAgent Core 四视图 →

AGENT STUDIO / RUNTIME ARCHITECTURE · 2026-09-17

Agent Runtime · 四视图

Agent Runtime 是将智能体或工作流配置实际运行起来的执行服务,负责接收请求、编排执行、调用模型与工具,并管理运行状态和结果输出。

四个架构视图从职责、代码、进程协作和部署形态解释 Agent Runtime。依据当前工作区源码与仓库部署模板整理。

逻辑视图

ARCHITECTURE

回答“系统提供哪些职责、这些职责如何协作”。每次分发选择一个顶层 Runner;模式框内展开主要组件,底部列出共享能力。

Agent Runtime 逻辑视图执行接口层接收 HTTP 请求并准备上下文,由编排层按模式选择一个顶层 Runner。Workflow 包含 IRConverter、LazyWorkflow / Workflow、会话与恢复及流包装;ReAct 包含 ReActAgent、工具与工作流能力、会话与流适配;Controller / PlanExecute 使用 IRConverter 构建分层 Agent Group,由成员 Agent 的控制或规划策略推进执行。各模式复用共享能力,通过事件适配输出结果。01 / LOGICAL VIEW三种执行模式,共用资源能力与输出协议提交执行包装结果流工作流ReActController / PlanExecuteHTTP执行接口层(HTTP 入口)接收请求 · 校验 · 准备执行上下文CORE请求编排与模式分发IR / context → ir_executeOUTPUT事件适配与输出EventHandler → SSE / JSONMODEWorkflowWorkflowRunnerIRConverterIR → LazyWorkflowLazyWorkflow / Workflow延迟构图 · 节点 / 分支 / 循环执行会话、恢复与流包装Session · Checkpoint · 事件包装MODEReActReActAgentRunnerReActAgentLLM · Prompt · 推理 / 工具循环工具与工作流能力Plugins · MCP · Skills · Workflow会话与流适配Session · ReactStreamDataAdapterMODEController / PlanExecuteControllerRunnerIRConverterIR → Agent GroupHierarchicalAgentGroup控制 Agent · 成员 Agent · 消息调度成员 Agent 的执行策略ControllerMode / PlanExecuteModeRESOURCES共享能力(4 组)模型接入LLM / model_service工具执行MCP / API / Skills / Code知识与记忆KB · 可选 LTM状态与恢复Redis / checkpoint依赖 / 协作关系内部主要组件 / 职责顶层 Runner 按请求择一 · 能力可复用

这张逻辑视图回答:Runtime 收到一次运行请求后,由谁准备任务、由谁推进执行、执行时使用哪些能力,以及如何把结果返回给调用方。可以先看上排的请求与输出,再看中排的三种执行方式,最后看底部共享能力。

位置内容负责的问题
上排执行接口、请求编排、事件输出如何接收、组织和交付一次执行。
中排Workflow、ReAct、Controller / PlanExecute具体用什么方式完成任务。
下排模型、工具、知识与记忆、状态与恢复执行过程中需要调用哪些资源。
  • 连线表示职责之间的依赖与协作。执行过程中会按需调用底部资源,并持续向输出层传递事件。
  • 每次请求选择一个顶层 Runner,内部能力可以组合复用。例如 ReAct 可以调用已注册的工作流。
  • 小框既包含具体类,也包含职责分组。它们的位置表达逻辑关系;源码归属看开发视图,进程边界看进程与部署视图。

执行接口层:把外部请求准备成可以执行的任务

左上角“执行接口层(HTTP 入口)”同时承担对外接入和执行准备。调用方传来的通常是面向业务的信息:

  • 要执行哪个 Agent 或 Workflow、哪个版本。
  • 用户本次输入,以及会话标识、已有对话消息。
  • 是否需要流式返回。

执行引擎还需要知道运行配置在哪里、历史上下文是什么、环境变量有哪些、这次执行属于哪个会话。因此,接口层会完成参数校验、版本与 IR 路径解析、历史消息加载、环境变量加载,以及请求上下文准备。

IR 是描述 Agent 或 Workflow 如何运行的结构化配置。它可以包含执行模式、节点、连接、模型和工具等信息,具体内容取决于运行类型。

经过这一层,“请运行这个工作流,输入是这段文字”就被准备为“使用这份确定的运行配置,带上这些输入、历史和环境信息,执行这个任务”。之后再交给 ir_execute()。

源码:agent-runtime/agent_runtime/serve/apis/app_run.py:535,673。

请求编排与模式分发:决定本次任务交给哪一种 Runner

中间这个框的核心职责,是组织一次执行,并选择它的顶层执行方式。它读取 IR 中的 configs.mode,按下面的规则分发:

IR 中的模式顶层 Runner主要执行方式
Workflow,默认工作流路径WorkflowRunner按工作流图推进。
ReActReActAgentRunner按模型判断与工具反馈推进。
Controller / PlanExecuteControllerRunner组织 Agent Group,由成员策略推进。

一次请求在这里选择一个顶层 Runner。选择依据是配置中的模式;任务执行的每一步不会重新进行顶层分发,Agent 数量也不是这里的选择条件。

这一层还处理跨模式的执行事务:整理输入和全局变量、按配置接入内容审核,以及在流式执行链路中登记执行、关联取消操作和收尾清理。

两种层次的“编排”

  • 请求编排:这次请求交给谁执行,如何组织上下文、输出和生命周期。
  • 业务执行编排:下一步运行哪个节点、调用什么工具、交给哪个 Agent。

图中上排主要负责请求编排;中排三个模式负责各自的业务执行编排。

源码:agent-runtime/agent_runtime/serve/apis/orchestration.py:133,153,225,339。

Workflow:把声明式配置变成可执行的图,再管理这次图执行

Workflow 框里的三个小框,分别覆盖构建执行对象、推进业务步骤、管理执行状态。

① IRConverter:把 IR 转成运行对象

工作流 IR 描述节点、参数、连接和分支等配置。实际执行需要对应的 Python 组件对象与工作流对象,IRConverter 负责完成两者之间的转换。

工作流配置 → 节点与连接的运行时表示 → 可执行工作流

当前实现中,ir_to_workflow() 首先返回一个 LazyWorkflow,完整构图延后到实际执行时。转换器实现在 Runtime 项目的 jiuwen 包中。放在 Workflow 框内,表示这条路径使用它;Controller 构建 Agent Group、ReAct 注册子工作流时也会复用它。

② LazyWorkflow / Workflow:推进节点、分支和循环

  • LazyWorkflow 在首次 invoke() 或 stream() 时触发完整构图。
  • 底层 Workflow 按照图结构执行节点、传递数据,并处理分支与循环。

同一个 LazyWorkflow 对象后续调用可以复用已经构建的图。复用范围是这个对象,不能直接推广为所有请求之间都共享同一张已构建的图。

例如,工作流预先定义“开始 → 知识检索 → 模型生成 → 条件判断 → 结束”,运行时就按这张图推进,由条件节点决定走哪条连接。节点本身可以包含模型、工具,甚至 Agent 能力。Workflow 的特征是由图结构组织执行。

③ 会话、恢复与流包装:让图执行可以持续管理

职责作用
Session为本次执行提供运行上下文。
Checkpoint在需要中断时保存可恢复状态。
流包装把工作流产生的执行片段整理为 Runtime 事件。

如果一个交互节点需要用户补充信息,执行过程是:

  1. 工作流运行到该节点,产生提问并保存中断状态。
  2. 当前请求把问题返回给调用方。
  3. 用户随后发起新请求,携带同一会话和补充输入。
  4. Runtime 检查可恢复状态,通过 InteractiveInput 等机制继续执行。

这是跨请求的恢复过程。是否恢复取决于有效检查点和执行状态;仅使用同一个会话标识,并不意味着一定从上次位置继续。上述职责由 WorkflowRunner 组织起来,执行输出再经过 WorkflowStreamDataWrapper 交给外层事件处理。

源码:agent-runtime/jiuwen/serve/controllers/execution/ir_converter.py:1591,1633;agent-runtime/jiuwen/extension/workflow/lazy_workflow.py:96,121,138;agent-runtime/agent_runtime/runner/workflow_runner.py:283,339,480,575,588。

ReAct:让模型根据当前信息和工具反馈决定下一步

ReAct 框的中心是 ReActAgent。它的执行通常呈现为一个循环:

模型读取任务与上下文 → 决定回答或调用工具
→ 执行工具 → 把结果交回模型 → 继续判断

Workflow 的推进主要依据预先定义的图,ReAct 的推进主要依据每轮模型输出和执行反馈。

小框主要内容
ReActAgent配置模型、提示词、迭代限制,推进模型与工具交互。
工具与工作流能力注册 Plugins、MCP、Skills、Workflow 等可调用能力。
会话与流适配创建 Session,通过 ReactStreamDataAdapter 转换执行事件。

注册能力和实际调用能力需要分开理解:注册了五个工具,表示它们可供使用;本次任务可能只调用其中一个,也可能直接回答。

顶层选择 ReAct,内部仍然可以调用工作流

例如,ReAct Agent 为完成用户任务,调用已经注册的“生成报表工作流”。整体仍由 ReActAgentRunner 驱动,只是在内部使用 Workflow 能力。当前注册子工作流的代码会调用 IRConverter,无需重新走一次顶层 Runner 分发。

因此,ReAct 可以理解为围绕一个主要决策 Agent 展开执行,但它能调用的能力可以相当复杂。

源码:agent-runtime/agent_runtime/runner/react_agent_runner.py:430,655,695,759,804,835。

Controller / PlanExecute:分清“选择成员”和“成员如何执行”

这个框有两层控制关系:Group 选择成员接手任务,成员再按自己的策略完成任务。

第一层:Agent Group 决定由哪个成员接手

IRConverter 根据配置创建或恢复 HierarchicalAgentGroup,组织成员 Agent 及其关系。

组内的 HierarchicalControlAgent 选择成员,通过 StandaloneRunner 发送消息,再根据成员执行结果决定继续、转交、结束或中断。多 Agent 在这里首先体现为成员之间的选择、消息调度与任务交接;当前这条控制链不能直接理解成所有 Agent 同时并行运行。

第二层:被选中的成员决定自己怎么完成任务

第三个小框“成员 Agent 的执行策略”描述这一层:

  • ControllerMode:围绕任务识别、工作流执行、交互与任务转交等控制逻辑推进。
  • PlanExecuteMode:围绕场景匹配、任务规划、逐步执行来推进。

例如,Group 选择“报表 Agent”接手任务;报表 Agent 使用 PlanExecute 策略,规划“取数、计算、生成报告”几个步骤,再逐步完成。“选报表 Agent”和“规划报表生成步骤”发生在不同层次。

图中列出了重点策略;代码中的策略工厂还支持其他模式,具体成员使用哪一种,取决于该成员配置的 plan_mode。

模式主要由什么决定下一步
Workflow工作流的节点、连接与条件。
ReAct模型当前判断与工具执行反馈。
Controller / PlanExecuteGroup 的成员调度,以及成员自己的控制或规划策略。

源码:agent-runtime/agent_runtime/runner/controller_runner.py:161,203,206;agent-runtime/jiuwen/multi_agent/agent_group/hierarchical_group/control_agent.py:53;agent-runtime/jiuwen/controller/agent/control_mode/control_factory.py:14。

共享能力:三种执行方式共同使用的资源

底部四个小框表示各执行模式按需要调用的能力:

共享能力提供什么典型使用位置
模型接入调用模型、处理模型配置与响应。Workflow 的模型节点、ReAct 循环、成员 Agent。
工具执行MCP、API、Skills、代码等能力。工具节点、模型发起的工具调用、计划中的执行步骤。
知识与记忆知识检索、可选长期记忆。为生成或决策补充信息。
状态与恢复Redis、检查点等状态设施。保存和恢复可中断的执行。

这些小框是逻辑能力分组,具体部署方式需要结合部署视图理解。各模式按需使用这些能力:长期记忆可以关闭,工具只有在被调用时才执行,沙箱按配置启用。

对话历史、长期记忆与检查点分别保存什么

  • 对话历史:之前说过什么。
  • 长期记忆:需要跨多次交互保留的有用信息。
  • 执行检查点:程序执行到了哪里,恢复时需要哪些状态。

前两者主要帮助理解和生成内容;检查点主要帮助继续执行。单纯保存聊天记录,通常不足以恢复一个中断的工作流。

源码:agent-runtime/agent_runtime/memory/adapter/ltm_manager.py:62;agent-runtime/agent_runtime/runner/fast_redis_checkpointer.py:168;模型接入实现位于 packages/model_service/。

事件适配与输出:把内部执行过程变成调用方能消费的结果

右上角这个框负责对外输出。这里实际上有两层转换:

底层执行片段 → 各 Runner 的事件包装或适配
→ EventHandler → 对外 SSE / JSON

第一层:各模式处理自己的底层事件

  • Workflow 使用 WorkflowStreamDataWrapper。
  • ReAct 使用 ReactStreamDataAdapter。
  • Controller 使用 ControllerStreamDataAdapter。

它们处理各自底层框架产生的数据,把节点、模型、工具等执行信息整理成 Runtime 可以处理的事件。

第二层:EventHandler 整理对外协议与结果

外层 EventHandler 完成事件字段转换、输出整理、结果聚合,以及结束后的对话历史保存等。

当前应用运行入口,即使调用方要求非流式 JSON,内部也会使用流式执行,再由 EventHandler 消费整个事件流,聚合为最终响应。

  • 流式返回:执行过程中持续向调用方发送事件。
  • 非流式返回:内部处理完事件流,再一次返回聚合结果。

源码:agent-runtime/agent_runtime/serve/apis/app_run.py:367,415;agent-runtime/agent_runtime/event_handler/event_handler.py:151,187,194。

完整任务示例:把一次 Workflow 执行对应到图中的各个框

假设已经配置了一个工作流:“接收问题 → 检索知识库 → 调用模型生成回答 → 返回结果”。它与逻辑视图的对应关系如下:

执行步骤对应图中位置发生的事情
1. 用户提交问题执行接口层校验请求,解析工作流版本,加载历史和环境。
2. 确定执行路径请求编排与模式分发读取 IR,选择 WorkflowRunner。
3. 准备可执行对象Workflow → IRConverter将 IR 转成 LazyWorkflow。
4. 启动实际执行Workflow → LazyWorkflow / Workflow首次构图,按节点与连接推进。
5. 检索资料共享能力 → 知识与记忆检索节点调用知识库能力。
6. 生成回答共享能力 → 模型接入模型节点使用问题与检索结果生成内容。
7. 整理执行事件Workflow → 会话、恢复与流包装把节点事件、模型输出等包装为运行事件。
8. 返回调用方事件适配与输出转成 SSE,或聚合成 JSON。

表格将职责拆开以便对照。实际流式执行时,事件包装和返回会随着节点执行持续发生。

如果中途加入“需要用户补充条件”的交互节点,就会同时用到 Workflow 的会话与恢复职责,以及底部的状态与恢复能力。用户补充输入后,再经过入口和分发层进入恢复流程。

看图时可以沿着具体任务追踪:这一步由哪个模式负责推进,调用了哪项共享能力,产生的事件又经过哪一层返回。

源码定位(相对 agent-studio)

agent-runtime/agent_runtime/serve/apis/app_run.py:673
agent-runtime/agent_runtime/serve/apis/orchestration.py:133
agent-runtime/agent_runtime/event_handler/event_handler.py:33
agent-runtime/agent_runtime/runner/workflow_runner.py:226,264,339,480,575,588
agent-runtime/jiuwen/extension/workflow/lazy_workflow.py:48,96,121,138
agent-runtime/agent_runtime/runner/workflow_stream_data_wrapper.py:252,321
agent-runtime/agent_runtime/runner/react_agent_runner.py:430,655,695,741,759,804,835
agent-runtime/agent_runtime/runner/controller_runner.py:70,86,161,203,206
agent-runtime/jiuwen/serve/controllers/execution/ir_converter.py:980,1535,1591,1633
agent-runtime/jiuwen/multi_agent/agent_group/hierarchical_group/agent_group.py:27
agent-runtime/jiuwen/multi_agent/agent_group/hierarchical_group/control_agent.py:23,58
agent-runtime/jiuwen/controller/agent/control_mode/control_factory.py:14
agent-runtime/agent_runtime/memory/adapter/ltm_manager.py:62

开发视图

DEPENDENCY GRAPH

展示代码归属与主要依赖。运行服务包内的小框表示主要代码模块,外框之间的箭头表示包级依赖;徽标仅统计图中包级入边。下方六点解读展开职责、调用关系、源码边界及省略的依赖。

Agent Runtime 开发视图运行服务包内展开 serve、runner、extension、event_handler、memory/context 五组模块;外框依赖 jiuwen、共享包及 openjiuwen SDK,jiuwen 的 IR 编译器又引用 runtime 节点扩展,构成包级依赖环。02 / DEVELOPMENT VIEW运行服务、构建服务、共享包与 SDK 的代码边界模型 / 日志 / 加密CYCLEPACKAGE1 in运行服务包agent-runtime/agent_runtime/serveHTTP 接入 / 校验 / 模式分发runnerWorkflow / ReAct / Controllerextension代码 / 知识检索 / 交互节点event_handler内部事件 → SSE / JSONmemory / context记忆能力 / 请求与会话上下文PACKAGE0 in独立构建服务agent_builder/构建 / 提示词 / 模型测试PACKAGE1 inIR 编译与多智能体控制agent-runtime/jiuwen/IRConverter · controller · groupSHARED2 in共享包(3 个)packages/model_service · storage · utilsSDK3 inopenjiuwen / agent-coregit dependency @ developIR 编译器回引runtime 节点扩展调用方 → 被依赖包橙色虚线:包级依赖环N in:图内入边数

这张图回答三个问题:代码放在哪里、哪些包依赖哪些包、修改一个功能会影响哪里。逻辑视图按职责分组,开发视图按代码归属分组。一个逻辑框可以由多个代码包共同实现;例如 Workflow 跨越了 agent_runtime、jiuwen 和 openjiuwen。

  • 箭头 A → B:A 使用或导入 B 中的代码,按依赖关系阅读。
  • 橙色虚线:突出一组包之间的双向依赖。
  • N in:只统计当前图中指向该框的箭头数量;它不是调用次数,也不是完整源码的依赖统计。

运行服务包:把一次请求组织成一次执行

左上角对应 agent-runtime/agent_runtime/,是 Runtime 面向业务请求的服务层。外框内的五个小框分别展示主要模块,其中 memory / context 合并两组相关能力;包级依赖箭头仍连接外框。它要确定执行目标、准备上下文、处理恢复输入、选择 Runner,并把执行结果交还调用方。

模块主要职责对应逻辑视图
serveHTTP 接入、请求校验、执行上下文准备、模式分发。执行接口层、请求编排。
runner组织 Workflow、ReAct、Controller 等执行方式。三个模式框的执行入口。
extension实现产品需要的具体节点能力。Workflow 内的节点扩展。
event_handler将内部运行事件转换成对外 SSE 或 JSON。事件适配与输出。
memory、context 等提供记忆与上下文相关能力。共享能力及执行上下文。

以 Workflow 为例:入口完成请求准备,_get_runner_by_type() 选择执行模式,WorkflowRunner 继续准备 IR、Session、检查点和执行输入。底层生成的 chunk 再经过运行事件包装与输出适配。

这个包的核心作用,是把底层执行能力组织成可对外提供的 Runtime 服务。HTTP 接口规则、运行上下文、模式选择和流式协议的修改,通常从这里入手。

源码:agent-runtime/agent_runtime/serve/apis/orchestration.py:133;agent-runtime/agent_runtime/runner/workflow_runner.py:226;agent-runtime/agent_runtime/event_handler/event_handler.py:228。

IR 编译与多智能体控制:把配置描述变成可执行对象

左中间对应 agent-runtime/jiuwen/。它包含 IR 转换、节点扩展、多智能体控制和 Agent Group 等实现。这里需要分清两个相近的名字:

名称在当前工程中的身份
jiuwen仓库内的实现代码,负责 IR 转换、产品适配和多智能体控制等。
openjiuwen依赖的底层 SDK,由 agent-core 项目提供。

IRConverter 实现在 Runtime 工程的 jiuwen 包中。开发视图按源码归属把它放在本框;逻辑视图按职责将它放进 Workflow 与 Controller。两个位置是在描述同一段代码的不同侧面。

这里“编译”指将 IR 中的节点、连线、参数和策略组装成 Python 执行对象:

Workflow IR → LazyWorkflow → SDK Workflow、节点、分支、循环

Agent Group IR → Agent Group、成员 Agent 及相关配置

Workflow 路径由 ir_to_workflow() / async_ir_to_workflow() 先返回壳对象;首次使用时,LazyWorkflow.instantiate() 再调用 build_openjiuwen_workflow_from_ir() 构建完整图。多智能体路径通过 ir_to_agent_group() 构建 Agent Group。

agent_runtime 组织“这一次执行”,jiuwen 则负责将 IR 和产品特有能力组装成执行对象。调整 IR 字段含义、节点映射或构图方式,优先查看转换器与节点实现。

源码:agent-runtime/jiuwen/serve/controllers/execution/ir_converter.py:1535,1591,1613,1633;agent-runtime/jiuwen/extension/workflow/lazy_workflow.py:96。

橙色回线:包级依赖环是怎样形成的

Runner 执行前需要转换 IR,因此会引用 jiuwen 中的 IRConverter。转换器构造具体节点时,又会引用 agent_runtime.extension 提供的节点实现:

agent_runtime.runner → jiuwen 中的 IRConverter
jiuwen 中的 IRConverter → agent_runtime.extension 中的节点

包级关系:agent_runtime → jiuwen → agent_runtime

当前转换器直接导入 FlowCode(代码节点)、FlowKnowledgeRetrieval(知识检索)、Questioner(问题交互)、ComplexIntentDetection 等节点。这就是橙色反向箭头的具体来源。

包级依赖环描述两个包相互引用。是否触发 Python 循环导入错误,还取决于具体模块的导入顺序,以及导入期间是否读取尚未初始化的对象。

对开发的影响是:修改节点接口、移动节点实现或拆分安装包时,需要同时检查 Runner 和 IRConverter 两侧。当前 jiuwen 中的这部分实现仍依赖 Runtime 节点;独立复用它时,需要把这些依赖一并考虑。

源码:agent-runtime/agent_runtime/runner/workflow_runner.py:27;agent-runtime/jiuwen/serve/controllers/execution/ir_converter.py:16。

独立构建服务:帮助用户设计和生成智能体

右上角对应 agent_builder/,主要包含自然语言构建、提示词生成与优化、模型测试等能力。这里的“构建”与 Runtime 构建执行对象分别发生在两个职责层次:

位置构建的内容
Builder用户需要的智能体或工作流配置、提示词等设计产物。
Runtime 中的 IRConverter本次执行需要的 Workflow、Agent Group 等执行对象。

执行已有 Workflow 时,IRConverter 在 Runtime 内完成转换。主执行链可以直接使用已有 IR,不需要先进入 Builder。Builder 与 Runtime 复用共享能力,并保持各自的服务职责。

仓库用两组测试约束直接导入边界:Builder 生产代码不直接 import agent_runtime,Runtime 代码不直接 import agent_builder。这是代码边界约束,不能据此推断整个产品没有通过接口或共享数据进行协作。

独立服务与开发环境也需要分别理解:当前 Builder 的 pyproject.toml 明确说明复用 Runtime 的虚拟环境及已有依赖。业务包独立,并不意味着当前开发安装方式已经为两个服务各建一套环境。

图中的 0 in 仅表示本图没有画出指向 Builder 的依赖边;外部请求入口和管理侧调用没有在这张核心依赖图中展开。

源码:agent_builder/tests/test_no_agent_runtime_imports.py:16;agent_builder/tests/test_runtime_decoupled.py:23;agent_builder/pyproject.toml:7。

共享包:让两个服务复用基础实现

右中间的 SHARED 框聚合了三个 Python 包,图中的 utils 是 common_utils 的缩写。它们以代码依赖的方式被服务加载。

共享包主要内容
model_service模型配置解析、鉴权、调用策略、模型客户端适配。
storage对象存储访问及存储提供者。
common_utilsRedis 管理、配置、加解密、请求头等公共工具。

例如 Runtime 执行 LLM 节点、Builder 调试模型,两边都需要模型调用能力,可以复用 model_service 的实现。模型调用策略在这一层修改后,需要同时考虑两个宿主的使用场景。

同一份共享实现,怎样使用不同服务的配置?

代码提供 ports 注册接口,由宿主在启动时注入存储、配置和上下文获取函数。Builder 会注册自己的 settings.llm、请求头和环境变量获取函数;调用共享逻辑时,这些函数提供本服务当前请求需要的数据。

宿主启动 → 注册配置 / 存储 / 上下文提供者
共享逻辑执行 → 通过 ports 取得当前宿主的数据

当前实现仍保留部分回退引用。model_service 在没有注入部分提供者时,会回退导入 Runtime 配置或上下文;部分缓存获取函数还会引用 jiuwen。因此,这一层的解耦程度需要结合初始化路径判断。图中突出的是主要依赖,未逐条画出这些条件回引。

源码:agent_builder/serve/server_fastapi.py:96;packages/model_service/model_service/ports.py:93,113,138,188,198;packages/storage/storage/ports.py:35。

底层 SDK:提供执行框架和通用基础能力

底部的 openjiuwen 提供 Workflow、Session、Checkpointer、模型客户端等基础抽象。上层代码按自己的职责使用这些能力:

  • Runtime:使用会话、检查点、交互输入、流事件等执行接口。
  • jiuwen:使用 Workflow、节点、分支和循环等对象组装执行图。
  • model_service:扩展 SDK 模型客户端,例如基于 OpenAIModelClient 的 StudioModelClient。

Runtime 的依赖声明指定从 agent-core 的 develop 分支安装 SDK。这描述安装来源;实际环境加载哪份 SDK,还要查看安装结果和模块位置。本地存在 agent-core/ 源码目录,并不能单独证明当前进程正在使用它。

结合源码补充图中省略的关系:

  • Builder 也直接使用 SDK:agent_builder.adapter.model_bridge 导入了 SDK 的模型类。图中省略了 Builder → SDK 这条边。
  • 三个共享包都依赖 SDK:模型客户端适配来自 model_service,storage 使用 SDK 日志,common_utils 使用 SDK 加解密能力。共享包到 SDK 的箭头标注为“模型 / 日志 / 加密”。
  • SDK 的 3 in 表示图中已展示的三组依赖;加上省略的 Builder 直接依赖后,完整源码的依赖数量并不限于三组。

源码:agent-runtime/pyproject.toml:11,75;agent_builder/adapter/model_bridge.py:22;packages/model_service/model_service/client.py:28;packages/storage/storage/object_storage.py:34;packages/common_utils/common_utils/crypto_tool.py:6。

源码定位(相对 agent-studio)

agent-runtime/agent_runtime/serve/apis/orchestration.py:133
agent-runtime/agent_runtime/runner/workflow_runner.py:27,226
agent-runtime/agent_runtime/event_handler/event_handler.py:228
agent-runtime/jiuwen/serve/controllers/execution/ir_converter.py:16,1535,1591,1613,1633
agent-runtime/jiuwen/extension/workflow/lazy_workflow.py:96
agent-runtime/pyproject.toml:11,75
agent_builder/tests/test_no_agent_runtime_imports.py:16
agent_builder/tests/test_runtime_decoupled.py:23
agent_builder/pyproject.toml:7
agent_builder/serve/server_fastapi.py:96
agent_builder/adapter/model_bridge.py:22
packages/model_service/model_service/ports.py:93,113,138,188,198
packages/model_service/model_service/client.py:28
packages/storage/storage/ports.py:35
packages/storage/storage/object_storage.py:34
packages/common_utils/common_utils/crypto_tool.py:6

进程视图

SEQUENCE / CONCURRENCY

回答“运行时谁在执行、如何并发和跨进程协作”。展示流式执行中的一种交错顺序;A、B 是多 worker 场景示意。

Agent Runtime 进程视图流式请求由 worker A 的请求协程执行;取消请求可落到 worker B,写 Redis 标记并发布事件,worker A 的订阅协程取消本地任务并注销执行。03 / PROCESS VIEW请求协程执行;Redis 把取消信号送回持有任务的进程OPT[执行仍在飞;取消请求可落到另一个 worker]执行请求登记执行归属模型 / 工具 I/OchunkSSE 事件POST /cancel校验后置位 / 广播signal acceptedruntime:canceltask.cancel()finally: 注销执行CLIENT调用方HTTP / SSEPROCESS执行 worker A请求协程 + 订阅协程STATERedis共享归属 / 取消标记PROCESS取消 worker B另一个进程(可选)ENDPOINT外部模型 / 工具await I/O请求 / 调用返回异步通知SSE 主响应

一次任务在某个 Python worker 内运行;取消请求即使落到另一个 worker,也能通过 Redis 把信号传递到持有任务的进程,触发本地取消。这张图用序列图展示“流式执行 + 可选取消”,重点说明代码运行在哪里、任务如何并发、进程之间如何协作,以及执行怎样结束。

  • A、B 是同类 Runtime 进程在两次请求中扮演的角色。它们没有固定的执行或取消分工;单 worker 场景下,两种角色可以由同一进程承担。
  • 一个 Runtime worker 对应一个 Python 工作进程。进程内可以有多个异步任务,选择 Runner 不会因此启动新进程。
  • 取消信号已接受,不等于所有执行已停止。通知、任务退出和清理是异步协作过程。

五条生命线:分别代表哪些参与者

图中从左到右有五个参与者:

参与者实际含义在图中的职责
调用方前端、其他服务或 API 客户端。发起执行、接收 SSE、发送取消请求。
执行 worker A一个运行 Runtime 的 Python 进程。持有执行任务,调用模型或工具,产生输出。
Redis各 worker 共同访问的状态与通知设施。保存执行归属、取消标记,广播取消信号。
取消 worker B另一个可能接到取消请求的 Runtime 进程。校验取消请求,写入标记并广播。
外部模型 / 工具被调用的模型、API、MCP 等端点。执行外部操作,返回结果或流式片段。

A、B 是本次交互中的角色名称。它们运行同一类 Runtime 服务代码。某个进程这次接到执行请求,下次也可以接到取消请求,没有固定分工。

如何阅读时间与消息

  • 竖向虚线是参与者的生命线,时间从上往下推进。
  • 横向箭头表示调用、返回或通知,具体线型由图底部图例说明。
  • 橙色箭头突出向调用方发送 SSE 的主响应。
  • OPT 框表示可选情形:只有执行期间发生取消,才走这段流程。

模型调用、片段返回和 SSE 输出在实际运行中可能重复很多次,图中只画了一组代表性消息。

进程、worker、Runner、Task:各自处于什么层次

在当前 Agent Runtime 中,一个 worker 就是一个负责处理请求的 Python 工作进程。“进程”强调操作系统中的运行单位,“worker”强调服务中承担工作的角色。

概念是什么在这里的关系
进程(Process)有自己的 PID、内存和运行资源的操作系统运行单位。承载 FastAPI、事件循环和执行代码。
worker承担请求处理工作的角色。当前实现用独立 Python 进程承担这个角色。
Runner组织业务执行的 Python 对象与方法。在 worker 内调用,例如 WorkflowRunner。
异步任务(Task)由事件循环调度的一项执行工作。承载请求执行、后台订阅或成员 Agent 调度等工作。

选择 WorkflowRunner 后,通常是在当前 worker 内调用它的异步方法,继续完成 IR 转换、Session 准备和工作流执行。选择某个 Runner,不会因此启动一个新的 Python 进程。

一个 worker 内可以同时有哪些任务

  • 请求 1 的任务:等待模型返回。
  • 请求 2 的任务:调用工具。
  • 请求 3 的任务:处理取消请求。
  • 后台任务:持续订阅 Redis 的取消通知。

这些任务由同一进程的事件循环调度。某个任务等待支持异步操作的模型或工具 I/O 时,可以通过 await 让出执行机会,让其他就绪任务继续运行。一个 worker 因而可以同时处理多个等待外部响应的请求。

请求数量、Task 数量和进程数量是三回事。收到新请求通常不会新建进程;一个请求也可能在内部创建多个 Task。CPU 密集计算或同步阻塞调用能否并行,要看具体实现。

多 worker 时,A、B 可以是两个独立 Python 进程,B 无法直接操作 A 内存中的 Task,因此需要 Redis 传递信号。单 worker 时,两次请求由同一 Python 进程中的不同任务处理。

worker 是通用角色名称,在其他系统中也可能由线程承担。这里的 Runtime worker 特指 Python 工作进程;其数量也不等于容器的进程总数,容器里还可能运行 Nginx 等进程。

源码:agent-runtime/agent_runtime/EIStart_base.py:9,45;agent-runtime/agent_runtime/serve/server.py:196。

正常执行链:请求进入 A 后发生什么

图中上半部分描述正常执行,可以依次看六个动作:

  1. 调用方向 A 发起执行请求。入口准备输入和上下文,编排层选择一个顶层 Runner。逻辑视图中的这些模块,在进程视图里都合并到了 A 的生命线上。
  2. A 登记本次执行。进入已注册的流式执行链路时,代码取得当前 asyncio.Task,登记它与会话、执行归属之间的关系,为后续取消提供定位依据。
  3. A 调用模型或工具。Runner 推进业务执行,按需发起模型调用、工具调用、知识检索等操作。
  4. 外部端点返回 chunk。这是一次返回的数据片段,可能是模型输出的一部分,也可能是工具执行结果;它不一定对应一个 token,也不一定是最终对外的 SSE 格式。
  5. A 包装事件并发送 SSE。流适配、事件包装和 EventHandler 在 worker 内处理执行信息,整理成调用方可消费的事件。
  6. 执行结束后注销登记。正常执行结束会走流的收尾逻辑;无论正常退出还是被取消,代码都通过 finally 尝试注销本次执行。
请求处理 → Runner 执行 → 模型与工具交互
→ 事件转换 → 响应输出 → 清理

A 的一条生命线合并了这些职责,它们并不分别对应独立进程。实际流式运行时,模型或工具交互、事件转换和响应输出会随着执行推进持续发生。

源码:agent-runtime/agent_runtime/serve/apis/orchestration.py:339;agent-runtime/agent_runtime/event_handler/event_handler.py:151,187,194。

为什么同时需要本地注册表和 Redis

执行登记采用两层结构,两层保存的东西不同。

本地注册表:保存真正的 Task 对象

conversation_id → 当前进程中的执行 Task

Python Task 存在于当前进程内存中。worker B 无法直接读取 worker A 的这张表,也无法直接操作 A 内存里的对象。

Redis:保存跨进程可见的信息

Redis 内容保存或传递什么用途
exec:{conversation_id}正在执行的会话归属,例如实例、项目、入口资源。判断是否登记了执行,并进行归属校验。
suspend:{conversation_id}保留的执行归属快照。当前没有运行任务时,仍可校验挂起会话的归属。
cancel:{conversation_id}取消标记。让后续处理能够看到取消意图。
runtime:cancel发布订阅频道中的会话取消信号。把取消信号广播给各 worker。

suspend 保存的是归属信息,完整的工作流恢复状态由检查点机制管理。

Redis 让各进程知道“哪个会话被取消”;本地注册表让持有任务的进程找到“该取消哪个 Task”。每个 worker 在服务启动的 lifespan 中创建一个长期运行的取消订阅任务,收到广播后查询自己的本地表。

图中的 Redis 通道承担取消通知;模型输出沿着 A 到调用方的响应链返回。

源码:agent-runtime/agent_runtime/serve/execution_registry.py:77,132,194,216,226;agent-runtime/agent_runtime/serve/server.py:196。

跨 worker 取消:B 如何让 A 停止执行

假设用户已经在接收 A 返回的 SSE,此时点击“停止”。取消操作是另一个 HTTP 请求,它可能被负载均衡分发到 B,而原来的 SSE 请求仍由 A 处理。

图中 OPT 区域可以展开为八个动作:

  1. 调用方向 B 发送取消请求。完整路由是 POST /v1/{project_id}/conversations/{conversation_id}/cancel,图中简写为 POST /cancel。
  2. B 查询 Redis 中的执行登记。找到这个会话当前的执行归属。
  3. B 校验归属。校验路径中的项目;如果调用方提供了 Agent 或 Workflow 标识,还会校验对应入口。校验不通过则返回错误,不写取消标记。
  4. B 写入取消标记并发布通知。将 cancel:{conversation_id} 置为真,同时向 runtime:cancel 频道发布会话标识。
  5. B 返回接受结果。响应表示取消信号已被接受。
  6. 各 worker 的订阅任务收到广播。每个 worker 查询自己的本地注册表,没有对应 Task 的进程忽略通知。
  7. 持有任务的 A 调用 task.cancel()。取消作用于承载执行的异步任务。
  8. 执行链退出并进行清理。流式执行的 finally 注销本地与共享的执行登记。

B 返回 HTTP 响应与 A 实际收到广播之间,没有固定的先后保证。图里画的是一种可能的交错顺序。

源码:agent-runtime/agent_runtime/serve/apis/orchestration.py:491;agent-runtime/agent_runtime/serve/execution_registry.py:194,216,226。

“取消已接受”与“执行已停止”有什么区别

取消接口返回 cancel signal accepted 时,说明取消标记和信号已经提交。它没有等待所有执行工作及外部操作都停止后,再返回确认。

字段当前实现中的含义
cancelled: true取消标记确实已写入。
running: true处理取消请求时查到了执行登记。
message说明信号已接受,或没有找到可取消的会话。

如果没有执行登记,也没有可用于校验的归属快照,接口可以返回 HTTP 200,同时给出 cancelled: false、running: false 和“没有找到会话、未执行取消”的说明。HTTP 状态码本身不足以判断实际取消结果。

Task 如何响应取消

task.cancel() 采用异步任务的协作式取消机制:任务在可响应取消的位置收到 CancelledError,随后执行异常传播与清理代码。

  • 正在等待异步 I/O 的任务通常能响应取消。
  • 同步阻塞或长时间占用事件循环的代码,会影响响应速度。
  • 子任务需要正确传播取消。当前 Controller 控制链会显式取消正在执行的成员任务。
  • 本地停止等待外部调用,并不自动撤销外部服务已经完成的操作;能否停止远端处理,取决于客户端与远端协议。

图对应的内部流封装在正常走到流末尾时输出唯一的终态 done;被取消打断时,不会走到这段正常收尾逻辑。客户端还需要结合错误、挂起事件和外层协议判断业务结果,不能把所有连接结束都当作成功完成。

源码:agent-runtime/agent_runtime/serve/apis/orchestration.py:339,491;agent-runtime/jiuwen/multi_agent/agent_group/hierarchical_group/control_agent.py:124。

运行、挂起和取消:为什么恢复时还要读 Redis

“工作流等待用户输入”和“用户点击停止”是两种不同情况。

状态当前发生了什么后续处理
正在运行worker 内存在执行 Task,持续推进或等待 I/O。可以通过本地 Task 响应取消。
交互挂起工作流保存检查点,把问题返回用户。新请求携带补充输入后尝试恢复。
已取消写入取消意图,并尝试停止正在运行的 Task。后续执行需处理取消状态。
正常完成当前执行结束并注销登记。后续请求按新的执行条件处理。

工作流挂起后,原来的请求已经结束,通常没有一个旧 Task 一直留在 worker 内等待用户回答。用户回答时,需要发起新的请求。Runtime 根据会话检查持久化状态,再创建本次执行所需的对象,通过恢复输入继续工作流。

挂起期间可能没有可取消的运行 Task,但仍然需要保存“用户已经取消这次挂起执行”的意图。因此取消除了广播,还要保留持久化标记。

当前 Workflow 路径会检查这个状态:

  • 存在有效中断检查点,且未取消:使用 InteractiveInput 恢复。
  • 存在中断检查点,但已经取消:尝试清理旧检查点,清理成功后从入口重新执行;清理失败时保留取消标记,避免静默恢复旧状态。

广播用于尽快通知正在运行的任务,持久化标记用于让后续执行仍能识别取消意图。

源码:agent-runtime/agent_runtime/runner/workflow_runner.py:264,371,480;agent-runtime/agent_runtime/runner/fast_redis_checkpointer.py:168。

一个 Runtime 有多少个 worker,图中的 A、B 如何对应部署

图中画出 A、B,是为了说明多 worker 场景下的协作机制。按当前仓库默认配置,一个 Runtime 实例启动 1 个 Python worker,可以配置成多个。

如果“一个 Runtime”指一个 Pod 或容器实例,其 Python worker 数量由 GUNICORN_WORK_NUM 配置,默认是 1。如果指整个 Runtime 服务,则还需要计算实例副本数。

配置当前值含义
replicas1启动 1 个 Runtime Pod。
GUNICORN_WORK_NUM1每个实例启动 1 个处理请求的 Python worker。
NGINX_LOAD_BALANCINGtrue通过 Nginx 转发请求。

按这个 Kubernetes 模板启动时,整个 Runtime 服务有 1 个处理请求的 Python worker。执行请求和取消请求可以由同一进程内的不同异步任务处理,A、B 两种角色就落在同一进程中。以上是仓库配置,实际运行数量还要看部署时是否覆盖了环境变量。

实例数与 worker 数可以分别调整

Python worker 总数 = Runtime 实例数 × 每个实例的 worker 数

示例:3 个实例 × 每个实例 4 个 worker = 12 个 Python worker

这个公式适用于每个实例采用相同 worker 配置的情况;配置不同的实例需要分别求和。增加 worker 或实例后,两次请求可能进入不同进程,Redis 广播与本地 Task 注册表的配合就能发挥跨进程作用。

当前代码支持两种启动方式

  • 关闭 Nginx 模式:由 Uvicorn 启动配置数量的 worker。
  • 开启 Nginx 模式:启动脚本拉起多个独立 Python 服务,每个服务内部只运行一个 Uvicorn worker,再由 Nginx 分发请求。

因此,配置为 4 时,目标是 4 个 Python worker。Nginx 启动脚本先保存目标数量,再将每个子服务的 worker 数设置为 1,避免重复扩出多层工作进程。

这张图的适用范围

它展示已接入执行注册的流式执行链路。应用接口即使最终返回 JSON,也可能在内部走这条链路;直接阻塞执行和单组件调试是否具备同样的取消行为,需要分别看对应实现。

如果 A 进程退出,它内存中的 Task 也随之消失。Redis 中的登记和检查点不会把 Task 自动迁移到 B;后续恢复依赖新的请求和有效持久化状态。

源码:deploy/k8s/studio-runtime.yaml:7,149,201;agent-runtime/agent_runtime/common/config.py:29;agent-runtime/agent_runtime/EIStart_base.py:9,45;docker/studio-runtime/script/start_nginx_lb.sh:17,26。

一个 worker 可以承担多少并发,怎样得到可信的容量数字

一个 worker 可以通过异步任务同时处理多个请求,但目前没有经过容量压测确认的“单 worker 最大并发数”。它尤其适合模型调用、工具调用这类包含大量 I/O 等待的工作。

需要区分三个数字:实例数、每个实例的 worker 数、每个 worker 的请求并发数。前两个主要由部署配置确定,第三个与任务负载、资源和可接受的延迟、错误率有关。

同时接收请求与稳定服务请求,衡量的是不同能力

影响因素为什么影响容量
CPU 和同步阻塞代码构图、数据处理等工作会占用执行时间;阻塞调用可能拖慢其他请求。
内存每个请求都要保存上下文、消息、运行状态等。
模型与工具服务下游的限流、连接数和响应速度可能先成为瓶颈。
连接池与网络Redis、HTTP 客户端、SSE 连接等都有资源开销。
任务复杂度简单问答和包含多个模型、工具、分支的工作流,资源消耗差别很大。

当前 uvicorn.run() 的启动代码没有显式设置 limit_concurrency。这说明启动入口没有配置一个固定的请求并发限制,不能据此认定容量无限。

两个配置数字不能直接当作业务并发数

  • worker_connections 1024:是 Nginx 的连接限制,不代表一个 Python worker 能稳定运行 1024 个工作流。
  • Redis 默认连接池大小 50:是 Redis 连接数量,不代表最多支持 50 个用户请求。

如何得到可靠的并发容量

  1. 固定测试条件。确定 CPU、内存、worker 数、模型端点,以及有代表性的工作流或 Agent 任务。
  2. 逐级增加并发。记录同时执行的请求数,并保持测试条件可比较。
  3. 观察服务质量与瓶颈。检查首个输出延迟、总耗时、错误率、CPU、内存,以及下游限流情况。
  4. 按服务目标确定容量。选择仍满足延迟和错误率要求的稳定并发水平。

最终应得到带条件的结论:“在指定 CPU、内存、模型和工作流配置下,单个 worker 在满足延迟与错误率要求时,能稳定承载 N 个并发请求。”

当前已经确认:默认 1 个 worker,支持异步并发。这里依据配置与源码解释实现,没有进行容量压测,因此没有把单 worker 标成“支持 100 或 1000 并发”。

源码:agent-runtime/agent_runtime/EIStart_base.py:45;docker/studio-runtime/script/start_nginx_lb.sh:99;packages/common_utils/common_utils/common_config.py:62;packages/common_utils/common_utils/redis_manager.py:108。

源码定位(相对 agent-studio)

agent-runtime/agent_runtime/EIStart_base.py:9,45
agent-runtime/agent_runtime/common/config.py:29
agent-runtime/agent_runtime/serve/server.py:196
agent-runtime/agent_runtime/serve/apis/orchestration.py:339,491
agent-runtime/agent_runtime/serve/execution_registry.py:77,132,194,216,226
agent-runtime/agent_runtime/event_handler/event_handler.py:151,187,194
agent-runtime/jiuwen/multi_agent/agent_group/hierarchical_group/control_agent.py:124
agent-runtime/agent_runtime/runner/workflow_runner.py:264,371,480
agent-runtime/agent_runtime/runner/fast_redis_checkpointer.py:168
deploy/k8s/studio-runtime.yaml:7,149,201
docker/studio-runtime/script/start_nginx_lb.sh:17,26,99
packages/common_utils/common_utils/common_config.py:62
packages/common_utils/common_utils/redis_manager.py:108

部署视图

KUBERNETES DEPLOYMENT

回答“软件装在哪里、由什么进程运行、通过哪些端口通信”。边界内仅展示模板明确声明的 Service 与 Pod。

Agent Runtime 部署视图仓库 Kubernetes 模板将 NodePort Service 指向一个 studio-runtime Pod,Pod 内 Nginx 代理至一个 Python worker;Redis、对象存储、模型工具和可选 OpenSearch 仅声明为可配置依赖端点。04 / DEPLOYMENT VIEW仓库 Kubernetes 模板快照 · 非线上环境实测Kubernetes · namespace: default依赖端点 · 实际位置未声明HTTP:31014TCP:6379HTTP(S):配置HTTP(S):配置HTTP(S):配置SERVICEstudio-runtimeport = targetPort: 31014PODx1studio-runtime · Podstudio-runtime1.0.0.20260526091150.x86_6401 Nginx0.0.0.0:31014 · least_connupstream → 127.0.0.1:3101502 Python / Uvicorn ×10.0.0.0:31015NGINX_LOAD_BALANCING=trueGUNICORN_WORK_NUM=1requests = limits: 2 CPU / 4 Gi/dev/shm: emptyDir(Memory), 2 GiENDPOINTRedisREDIS_HOST: 未配置ENDPOINT对象存储STORAGE_TYPE=OBSENDPOINT模型 / 工具端点LLM · MCP · 可选 SandboxOPTIONALOpenSearch(长期记忆)MEMORY_ENABLED=falseNodePort :31014入口暴露在节点端口跨 Pod 依赖调用可选 / 默认关闭x1:副本数;端口随配置变化

这张图描述一套具体的 Kubernetes 部署形态:调用方通过 Service 进入 Runtime Pod,由容器内的 Nginx 转发到 Python worker,执行过程中再访问 Redis、对象存储、模型与工具等依赖。

  • 副本数、端口和资源配额来自仓库部署模板。它们表示“按这份模板会怎样部署”,这里没有对实际运行的集群做探测。
  • 当前是 1 个 Pod、1 个 Runtime 容器、1 个 Python worker。容器内同时运行 Nginx 和 Python 服务,三个数量对应不同层次。
  • 右侧表示依赖端点,实际部署位置未声明。依赖可能位于同一集群、其他集群或托管服务中。

先看边界:Namespace、Deployment、Pod、容器和进程是什么关系

左侧大框标注 Kubernetes · namespace: default,表示图中明确声明的 Kubernetes 资源位于 default 命名空间。里面主要画了 Service 和 Pod,它们与其他概念的关系是:

层次在当前部署中的作用
Namespace对 Kubernetes 资源进行逻辑分组;当前是 default。
Deployment声明 Runtime 使用什么镜像、启动多少副本、如何更新。
PodRuntime 实例的运行单元,由 Deployment 管理。
容器Pod 内运行 Runtime 镜像的环境;当前模板声明一个容器。
进程容器内实际运行的程序,包括 Nginx、Python worker 等。

当前配置可以理解为:一个 Deployment 管理一个 Runtime Pod;Pod 内有一个 Runtime 容器;容器内同时运行 Nginx 和 Python worker。

Pod、容器、进程是不同层次。一个容器可以运行多个进程;一个 Python worker 内又可以运行多个异步任务。

图中的 x1 表示当前配置的 Pod 副本数为 1。它和下面的 Python / Uvicorn ×1 分别描述实例数量与实例内部的 Python worker 数量。

源码:deploy/k8s/studio-runtime.yaml:1,7,21。

左侧 Service:外部请求怎样进入 Runtime

studio-runtime Service 提供访问 Runtime 的稳定入口。它通过标签选择后端 Pod:

selector:
  app: studio-runtime

Deployment 创建的 Pod 带有对应标签,Service 因而能够找到它们。模板中三个端口都写成 31014,但含义不同:

配置当前值含义
nodePort31014暴露在 Kubernetes 节点上的访问端口。
port31014Service 自身提供服务的端口。
targetPort31014请求转发到后端 Pod 的目标端口。

在网络允许访问节点端口的情况下,调用方可以通过“节点 IP + 31014”进入服务链。三个值相同是当前配置选择,并不要求它们始终相同。

Service 的目标端口连接的是 Pod 内的 Nginx。当前链路是:

节点端口 :31014
  → Service
  → Pod 内 Nginx :31014
  → Python worker :31015

Service 负责把网络流量送到匹配的后端 Pod;Workflow 或 Agent 的执行发生在 Python worker 中。当前模板设置 HTTPS=false,这条服务链按 HTTP 配置。

源码:deploy/k8s/studio-runtime.yaml:83,307。参考:Kubernetes Service 文档。

Runtime Pod:部署进去的到底是什么

Pod 内使用的镜像是 studio-runtime:1.0.0.20260526091150.x86_64。这是模板指定的镜像名称和标签,标识要运行的交付产物;不能据此认定当前工作区的最新源码已经包含在镜像中。

Dockerfile 会把 Runtime 相关代码和启动脚本打包进去,包括:

  • agent_runtime:HTTP 服务、Runner、事件处理等。
  • jiuwen:IR 转换、控制策略、多智能体相关实现。
  • model_service、storage、common_utils 等共享代码。
  • Nginx 配置与服务启动脚本。

这些对应开发视图中的代码包。部署时,它们进入同一个镜像;运行时,再由 Python 进程加载使用。

角色当前监听地址主要工作
Nginx0.0.0.0:31014接收并转发请求。
Python / Uvicorn worker0.0.0.0:31015执行 FastAPI 接口、Runner 和业务逻辑。

图中省略了启动脚本、监护脚本等辅助工作,因此这两个框不能用来统计容器内的全部操作系统进程。

镜像拉取策略会影响启动方式

模板设置 imagePullPolicy: Never。它要求镜像已经存在于目标节点的容器运行环境中,Kubernetes 不会自动下载;本地不存在时会启动失败。

源码:docker/studio-runtime/Dockerfile:35,81;deploy/k8s/studio-runtime.yaml:23,251。参考:Kubernetes 镜像策略。

Nginx 为什么放在 Python worker 前面

在当前部署中,Nginx 主要承担统一入口、worker 分流和流式响应转发。

为外部提供固定入口

启动脚本使用 PORT 作为 Nginx 的端口,再按 PORT + i 分配 Python worker 端口:

Python worker 数量Nginx 入口worker 端口
13101431015
23101431015、31016
43101431015~31018

在同一个实例中增加 worker,外部仍然访问 31014。Nginx 模式下,每个 Python 子服务内部只运行一个 Uvicorn worker。

在多个 worker 之间分配请求

当前配置是 LB_STRATEGY=least_conn。它按活动连接数量选择后端,为持续时间不同的请求(包括 SSE 长连接)提供按连接占用情况分流的方式。它不直接测量 worker 的 CPU 或业务计算量。当前只有一个 Python worker,因此还没有多个后端可供选择。

为 SSE 关闭响应缓冲和缓存

proxy_buffering off;
proxy_cache off;

这可以避免响应片段在这一层代理中被集中缓冲,有利于将 Runtime 产生的事件及时向调用方转发。

图中的 upstream → 127.0.0.1:31015 表示 Nginx 通过本地回环地址访问 Python worker。它与 worker 监听 0.0.0.0:31015 并不矛盾:前者描述连接目标,后者描述监听地址。

源码:docker/studio-runtime/script/start_nginx_lb.sh:17,36,44,124,132,149;agent-runtime/agent_runtime/EIStart_base.py:9。参考:Nginx 负载均衡说明、Nginx 代理缓冲说明。

右侧四组依赖:Runtime 自己运行什么,又调用什么

右侧画的是 Runtime 使用的依赖端点:

依赖主要用途当前模板状态
Redis执行状态、检查点、取消标记和跨 worker 通知等。单机模式、端口 6379;主机地址待填写。
对象存储提供运行所需的配置和存储资源。STORAGE_TYPE=OBS,端点及桶等配置待填写。
模型 / 工具端点调用 LLM、MCP、API,以及可选沙箱。由运行配置和环境配置确定。
OpenSearch可选长期记忆中的向量存储。MEMORY_ENABLED=false,默认未启用记忆库。

Redis 是当前服务启动链中的关键依赖

Runtime 启动时会初始化 Redis 客户端并执行连接检查,失败会抛出启动错误。它还被用于创建检查点能力和取消通知订阅。这里的 Redis 已经参与执行状态管理。

对象存储还参与运行配置获取

例如,模板设置了 MODEL_CONFIG_STRATEGY=obs,对应实现会从对象存储获取模型服务元数据和认证信息。因此,“镜像已经启动”和“运行所需的配置已经可读取”是两个不同条件。

OpenSearch 对应可选长期记忆

当前总开关关闭时,长期记忆初始化会直接跳过;开启后,才需要相关存储和模型配置。该路径还包含连接失败时的降级处理。

右侧的位置只表达依赖关系,不能据此判断它们一定在 Kubernetes 集群外。它们可能部署在同一集群、其他集群,或使用托管服务;这份 Runtime 模板没有声明其实际位置。图中的 HTTP(S):配置 表示协议与端口取决于具体端点,并非统一使用 443。

模型、MCP 和可选沙箱在图中合为一个端点组。Manager、Builder、Console、监控及代码执行子进程没有在这张 Runtime 部署图中展开;仓库中的 Docker Compose 是另一套部署方案。

源码:deploy/k8s/studio-runtime.yaml:25,54,69,97,112,127;agent-runtime/agent_runtime/serve/server.py:178;agent-runtime/agent_runtime/common/model_providers.py:321;agent-runtime/agent_runtime/memory/adapter/ltm_manager.py:64。

2 CPU、4 Gi 内存和 /dev/shm 的 2 Gi 应该怎么理解

模板给 Runtime 容器配置:

资源requestslimits
CPU22
内存4 Gi4 Gi
  • requests 用于 Kubernetes 调度时计算资源需求。
  • limits 约束容器能够使用的资源;CPU 超限可能受到节流,内存超限可能触发 OOM 处理。

这些资源属于整个 Runtime 容器,由里面的 Nginx、Python worker 等进程共同使用。将 worker 数从 1 改成 4,不会自动给每个 worker 各增加 2 CPU、4 Gi 内存。

/dev/shm 是内存型临时卷

模板给 /dev/shm 挂载了一个 emptyDir:

emptyDir:
  medium: Memory
  sizeLimit: 2Gi

它表示该卷最多使用 2 Gi,且实际写入的数据占用内存。这部分使用量会计入内存预算,不能理解成容器有“4 Gi + 2 Gi = 6 Gi”可用内存。sizeLimit: 2Gi 也不表示启动时立即占满 2 Gi。

这些数值描述资源配置,不能直接推出支持多少个工作流并发;容量还需要结合具体任务和压测结果判断。

源码:deploy/k8s/studio-runtime.yaml:16,220,227。参考:Kubernetes 资源管理、内存型 emptyDir 的资源计量。

健康检查:Kubernetes 如何判断服务是否启动、是否需要重启

虽然图中没有展开探针,模板实际配置了两类:

探针当前检查方式主要目的
startupProbeTCP 连接 31014。判断启动阶段监听端口是否已经可连接。
livenessProbeHTTP 请求 31014 的 /v1/health。判断服务是否还能响应存活检查。
readinessProbe未单独配置。当前模板没有独立的业务就绪探针。

配置了 startup probe 后,Kubernetes 会等待它成功,再启动存活等后续探针;存活探针持续失败达到阈值时,会触发容器重启。readiness 主要用于决定是否继续向 Pod 分配服务流量。

当前存活检查的配置包括:

  • 检查间隔:20 秒。
  • 单次超时:5 秒。
  • 连续失败阈值:5 次。

两个探针都走 Nginx 的 31014 端口,HTTP 健康请求再通过代理到达 Python 服务。

/v1/health 当前主要返回健康响应文本,不会每次执行完整工作流,也不会逐一验证所有模型、工具和存储端点。因此,健康接口成功只能证明这条检查路径可以响应,不能等同于所有业务依赖都正常。

源码:deploy/k8s/studio-runtime.yaml:230,241;agent-runtime/agent_runtime/serve/apis/orchestration.py:141。参考:Kubernetes 探针说明。

如何扩容,以及这张图体现了什么可用性边界

这里有三个不同的扩容方向:

调整方式改变什么主要影响
增加 GUNICORN_WORK_NUM单个实例内的 Python worker 数量。更多进程共同使用该容器的资源。
增加 replicasRuntime Pod 数量。增加独立实例,由 Service 分配流量。
增加 CPU / 内存配额单个容器的资源预算。为该实例提供更多计算和内存资源。

如果每个实例配置一致:

Python worker 总数 = Pod 副本数 × 每个 Pod 的 Python worker 数

示例:3 个 Pod × 每个 Pod 4 个 worker = 12 个 Python worker

此时会有两层流量分配:

  1. Kubernetes Service 将连接分配到后端 Pod。
  2. Pod 内 Nginx 将请求分配到该实例的 Python worker。

副本、更新和执行状态需要一起理解

当前 replicas=1 表示只有一个 Runtime 实例。该实例不可用时,没有另一个现成副本接替它的业务处理;重建实例也不会自动迁移原进程内正在运行的 Task。

模板设置了优先分散节点和可用区的软反亲和规则,也配置了滚动更新,但这些配置不能直接视为正在执行的长请求不会中断。它还设置了 30 秒的终止宽限期,运行中的请求与恢复机制需要结合这个生命周期理解。

评估扩容时要同时看实例数、每实例 worker 数、资源预算、下游服务容量,以及状态恢复能力。当前模板没有声明 HPA 自动扩容规则。

源码:deploy/k8s/studio-runtime.yaml:7,149,220,256,267,298。

源码定位(相对 agent-studio)

deploy/k8s/studio-runtime.yaml:1,7,16,23,83,149,201,220,230,241,251,256,267,298,307
docker/studio-runtime/script/start_nginx_lb.sh:17,26,36,44,124,132,149
docker/studio-runtime/script/start_server.sh:72
docker/studio-runtime/Dockerfile:35,81
agent-runtime/agent_runtime/EIStart_base.py:9,45
agent-runtime/agent_runtime/serve/server.py:178,196
agent-runtime/agent_runtime/serve/apis/orchestration.py:141
agent-runtime/agent_runtime/common/model_providers.py:321
agent-runtime/agent_runtime/memory/adapter/ltm_manager.py:64