AgentScope | LangGraph | FastAPI | ReAct | AI Coding Agent | 框架深度对比

你可能读过很多"框架介绍"文章,但很少有一篇告诉你:用两个框架做同一个东西,到底会碰到什么不同的问题。

Babi Agent 是一个 AI Coding Agent——能读写代码、执行 Shell、搜索网页、调用 GitHub API、通过 Skills 扩展能力。它有两个 Python 实现:

  • babi-agentscope:基于 AgentScope + FastAPI
  • babi-langgraph:基于 LangGraph + FastAPI

两个项目共享同一套设计理念——相同的工具集、相同的 Skills 系统、相同的系统提示词结构、相同的前端页面——但底层框架完全不同。

这篇文章不是 API 文档的搬运,而是从真实工程实践中提炼出的框架选型洞察。


🎯 为什么要用两个框架写同一个 Agent?

动机很简单:如果只用一个框架,你永远不知道它的 API 是"设计得好"还是"你习惯了"。

AgentScope 和 LangGraph 都是 Python 生态中优秀的 Agent 框架,但它们的设计哲学截然不同。只有用同一套业务需求把两个框架都跑通,你才能回答这些真实问题:

  • 创建一个 ReAct Agent,两个框架分别要写几行代码?
  • 文件系统工具(读/写/编辑/搜索),框架帮你做了多少?
  • 会话持久化,两个框架的心智模型有什么本质区别?
  • Web 层集成,谁让你写更多的"胶水代码"?
  • 当你想加一个自定义路由(比如清除会话),谁的扩展更自然?

📐 第一层对比:Agent 构建——“配置驱动” vs “函数组装”

AgentScope:声明式配置 + 框架托管

# babi-agentscope/babi/agent/builder.py
from agentscope.agent import Agent, ContextConfig, ReActConfig
from agentscope.tool import Toolkit, FunctionTool

agent = Agent(
    name="BabiAgent",
    system_prompt=sys_prompt,
    model=DashScopeChatModel(
        credential=DashScopeCredential(api_key=settings.dashscope_api_key),
        model=settings.model_name,
        stream=True,
        max_retries=settings.max_retries,
    ),
    toolkit=Toolkit(tools=[
        Bash(), Grep(), Glob(), Read(), Write(), Edit(),  # 框架内置工具
        FunctionTool(fetch_url),                            # 自定义工具
        FunctionTool(http_request),
        FunctionTool(web_search),
    ]),
    context_config=ContextConfig(),
    react_config=ReActConfig(),
    state=AgentState(
        permission_context=PermissionContext(mode=PermissionMode.BYPASS),
    ),
)

关键观察:AgentScope 的 Agent 是一个"重量级对象"——它内部封装了 ReAct 循环、上下文管理、权限控制、状态持久化。你通过配置项告诉它"要什么",框架帮你处理"怎么做"。

FunctionTool(fetch_url) 是一个特别优雅的设计——任何普通 Python 函数,一行代码变成 Agent 可调用的工具,不需要装饰器,不需要继承基类。

LangGraph:函数式组合 + 最小封装

# babi-langgraph/babi/agent/builder.py
from langchain.agents.factory import create_agent

agent = create_agent(
    model=llm,
    tools=tools,
    checkpointer=checkpointer,
    system_prompt=sys_prompt,
)

一行代码创建一个 Agent——但这一行背后是完全不同的设计哲学。

LangGraph 的 create_agent 不创建"对象",它返回一个 CompiledGraph(编译后的状态图)。Agent 不是一个有状态的实体,而是图的一次执行。状态由 Checkpointer 管理,模型是 LangChain 的 ChatOpenAI,工具是 @tool 装饰的函数。

# LangGraph 的工具注册方式
from langchain_core.tools import tool

@tool
def read_file(file_path: str) -> str:
    """Read the contents of a file."""
    try:
        path = os.path.expanduser(file_path)
        with open(path, "r", encoding="utf-8", errors="replace") as f:
            content = f.read()
        return content[:50000] if len(content) > 50000 else content
    except Exception as e:
        return f"Error reading file: {e}"

