OpenRath v1.2.1
在 v1.2.0 的 Memory 平面与内部 Async 运行时之上,v1.2.1 把控制流带进了 Workflow。此前 Workflow 里下一步跑哪个 Agent 必须在写代码时就定好;现在新增的动态工作流让这个选择交由 LLM 在运行时做出,而 if / while 这类分支与循环仍用普通 Python 表达。Selector 负责判断该走哪条路,你的代码负责分派——没有图引擎,也没有 DSL。OpenRath 由此从「可组合的多 Agent 框架」进一步走向运行时可动态路由的多 Agent 编排。
核心特性
动态工作流(LLM 路由)
围绕 Selector 新增了一套动态控制流原语。它们都沿用既有模式——Selector 仿照 Compressor,select_session 仿照 run_session_compress——并且不引入 system tool、registry,也不改动 session graph:
flow.Selector— 与Agent/Compressor同级的内置 Workflow,本质是一个由 LLM 驱动的路由器。调用Selector.forward(session, *workflows)时,它把路由说明、当前 session、以及各候选 Workflow 的description组成的带编号菜单合并为一次 LLM 调用,再返回被选中的下一个 Workflow。动态多 Agent 控制流由此可以用普通 Python 写出来。flow.EmptyWorkflow— 一个空操作 Workflow,forward会原样返回输入的 session。当没有合适候选、下标越界或根本没有候选时,Selector会返回它而非None。这样调用方既能无条件分派(session = result(session)是安全的空操作),也能通过isinstance(result, flow.EmptyWorkflow)判断任务是否结束。Workflow.description— 新增字段(默认""),让每个 Workflow 都能附带一句自我描述;Agent与Compressor均支持。这正是Selector路由时所依据的文本。session.select_session—Selector底层的路由原语,用一次 LLM 调用完成选择。如果想自己拼装控制流,可以直接使用它。
快速上手:python example/11_dynamic_selector.py,演示 if 分支与 while 循环两种用法。
from rath import flow
selector = flow.Selector(provider)
triage = flow.Agent("处理账单问题。", provider, description="账单、发票、退款、支付方式")
tech = flow.Agent("解决技术问题。", provider, description="安装、报错、配置、故障排查")
wrapup = flow.Agent("收尾并总结。", provider, description="收尾并给出最终总结")
# 持续路由,直到 Selector 返回 EmptyWorkflow(表示任务结束)
while not isinstance(
nxt := selector.forward(session, triage, tech, wrapup), flow.EmptyWorkflow
):
session = nxt(session)其他改进
- 示例 — 新增
example/11_dynamic_selector.py,演示动态多 Agent 场景下的if与while控制。 - 文档 — 新增用户指南
docs/source/user_guide/dynamic_workflow.md,并在workflow_agent.md/main_components.md中交叉引用;README 与 README_zh 补充了「动态控制流」章节,PyTorch 对照表中也新增了Selector一行。 - CI — Lint、Test Fast、Build、OpenViking、OpenSandbox 全部通过。
安装
pip install --upgrade openrath
# 可选 memory backend
pip install --upgrade "openrath[openviking]"
# 可选 sandbox backend
pip install --upgrade "openrath[opensandbox]"兼容性
- 完全向后兼容:
Workflow.description默认为"",所有新增符号都是增量的,现有Agent/Compressor/Workflow调用点不受影响。 Selector.forward(session, *workflows)返回的是Workflow而非Session,刻意偏离了基类forward(session) -> Session的约定——因为它是一个路由决策组件,而非 session 变换器;任务结束以返回EmptyWorkflow表示。- 支持 Python 3.10 – 3.13。
完整变更: v1.2.0...v1.2.1
OpenRath v1.2.1
Building on v1.2.0's memory plane and internal async runtime, v1.2.1 brings control flow into the Workflow layer. Until now, which agent runs next had to be decided when you wrote the code; the new dynamic workflow lets an LLM make that choice at runtime, while branching and loops stay plain Python if / while. Selector decides which path to take and your code does the dispatch—no graph engine, no DSL. With it, OpenRath moves from a composable multi-agent framework toward one with runtime dynamic routing.
Major Features
Dynamic Workflows (LLM-routed control flow)
A set of dynamic-control-flow primitives built around Selector. They follow existing patterns—Selector mirrors Compressor, select_session mirrors run_session_compress—and introduce no system tool, no registry, and no change to the session graph:
flow.Selector— a built-in Workflow, sibling toAgent/Compressor, that acts as an LLM-backed router. CallingSelector.forward(session, *workflows)merges the routing instructions, the current session, and a numbered menu of each candidate'sdescriptioninto a single LLM call, then returns the next workflow to run. Dynamic multi-agent control flow becomes expressible in ordinary Python.flow.EmptyWorkflow— a no-op Workflow whoseforwardreturns the input session unchanged. When no candidate fits, the index is out of range, or there are no candidates at all,Selectorreturns this instead ofNone. Callers can then dispatch unconditionally (session = result(session)is a safe no-op) and checkisinstance(result, flow.EmptyWorkflow)to tell when the task is done.Workflow.description— a new field (default"") that lets every Workflow carry a one-line self-description; supported on bothAgentandCompressor. This is the textSelectorroutes on.session.select_session— the routing primitive beneathSelector, making the choice in a single LLM call. Use it directly if you want to assemble your own control flow.
Quick start: python example/11_dynamic_selector.py, which demonstrates both the if and while patterns.
from rath import flow
selector = flow.Selector(provider)
triage = flow.Agent("Answer billing questions.", provider,
description="Billing, invoices, refunds, payment methods")
tech = flow.Agent("Solve technical problems.", provider,
description="Installation, errors, configuration, troubleshooting")
wrapup = flow.Agent("Wrap up and summarize.", provider,
description="Wrap up and produce a final summary")
# keep routing until Selector returns an EmptyWorkflow (the task is done)
while not isinstance(
nxt := selector.forward(session, triage, tech, wrapup), flow.EmptyWorkflow
):
session = nxt(session)Other Improvements
- Example — new
example/11_dynamic_selector.pyshowingifandwhilecontrol flow in a dynamic multi-agent setting. - Docs — new user guide
docs/source/user_guide/dynamic_workflow.md, cross-referenced fromworkflow_agent.md/main_components.md; README and README_zh gain a "dynamic control flow" section, and the PyTorch mapping table gains aSelectorrow. - CI — Lint, Test Fast, Build, OpenViking, and OpenSandbox all pass.
Install
pip install --upgrade openrath
# optional memory backend
pip install --upgrade "openrath[openviking]"
# optional sandbox backend
pip install --upgrade "openrath[opensandbox]"Compatibility
- Fully backward compatible:
Workflow.descriptiondefaults to"", all new symbols are additive, and existingAgent/Compressor/Workflowcall sites are unaffected. Selector.forward(session, *workflows)returns aWorkflowrather than aSession, deliberately departing from the baseforward(session) -> Sessioncontract—it is a routing-decision component, not a session transformer, and signals completion by returning anEmptyWorkflow.- Python 3.10 – 3.13 supported.
Full changelog: v1.2.0...v1.2.1