CubePi vs LangGraph
CubePi 和 LangGraph 都用 Python 构建会调用工具的 LLM agent,但出发点完全相反。LangGraph 要求你把 agent 表达成一张状态图 —— 手动连接节点、边和类型化通道。CubePi 则把同样的 agent 建模为一个可以从上读到下的普通 async while 循环。
如果你发现自己在用画图的方式去表达本质上线性的「调用模型 → 执行工具 → 重复」循环,CubePi 就是更精简的替代方案。下面是两者的对比。
并排对比
| LangGraph | CubePi | |
|---|---|---|
| 抽象模型 | 状态图:手动连接节点 + 边 + 通道 | 普通 async while 循环 —— run_agent_loop 从上读到下 |
| 控制流 | add_edge / add_conditional_edges 把循环具象成图 | 工具调用 → 再次提示 的循环本身就是运行时,无需具象化 |
| 流式输出 | 基于回调,多个 stream_mode 标志 | async for event in stream —— 统一模式,11 种事件类型 |
| 检查点 | 每步全量快照,序列化整个消息列表 | 追加式 —— 无论对话多长,每轮 O(1) DB I/O |
| 依赖项 | langchain-core、langgraph-sdk 及传递依赖 | 3 个核心依赖:pydantic、anthropic、openai |
| 工具 | 工具是需要手动连线的图节点(ToolNode) | 声明为函数;框架自动路由并并行执行 |
| 异步 | invoke / ainvoke 两套接口 | 异步优先 —— 每个入口都是 async |
| 可观测性 | LangSmith / Langfuse 集成 | 原生 OpenTelemetry —— GenAI semconv,开箱即用 OTLP / JSONL |
一个会调用工具的 agent
from langchain_anthropic import ChatAnthropic
from langgraph.graph import StateGraph, END
from langgraph.prebuilt import ToolNode
from langchain_core.tools import tool
@tool
def get_weather(city: str) -> str:
"""Get current weather for a city."""
return f"72F and sunny in {city}"
llm = ChatAnthropic(model="claude-sonnet-4-5").bind_tools([get_weather])
class State(TypedDict):
messages: list
def call_model(state):
return {"messages": [llm.invoke(state["messages"])]}
def should_continue(state):
return "tools" if state["messages"][-1].tool_calls else END
graph = StateGraph(State)
graph.add_node("llm", call_model)
graph.add_node("tools", ToolNode([get_weather]))
graph.add_edge("__start__", "llm")
graph.add_conditional_edges("llm", should_continue)
graph.add_edge("tools", "llm")
app = graph.compile()
from pydantic import BaseModel
from cubepi import Agent, AgentTool, AgentToolResult, TextContent
from cubepi.providers.anthropic import AnthropicProvider
class GetWeatherParams(BaseModel):
city: str
async def get_weather(tool_call_id, params: GetWeatherParams, *, signal=None, on_update=None):
return AgentToolResult(content=[TextContent(text=f"72F and sunny in {params.city}")])
agent = Agent(
model=AnthropicProvider(provider_id="anthropic", api_key="...").model("claude-sonnet-4-5"),
tools=[AgentTool(
name="get_weather",
description="Get current weather for a city.",
parameters=GetWeatherParams,
execute=get_weather,
)],
)
await agent.prompt("Weather in Tokyo?")
为什么用循环而不是图
LangGraph 的 agent 在运行时其实从不像通用图那样随意分支 —— 那张「图」几乎永远是同一个形状:调用模型,如果它请求了工具就执行,然后再次调用模型。CubePi 直接把这个形状变成运行时。没有 StateGraph、没有 END 哨兵、没有 should_continue 函数、没有 ToolNode 注册表,也没有需要同步维护的 State TypedDict。
确实需要变化的流程控制 —— 提前停止、生成总结、把某个工具放到人工审批之后 —— 都放在类型化的 middleware hook 里,而非条件边,因此是命令式的,也能单独测试。
不随对话增长的持久化
LangGraph 的 checkpointer 在每一步都对完整状态做快照,写入成本随对话长度线性增长。CubePi 的 checkpointing 是追加式的:无论线程多长,每轮写入都是 O(1),且消息保持 JSONB 可查询。MemorySaver / SqliteSaver / PostgresSaver 的思路对应到 MemoryCheckpointer / SQLiteCheckpointer / PostgresCheckpointer。
什么时候 LangGraph 更合适
如果你现在确实需要任意的多 agent supervisor 图、可视化图渲染,或在任意检查点做时间旅行/分叉,那么 LangGraph 更合适 —— CubePi 在设计上保持流程线性,并输出厂商中立的 OpenTelemetry,而不是自带一套 trace UI。但如果你的 agent 本质上就是一个循环,CubePi 帮你去掉了那些你其实没真正用上的图机制。