本质差异:AgentScope 说"我帮你管理 Agent 的生命周期",LangGraph 说"我给你原语,你自己组装"。


🔧 第二层对比:工具系统——“框架内置” vs “全部手写”

这是两个项目代码量差异最大的地方。

代码量对比

工具 babi-agentscope babi-langgraph
读文件 Read() — 框架提供 read_file() — 手写 17 行
写文件 Write() — 框架提供 write_file() — 手写 14 行
编辑文件 Edit() — 框架提供 edit_file() — 手写 27 行
代码搜索 Grep() — 框架提供 grep_files() — 手写 42 行
Glob Glob() — 框架提供 glob_files() — 手写 21 行
Shell 执行 Bash() — 框架提供 shell_execute() — 手写 34 行
基础工具总代码量 0 行 ~155 行

babi-langgraph 有一个完整的 filesystem.py(187 行),专门实现文件系统工具。而 babi-agentscope 只需要从框架导入 ReadWriteEditGrepGlobBash

edit_file 为例,看看 LangGraph 版本需要处理哪些细节:

# babi-langgraph/babi/tools/filesystem.py
@tool
def edit_file(file_path: str, old_text: str, new_text: str) -> str:
    """Edit a file by replacing exact text matches."""
    try:
        path = os.path.expanduser(file_path)
        with open(path, "r", encoding="utf-8") as f:
            content = f.read()

        count = content.count(old_text)
        if count == 0:
            return f"Error: Text not found in {file_path}."
        if count > 1:
            return f"Error: Text found {count} times. old_text must be unique."

        new_content = content.replace(old_text, new_text, 1)
        with open(path, "w", encoding="utf-8") as f:
            f.write(new_content)
        return f"Successfully edited {file_path}"
    except FileNotFoundError:
        return f"Error: File not found: {file_path}"
    except Exception as e:
        return f"Error editing file: {e}"

这段代码看起来简单,但它涉及精确文本匹配、唯一性校验、异常处理、路径展开——这些都是 AgentScope 框架帮你做好的。

工程洞察:如果你只关心"差异化能力"(网页抓取、GitHub 集成、搜索),AgentScope 让你只写差异化代码。LangGraph 给你最大的自由度,但也要求你写最多的"基础设施代码"。


🗄️ 第三层对比:会话持久化——“Redis 原生” vs “Checkpointer 抽象”

这是两个框架心智模型差异最大的地方。

AgentScope:Redis 键值存储 + 显式状态管理

# babi-agentscope/babi/web/app.py
from agentscope.app.storage import RedisStorage

storage = RedisStorage(host="localhost", port=6379)

# 启动时 bootstrap:credential → agent → session
async def bootstrap_babi(settings):
    async with storage:
        # 1. 创建凭证
        credential = DashScopeCredential(id="dashscope-default", api_key=...)
        await storage.upsert_credential(user_id, credential)

        # 2. 创建 Agent 记录(固定 ID,避免重启后失忆)
        agent_data = AgentData(name="BabiAgent", system_prompt=sys_prompt, ...)
        agent_record = AgentRecord(id="babi-agent", user_id=user_id, data=agent_data)
        await storage.upsert_agent(user_id, agent_record)

        # 3. 创建默认 Session(仅当不存在时)
        existing = await storage.get_session(user_id, agent_id, "default")
        if existing is None:
            await storage.upsert_session(user_id, agent_id, session_id="default", ...)

AgentScope 的会话模型是显式的三层结构:Credential → Agent → Session。你需要在 Redis 中预先创建这些记录,Chat 接口才能工作。

清除会话也需要直接操作 Redis:

# 清除会话:直接操作 Redis 键
msg_key = f"agentscope:user:{user_id}:session:{session_id}:messages"
await r.delete(msg_key)

session_key = f"agentscope:user:{user_id}:session:{session_id}"
raw = await r.get(session_key)
session_data = json.loads(raw)
session_data["state"]["context"] = []  # 清空对话历史
await r.set(session_key, json.dumps(session_data))

优势:完全掌控数据结构,可以精确清理消息、上下文、摘要。
代价:需要理解 Redis 键的命名规则,代码更"底层"。

