跳转至

核心概念 · 心智模型

开始编排前,先建立一套统一的心智模型。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):按 srcbranch 输出值路由到不同下游。

Channel(通道 = 数据的命脉)

节点之间传递的数据叫 channel,命名规则 {节点名}.{输出键}(例如 llm.replykb.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. 节点分类

从编排角度看,节点有三类角色:

  • 计算节点:算中间结果,不直接面向用户(IfElseHttpRequestKnowledgeBaseSearchQuestionClassifyVariableUpdate)。
  • 输出节点is_output=True):内容进最终 answerAnswerChatLLM 默认输出节点,可用 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/flowResponsesdetail=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

下一步