跳转到内容

GraphAgent 图驱动的 Agent 执行循环

学习目标

理解 LobeHub 中 GraphAgent 如何利用声明式有向图驱动 Agent 执行流程,包括 agent/llm 两种节点类型、回溯限制和结构化输出提取机制。

前置知识

本章涉及 Agent 核心循环和状态图的通用原理,建议先阅读:

下文假设你已理解上述概念,直接聚焦 LobeHub 的具体实现。

项目实践

双层 Agent 架构

LobeHub 的 agent-runtime 包提供两种 Agent 实现:

GeneralChatAgent 实现传统的 phase-driven 循环(LLM 决定流程),而 GraphAgent 用声明式图替代 LLM 自主决策,内部委托 GeneralChatAgent 处理 agent 节点的工具调用循环。

GraphAgent 核心循环

GraphAgent 的执行循环分为三个阶段:

// 伪代码:GraphAgent runner
async runner(context, state): Promise<AgentInstruction> {
const gc = this.getGraphContext(state);
// 1. 首次调用:初始化图
if (!gc) return this.initGraph(context, state);
const node = this.graph.states[gc.currentNode];
// 2. agent 节点:委托 GeneralChatAgent 处理工具调用循环
if (gc.nodeActive && node.type === "agent") {
const instruction = await this.innerAgent.runner(context, state);
// 拦截 finish — 工具循环完成后提取结构化输出
if (instruction.type === "finish") {
return this.startExtraction(state, gc);
}
return instruction;
}
// 3. llm 节点:直接单轮 LLM 调用
if (gc.nodeActive && node.type === "llm") {
if (context.phase === "llm_result") {
return this.onNodeComplete(state, gc);
}
return this.innerAgent.runner(context, state);
}
// 4. 过渡阶段:开始下一个节点
return this.startNode(gc, state);
}

Agent 节点 vs LLM 节点

GraphAgent 的 ReasoningGraph 包含两种节点类型:

节点类型Prompt 发送工具输出
agent不含 JSON schema启用(自由调用)工具循环结束后额外 LLM 调用提取结构化输出
llm含 JSON schema禁用单轮结构化输出

设计决策:agent 节点不发送 JSON schema 约束,让 Agent 自由使用工具;工具循环结束后再做一次 LLM 调用(extraction phase)从对话中提取结构化输出。这比直接在 agent 节点发送 JSON schema 更灵活——Agent 可以先搜索、分析、再总结,而不是被 schema 约束。

回溯限制机制

GraphAgent 支持图中回溯(backtrack),但有数量限制:

// 伪代码:回溯检查
gc.visitCount[gc.currentNode]++;
if (gc.visitCount[gc.currentNode] > 1) {
gc.backtrackCount++;
}
// 在 evaluateTransitions 中:
if (isBacktrack && gc.backtrackCount >= this.graph.maxBacktracks) continue;

回溯时清除中间节点存储的数据,避免使用过期的中间结果。

图上下文持久化

GraphAgent 将图执行状态存储在 state.metadata 中:

// 伪代码:图上下文
interface GraphContext {
currentNode: string; // 当前节点 ID
nodeActive: boolean; // 节点是否活跃
store: Record<string, any>; // 节点输出存储
backtrackCount: number; // 回溯计数器
visitCount: Record<string, number>; // 每节点访问计数
input: string; // 用户输入
extracting: boolean; // 是否在提取阶段
}

问题与规避

问题场景规避策略
JSON 解析失败agent 节点提取阶段,LLM 未返回合法 JSONfallback 到 { _raw: content },保留原始内容
无有效转换所有 transition condition 都不满足fallback 到线性推进(下一个节点)
回溯超限图中循环导致无限回溯跳过超限转换,继续线性推进
节点不存在图定义中的节点 ID 不存在返回 error_recovery finish

设计取舍

为什么用装饰器模式而非重写?

GraphAgent 内部委托 GeneralChatAgent 处理工具调用循环,而非重写整个循环逻辑。

优势

  • agent 节点复用 GeneralChatAgent 的完整功能(干预检查、压缩、abort 处理)
  • 减少代码重复
  • 两个 Agent 可以独立测试

代价

  • GraphAgent 需要拦截 GeneralChatAgent 的 finish 指令
  • 图上下文与 GeneralChatAgent 的状态管理耦合

为什么 agent 节点不用 JSON schema 约束?

直接在 agent 节点发送 JSON schema 会限制 Agent 使用工具的灵活性。LobeHub 选择在工具循环结束后做额外的 LLM 调用提取结构化输出。

代价:每次 agent 节点完成需要额外一次 LLM 调用。 收益:Agent 可以自由搜索、分析,最终输出才受 schema 约束。

参考来源

  • LobeHub GraphAgent 源码 — packages/agent-runtime/src/agents/GraphAgent.ts
  • State Graph Agent Loop — 通用状态图 Agent 循环原理