LangGraph:Checkpointer 模式 + 图状态快照

# babi-langgraph/babi/agent/builder.py
from langgraph.checkpoint.memory import MemorySaver
from langgraph.checkpoint.postgres.aio import AsyncPostgresSaver

# 生产环境:PostgreSQL
checkpointer_cm = AsyncPostgresSaver.from_conn_string(pg_dsn)

# 开发环境:内存
checkpointer = MemorySaver()

# 创建 Agent 时传入 checkpointer
agent = create_agent(model=llm, tools=tools, checkpointer=checkpointer, ...)

LangGraph 的会话管理是一个黑盒 Checkpointer——你不需要知道状态怎么存、怎么取,框架在每次图执行时自动保存快照。

清除会话也更简洁:

# 清除会话:调用 checkpointer 的删除方法
if hasattr(target, 'adelete_thread'):
    await target.adelete_thread(session_id)

优势:开发者不需要关心存储细节,PostgreSQL / Memory 一键切换。
代价:你无法精确控制"只清消息不清状态"——Checkpointer 是原子性的。

本质差异:AgentScope 把会话管理当作你的责任(给你 Redis 客户端,你自己操作);LangGraph 把会话管理当作框架的责任(给你 Checkpointer 接口,你不用关心实现)。


🌊 第四层对比:流式输出——“MessageBus 事件驱动” vs “astream_events 直推”

AgentScope:Fire-and-Forget + MessageBus

# AgentScope 的 chat 接口(框架内部 _chat.py)
# 不返回 SSE 流,而是触发后台任务
@chat_router.post("/")
async def chat(request: ChatRequest, ...):
    chat_run_registry.spawn(
        chat_service.run(
            user_id=user_id,
            session_id=request.session_id,
            agent_id=request.agent_id,
            input_msg=request.input,
        ),
        session_id=request.session_id,
    )
    return ChatTriggerResponse(status="started", session_id=request.session_id)

AgentScope 的 chat 接口是异步触发模式——POST 请求立即返回,Agent 在后台执行,事件通过 InMemoryMessageBus 推送到 GET /sessions/{sid}/stream SSE 长连接。

这意味着前端需要维护两个连接:一个 POST 发消息,一个 GET 收事件。

LangGraph:astream_events 一体化

# babi-langgraph/babi/web/app.py
@app.post("/api/chat")
async def chat(request: ChatRequest, ...):
    async def event_generator():
        async for event in agent.astream_events(
            {"messages": [("user", request.message)]},
            config=config,
            version="v2",
        ):
            kind = event.get("event")
            if kind == "on_chat_model_stream":
                chunk = event["data"]["chunk"]
                yield f"data: {json.dumps({'type': 'token', 'content': chunk.content})}\n\n"
            elif kind == "on_tool_start":
                yield f"data: {json.dumps({'type': 'tool_call', 'tool': event['name']})}\n\n"

    return StreamingResponse(event_generator(), media_type="text/event-stream")

LangGraph 的 astream_events同步流模式——POST 请求本身就是一个 SSE 流,事件从 Agent 内部直接产生,无需中间件。

前端只需要一个连接:POST 发消息,同一个连接收 SSE 事件。

工程洞察

维度 AgentScope (MessageBus) LangGraph (astream_events)
连接模型 2 个(POST + GET SSE) 1 个(POST = SSE)
事件路由 MessageBus 分发 直接从 Agent 产生
多进程支持 ✅ 天然支持(Bus 可跨进程) ❌ 单进程(流绑定到请求)
断线重连 ✅ 有 replay log ❌ 丢失即丢失
代码复杂度 高(需理解 Bus 架构) 低(直接迭代事件流)

AgentScope 的 MessageBus 是为企业级场景设计的——多进程部署、断线重连、事件重放。但对于单进程的个人 Agent,LangGraph 的 astream_events 更简单直接。


🏗️ 第五层对比:Web 层——“全权委托” vs “完全自建”

AgentScope:create_app 一把梭

# babi-agentscope/babi/web/app.py
from agentscope.app import create_app

