核心概念 · 心智模型¶
开始编排前,先建立一套统一的心智模型。AgentFramework 分四层,每层解决一类问题:
| 层 | 模块 | 回答的问题 |
|---|---|---|
| 节点组件库 | agentframework.nodes |
有哪些可复用的「动作」积木?输入输出是什么? |
| 编排 DSL | agentframework.dsl |
怎么把这些积木拼成一张图? |
| 运行时 | agentframework.runtime |
图怎么编译、执行、暂停恢复? |
| 网关 | agentframework.gateway |
怎么把图暴露成对外对话服务? |
1. 六个核心概念¶
Workflow(工作流 = 一张有向图)¶
编排的顶层对象。你往里加节点、连边,最后 compile() 成一棵可执行图。
Node(节点 = 一个动作)¶
最小的执行单元,形如「声明 + 实现」:
- 声明(
NodeDeclaration):这个节点长什么样——名字、描述、输入输出字段 schema、是否是输出节点。 - 实现(
__call__(inputs, ctx)):只管「算」,不碰状态 channel,返回声明的输出键。
内置节点见内置节点速查。框架把「输入解析/校验、输出写回」都交给编译管线统一处理,节点作者只需关心算法本身。
Edge(边 = 控制流)¶
节点之间的连线,决定执行顺序。分两种:
- 普通边
add_edge(src, dst):src跑完必跑dst。 - 条件边
add_conditional_edge(src, mapping):按src的branch输出值路由到不同下游。
Channel(通道 = 数据的命脉)¶
节点之间传递的数据叫 channel,命名规则 {节点名}.{输出键}(例如 llm.reply、kb.text)。节点输出会自动成为 channel;wf.ref("llm.reply") 就是引用这个 channel 的值。
关键约束:节点输出 channel 必须在编译期并入图的状态 schema(
_build_state_schema),否则运行期会被丢弃。使用声明式节点 +compile()时框架会自动处理,你无需手动维护 schema——这是 DSL 层为你省掉的事。
State(状态)¶
整张图共享的运行状态。一次请求里,所有已执行的节点输出都汇集在 state 中,供下游节点经 wf.ref 读取。对调用方(网关)而言,你只需要关心入口消息和返回的变量,state 内部细节由框架管理。
编译(compile)¶
wf.compile() 做两件事:静态校验(引用是否存在、必填输入是否齐、图是否连通)并翻译成 LangGraph 的 CompiledStateGraph。它返回的是原生图对象,不额外包一层,因此可以直接透传 invoke/astream,甚至接 LangGraph Studio 可视化。
2. 节点分类¶
从编排角度看,节点有三类角色:
- 计算节点:算中间结果,不直接面向用户(
IfElse、HttpRequest、KnowledgeBaseSearch、QuestionClassify、VariableUpdate)。 - 输出节点(
is_output=True):内容进最终answer(Answer;ChatLLM默认输出节点,可用output=False关闭)。 - 交互节点:暂停等用户输入(
UserSelect/UserInput),需配合断点持久化。
3. 一次请求的生命周期¶
以最典型的「网关 + 客服工作流」为例:
sequenceDiagram
participant C as 客户端
participant G as 网关(FastAPI)
participant WF as 编译后工作流
participant LLM as 模型
C->>G: POST /chat/completions (appId/chatId/messages)
G->>WF: 组装消息 + 注入运行上下文(stream_callback)
WF->>LLM: ChatLLM 生成(流式)
LLM-->>WF: 逐块文本
WF-->>G: emit_stream → answer 事件
G-->>C: SSE: answer(增量) ... [DONE]
G->>C: (stream=false 或 detail) 汇总/变量事件
关键点:流式增量在节点内经 ctx.emit_stream 产生,网关统一映射为 SSE answer 事件;节点执行状态经 ctx.add_detail 上报为 flowNodeStatus/flowResponses(detail=true 时)。具体事件结构见仓库 doc/Agent接口契约.md。
4. 「输出收口」:为什么用 Answer¶
框架遵循严格收口约定:一次请求的最终回答应只有一个出口。
- 默认
ChatLLM是输出节点,其流式内容直接进answer。 - 当链路里有多个可能回复来源(比如分流后不同分支给不同话术),通常让
ChatLLM(output=False)只算不回显,再由Answer统一收口,避免「模型流 + Answer 整段」双份输出。
wf.add_node(ChatLLM(name="llm", chat_model=model, output=False))
wf.add_node(Answer(name="ans", text=wf.ref("llm.reply")))
wf.add_edge("llm", "ans")
5. 变量:内部控制 vs 对外回传¶
VariableUpdate写内部控制变量(ctx.variables),不直接回传前端。VariableOutput显式声明本次要回传给前端的变量(ctx.return_variables)。- 文本里的
{{key}}占位符由框架按ctx.variables渲染(Answer/VariableUpdate链路均支持)。
为什么要分两套?——「内部算完,挑几个返回」。工作流内部可以随意记录中间状态,但只有你明确用 VariableOutput 声明的键才会作为 updateVariables 事件回传,避免把一堆内部临时量泄露给前端。详见指南 05。