app = create_app(
    storage=storage,
    message_bus=message_bus,
    workspace_manager=workspace_manager,
    extra_agent_tools=extra_agent_tools,
    title="Babi Agent",
    version="1.0.0",
    extra_middlewares=[...],
)
# 然后追加自定义路由
@app.get("/api/workspace/tree")
async def workspace_tree(...): ...

AgentScope 的 create_app 帮你创建了完整的 Web 应用——包括 chat 路由、session 管理、credential 管理、model 管理、workspace 管理。你只需要"追加"自定义路由。

代价是:你需要阅读框架源码才能知道它到底注册了哪些路由。

LangGraph:从零搭建

# babi-langgraph/babi/web/app.py
app = FastAPI(title="Babi Agent", version="1.0.0", lifespan=lifespan, ...)

@app.post("/api/chat")
async def chat(request: ChatRequest, ...): ...

@app.get("/api/workspace/tree")
async def workspace_tree(...): ...

@app.get("/api/workspace/file")
async def workspace_file(...): ...

@app.delete("/api/chat/memory")
async def clear_memory(): ...

@app.delete("/api/chat/session")
async def clear_session(...): ...

LangGraph 版本的一切都是你自己写的——chat 接口、工作区 API、会话管理。好处是完全透明,坏处是代码量更大。


📊 全景对比矩阵

维度 babi-agentscope babi-langgraph
Agent 创建 Agent(...) 配置驱动 create_agent(...) 一行创建
ReAct 引擎 框架内置 create_react_agent
基础工具 框架提供(0 行代码) 全部手写(~187 行)
自定义工具注册 FunctionTool(func) @tool 装饰器
会话持久化 Redis(显式键值操作) PG/Memory(Checkpointer 黑盒)
流式输出 MessageBus + SSE 长连接 astream_events 直推
Web 框架 create_app 托管 + 自定义路由 完全自建 FastAPI
模型接入 DashScope 原生 SDK ChatOpenAI 兼容 API
CLI 流式输出 agent.reply_stream() 事件迭代 agent.astream_events() 事件迭代
多进程部署 ✅ 天然支持 ❌ 需额外设计
断线重连 ✅ replay log ❌ 不支持
依赖复杂度 高(AgentScope SDK + Redis) 中(LangGraph + LangChain + 可选 PG)

💡 真实工程洞察

洞察一:框架的"开箱能力"决定了你的"差异化空间"

AgentScope 帮你做好了 Read/Write/Edit/Grep/Glob/Bash,你只需要写 fetch_urlgithub_api_request 这些差异化能力。

LangGraph 要求你写所有工具——包括 read_file 这种"基础设施"。

类比 Java:AgentScope 像 Spring Boot(约定大于配置),LangGraph 像裸 Servlet API(自由但啰嗦)。

洞察二:会话管理是框架选型的核心分水岭

AgentScope 的会话模型是显式的——你需要理解 user_id → agent_id → session_id 的三层关系,需要在 Redis 中 bootstrap 数据。这很"重",但也很"透明"。

LangGraph 的会话模型是隐式的——Checkpointer 自动保存/恢复,你只需要一个 thread_id。这很"轻",但你无法精细控制。

一个真实的坑:在 babi-agentscope 中,如果 Agent 启动时不指定固定 ID(AgentRecord(id="babi-agent", ...)),每次重启会生成随机 UUID,导致 Redis 中积累大量孤立 Agent 记录,Session 指向旧 Agent,造成"AI 重启后失忆"。这种坑在 LangGraph 中不存在——因为 Checkpointer 只认 thread_id

洞察三:流式输出的架构选择影响前端设计

AgentScope 的双连接模型(POST 发消息 + GET SSE 收事件)要求前端维护 EventSource 长连接,处理断线重连和事件重放。

LangGraph 的单连接模型(POST 即 SSE)让前端更简单——一个 fetch + ReadableStream 就够了。

但如果你的 Agent 需要多进程部署(比如 Gunicorn + 多个 worker),AgentScope 的 MessageBus 天然支持跨进程事件分发,而 LangGraph 的 astream_events 绑定在单个请求上,无法跨 worker。

洞察四:Skills 系统是跨框架的"不变量"

两个项目的 Skills 加载逻辑几乎是一比一的翻译

# 两个项目中几乎相同的代码
def load_all_skills(workspace_path=None):
    skills = {}
    _load_from_dir(Path.home() / ".agents" / "skills", skills)    # 全局
    _load_from_dir(Path.home() / ".babi" / "skills", skills)      # Babi 专属
    if workspace_path:
        _load_from_dir(workspace_path / ".qoder" / "skills", skills)  # 项目级
    return skills

这证明了一个道理:Agent 框架影响的是"基础设施层"(工具、会话、流式输出),而不是"业务设计层"(Skills、提示词、工具分类)。好的业务设计是跨框架的。


🎓 选型建议:你应该选哪个?

选 AgentScope,如果你:

  • ✅ 需要一个生产级的 Agent 平台(多进程、断线重连、事件重放)
  • ✅ 希望少写基础设施代码,专注差异化能力
  • ✅ 需要框架级的权限控制(PermissionContext)和工作区管理
  • ✅ 愿意接受框架的"约定"(Redis 必须、bootstrap 流程)
  • ✅ 团队有 Python 经验,能读懂框架源码排查问题

选 LangGraph,如果你:

  • ✅ 追求最小依赖最大透明度
  • ✅ 希望完全掌控每一个 API 端点和存储细节
  • ✅ 需要灵活的持久化选择(Memory / PostgreSQL 一键切换)
  • ✅ 偏好函数式编程风格而非面向对象
  • ✅ 已经在用 LangChain 生态(ChatOpenAI、@tool 等)

一句话总结

AgentScope 是"Agent 即服务"——你配置,它运行。LangGraph 是"Agent 即代码"——你组装,它执行。


🚦 快速体验

babi-agentscope(需要 Redis)

export DASHSCOPE_API_KEY=your_api_key

git clone https://github.com/javahongxi/babi-agentscope.git
cd babi-agentscope
pip install -e ".[dev]"

# CLI 模式
babi-as

# Web 模式(需要 Redis 运行在 localhost:6379)
babi-as --web

babi-langgraph(零外部依赖)

export DASHSCOPE_API_KEY=your_api_key

git clone https://github.com/javahongxi/babi-langgraph.git
cd babi-langgraph
pip install -e ".[dev]"

# CLI 模式(内存持久化,零依赖)
babi-lg

# Web 模式(可选 PostgreSQL)
export BABI_PG_DSN=postgresql://user:pass@localhost:5432/babi  # 可选
babi-lg --web

试试这些

# 代码分析
帮我看看当前项目的目录结构

# 代码搜索
搜索所有包含 @RestController 的文件

# GitHub 操作
查看我的 GitHub 置顶仓库

# 联网搜索
搜索 AgentScope 的最新版本

# Skills 扩展
# 将 .md 文件放到 ~/.babi/skills/ 目录,两个版本自动加载同一份技能

🔗 相关链接


📝 结语

用两个框架写同一个 Agent 之后,最大的收获不是"谁更好",而是理解了它们各自的设计权衡

  • AgentScope 用"重框架"换"轻业务代码"——你少写 187 行工具代码,但要多理解一套 Redis 键值模型
  • LangGraph 用"轻框架"换"重业务代码"——你多写所有基础设施,但获得完全的透明度和控制力

如果你正在

  • 🎯 深入理解 Python AI Agent 框架的设计哲学差异
  • 🚀 在 AgentScope 和 LangGraph 之间做技术选型
  • 🏗️ 搭建自己的 Coding Agent,需要一套经过验证的架构参考
  • 🤔 好奇"同一个需求用两个框架实现,代码到底差多少"

Star ⭐ 两个仓库,对比运行,你会对 Agent 框架有全新的理解。

git clone https://github.com/javahongxi/babi-agentscope.git
git clone https://github.com/javahongxi/babi-langgraph.git

© hongxi.org | 同一个 Agent,两个框架——理解差异,才能做出选择

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