☰
从零搭建AI Agent平台:React+Next.js+FastAPI实战指南
2026/9/26 18:56:14 网站建设 项目流程

1. 为什么"Agent 工厂"这个思路值得认真对待

过去一年我接触了不少团队做 AI Agent 落地,发现一个很普遍的现象:大家把 80% 的精力花在写 Prompt 和调模型上,剩下 20% 才用来搭工程骨架。结果就是每个业务线各自造轮子,A 团队做了一个客服 Agent,B 团队做了一个数据分析 Agent,代码结构完全不一样,连日志格式都对不齐。等到老板说"我们能不能把这些 Agent 统一管理起来",才发现根本没有"平台"这回事,只有一堆散落的脚本。

"Agent 工厂"这个说法我很喜欢,因为它把问题定义得很准确:你需要的不是再写一个更聪明的 Agent,而是一条能批量生产 Agent 的流水线。就像制造业从手工作坊进化到流水线一样,核心变化不是工人变聪明了,而是生产流程标准化了——每个工位只负责一件事,零件可以互换,质检有统一标准。

放到技术语境里,这条流水线要解决四件事:Agent 怎么定义、工具怎么挂载、记忆怎么管理、运行怎么隔离。这四件事如果不定清楚,你做的就永远是一次性项目,而不是平台。本文会围绕一个可落地的技术栈组合——React + Next.js 做前端控制台,FastAPI 做后端编排层——把从零搭建 Agent 平台的完整思路拆开讲。适合已经了解 LLM 基本调用、想往平台化方向走的开发者,也适合正在做企业内部 Agent 中台选型的技术负责人。

先说一个反直觉的结论:Agent 平台最难的部分不是 Agent 本身,而是"抽象边界"的设计。抽象做浅了,每个 Agent 都要写一堆重复代码;抽象做深了,业务方想加个自定义逻辑就得改平台核心。这个度怎么把握,后面会结合具体代码结构详细说。

2. 先把概念理清楚:Agent、LLM、AI 模型到底什么关系

2.1 三层结构:模型是发动机,LLM 是整车,Agent 是司机

很多刚入门的朋友会问:"DeepSeek 属于 Agent 还是 LLM?"这个问题本身就说明概念还没分层。我用一个类比来讲:

  • AI 模型是最底层的数学结构,比如 Transformer 架构训练出来的权重文件。它本身不会说话,只会做矩阵运算。
  • LLM(大语言模型)是在 AI 模型基础上,经过大规模文本训练、具备语言理解和生成能力的模型。DeepSeek、GPT 系列、Claude 系列都属于这一层。你可以把它理解成一台发动机。
  • Agent是在 LLM 之上加了一层"决策循环 + 工具调用 + 记忆管理"的系统。它不只是回答问题,而是会拆解任务、选择工具、观察结果、决定下一步。相当于给发动机装上了方向盘、油门和导航,变成了能自己开的车。

所以 DeepSeek 是 LLM,不是 Agent。但你可以用 DeepSeek 作为推理引擎去构建一个 Agent。这个区分很重要,因为平台设计时,模型层和 Agent 层必须是解耦的——今天用 DeepSeek,明天换别的模型,Agent 的逻辑不应该受影响。

2.2 Agent 的最小组成结构

一个能跑起来的 Agent,最少需要四个部件:

部件作用常见实现方式
推理引擎决定下一步做什么LLM API 调用
工具集能执行的具体动作函数注册表 / MCP 协议
记忆保存上下文和历史短期对话缓冲 + 长期向量库
循环控制驱动"思考-行动-观察"迭代代码里的 while 循环 + 终止条件

这里要特别提一下MCP(Model Context Protocol)。它本质上是一套标准化的工具描述协议,让 Agent 能用统一的方式发现和调用外部能力。你可以把它类比成 USB 接口——以前每个设备都有自己的插头,现在统一成 USB-C,插上就能用。平台如果支持 MCP,第三方工具接入的成本会大幅降低。

2.3 为什么平台化比单点开发更划算

算一笔账:假设你有 10 个业务场景需要 Agent,每个 Agent 单独开发平均需要 5 人天,总共 50 人天。如果先花 15 人天搭一个平台,之后每个 Agent 只需要 1 人天配置,总共 25 人天。省下来的不只是时间,更重要的是维护成本——当模型 API 变更、当安全策略调整、当需要统一加日志埋点,平台化方案只需要改一处。

但这里有个前提:你的 Agent 数量要足够多,或者预期会持续增长。如果只是做一个一次性 Demo,搭平台反而是过度设计。我见过不少团队为了"平台化"而平台化,最后平台本身成了最大的技术债。

3. 技术选型:为什么是 React + Next.js + FastAPI 这套组合

3.1 前端选 Next.js 而不是纯 React SPA 的理由

纯 React SPA 当然能做 Agent 控制台,但 Next.js 带来几个实际好处:

第一是 SSR 数据预获取。Agent 平台的控制台首页通常要展示 Agent 列表、运行状态、最近任务记录。如果用 SPA,用户打开页面会先看到白屏,等 JS 加载完再发请求拿数据。Next.js 的getServerSideProps或 App Router 里的 Server Component 可以在服务端就把数据取好,首屏直接渲染。对于内部平台来说,这种体验差异很明显。

第二是 API Routes 的便利性。Next.js 自带的服务端路由可以做 BFF(Backend for Frontend)层,把 FastAPI 返回的原始数据做一层聚合和裁剪,前端组件拿到的就是刚好需要的数据结构。这样前端不用关心后端接口的粒度问题。

第三是部署简单。Next.js 可以打包成一个 Node 服务,和 FastAPI 一起用 Docker Compose 编排,运维成本低。

不过要注意一个坑:Next.js 的 Server Component 和 Client Component 边界要划清楚。Agent 运行时的实时日志流、WebSocket 连接这些必须放在 Client Component 里,而 Agent 列表、配置详情这类静态数据适合放 Server Component。我见过有人把所有东西都写成 Client Component,那用 Next.js 就没意义了。

3.2 后端选 FastAPI 的核心考量

FastAPI 在 Agent 平台场景下有天然优势:

  • 原生异步支持:Agent 执行过程中大量涉及 LLM API 调用、工具执行、数据库读写,这些都是 IO 密集型操作。FastAPI 基于 asyncio,能用一个进程处理大量并发请求,不像 Flask 那样需要开多线程。
  • Pydantic 数据校验:Agent 的输入输出结构复杂,用 Pydantic 定义 Schema 后,请求校验、序列化、文档生成全部自动完成。
  • 自动生成 OpenAPI 文档:前端对接时直接看/docs就行,省去手写接口文档的麻烦。
  • 和 SQLAlchemy 配合成熟:Agent 配置、运行记录、用户信息这些都需要持久化,SQLAlchemy 2.0 的异步模式配合 FastAPI 很顺。

3.3 整体架构分层

┌─────────────────────────────────────┐ │ Next.js 控制台(Agent 管理界面) │ ├─────────────────────────────────────┤ │ FastAPI 编排层(API + 调度) │ ├──────────┬──────────┬───────────────┤ │ Agent │ 工具 │ 记忆 │ │ 运行时 │ 注册中心 │ 服务 │ ├──────────┴──────────┴───────────────┤ │ 模型适配层(统一 LLM 调用接口) │ ├─────────────────────────────────────┤ │ PostgreSQL + Redis + 向量库 │ └─────────────────────────────────────┘

这个分层的核心原则是:每一层只依赖它下面一层,不跨层调用。Agent 运行时不应该直接调 LLM API,而是通过模型适配层;控制台不应该直接查数据库,而是通过 FastAPI。

4. FastAPI 后端:Agent 运行时的核心设计

4.1 项目目录结构怎么定

FastAPI 项目最容易犯的错是把所有路由塞在一个main.py里。Agent 平台涉及的概念多,目录结构必须一开始就规划好:

backend/ ├── app/ │ ├── main.py # 应用入口 │ ├── config.py # 配置管理 │ ├── api/ │ │ ├── v1/ │ │ │ ├── agents.py # Agent CRUD │ │ │ ├── runs.py # 运行记录 │ │ │ ├── tools.py # 工具管理 │ │ │ └── chat.py # 对话接口 │ ├── core/ │ │ ├── agent_runtime.py # Agent 执行引擎 │ │ ├── tool_registry.py # 工具注册中心 │ │ └── memory.py # 记忆管理 │ ├── models/ # SQLAlchemy 模型 │ ├── schemas/ # Pydantic Schema │ ├── services/ # 业务逻辑 │ └── adapters/ # 模型适配层 ├── alembic/ # 数据库迁移 └── tests/

这个结构的关键在于core/和services/的分离。core/放的是与业务无关的通用能力(Agent 怎么跑、工具怎么注册),services/放的是具体业务逻辑(创建 Agent 时要校验什么、运行记录怎么存)。这样当你想把 Agent 运行时抽成独立服务时,直接搬core/就行。

4.2 Agent 执行引擎的循环逻辑

Agent 的核心就是一个循环:思考 → 行动 → 观察 → 再思考。用伪代码表示:

async def run_agent(agent_config, user_input, max_steps=10): messages = build_initial_messages(agent_config, user_input) for step in range(max_steps): # 1. 调用 LLM 获取下一步决策 response = await llm_client.chat( messages=messages, tools=agent_config.tools, ) # 2. 如果 LLM 决定直接回复,结束循环 if not response.tool_calls: return response.content # 3. 执行工具调用 for tool_call in response.tool_calls: result = await execute_tool(tool_call) messages.append(build_tool_result_message(tool_call, result)) return "达到最大步数限制,任务未完成"

这段逻辑看起来简单,但有几个细节决定成败:

max_steps 必须设上限。我见过 Agent 陷入死循环,反复调用同一个工具几十次,烧掉大量 token。10 步是个比较安全的默认值,复杂任务可以调到 20,但不要不设限。

工具执行要加超时。某个工具卡住了,整个 Agent 就挂在那里。每个工具调用都应该包一层asyncio.wait_for,超时后返回一个错误信息给 LLM,让它决定是重试还是换方案。

消息历史要控制长度。多轮工具调用后,messages 列表会变得很长,超出模型上下文窗口。需要在每轮循环后做一次裁剪,保留系统提示、原始问题、最近几轮交互,中间的工具调用结果可以摘要化。

4.3 工具注册中心的设计

工具是 Agent 的手脚。平台化的关键不是内置多少工具,而是让业务方能自己注册工具。我的做法是用装饰器 + 自动发现:

from app.core.tool_registry import register_tool @register_tool( name="query_database", description="根据 SQL 查询数据库并返回结果", parameters={ "sql": {"type": "string", "description": "要执行的 SQL 语句"} } ) async def query_database(sql: str) -> str: # 实际执行逻辑 ...

register_tool装饰器做三件事:把函数加入全局注册表、从函数签名和参数定义生成 JSON Schema、包装异常处理。这样新增工具只需要写一个函数加一个装饰器,不用改任何平台代码。

注意:工具的参数 Schema 一定要写清楚 description,这是 LLM 判断该不该调用这个工具的唯一依据。description 写得含糊,Agent 就会乱调工具。

4.4 记忆管理的两层结构

Agent 的记忆分短期和长期:

短期记忆就是当前对话的消息列表,存在 Redis 里,key 是 session_id,设置合理的过期时间(比如 2 小时)。用 Redis 而不是内存字典,是因为 FastAPI 可能跑多个 worker 进程,内存不共享。

长期记忆是把历史对话中值得保留的信息向量化后存入向量库。检索时用当前问题做相似度搜索,把最相关的几条历史注入到 prompt 里。这里要注意的是,不是所有对话都值得存长期记忆,需要加一个判断逻辑——比如让 LLM 自己判断"这段对话是否包含值得长期记住的用户偏好或事实信息"。

5. Next.js 控制台:让非技术人员也能配置 Agent

5.1 页面结构规划

控制台的核心页面不多,但每个都要想清楚:

  • Agent 列表页:展示所有 Agent,支持搜索、筛选、状态标识
  • Agent 编辑页:配置系统提示、选择模型、挂载工具、设置参数
  • 调试页:实时对话测试,能看到每一步的工具调用和中间结果
  • 运行记录页:历史任务列表,可查看每次运行的完整 trace

调试页是最有价值的,因为它把 Agent 的"思考过程"可视化了。用户能看到 Agent 调用了哪个工具、传了什么参数、拿到了什么结果、下一步又做了什么决策。这种透明度对于排查问题至关重要。

5.2 实时日志推送的实现

Agent 运行时产生的中间步骤需要实时推送到前端。方案有两种:

SSE(Server-Sent Events)适合单向推送场景,实现简单,浏览器原生支持 EventSource。FastAPI 侧用StreamingResponse就能实现。

WebSocket适合需要双向通信的场景,比如用户在 Agent 运行过程中想中断或补充信息。

我的建议是默认用 SSE,因为 Agent 运行日志本质上是单向的。只有在需要"运行中干预"这种高级功能时才上 WebSocket。SSE 的一个坑是:Next.js 的 API Route 默认会缓冲响应,需要在返回头里加X-Accel-Buffering: no并确保没有中间层做缓冲。

5.3 前端状态管理的取舍

Agent 配置表单的状态比较复杂——有基本信息、模型参数、工具列表、提示词模板等多个区块。用 React 原生的 useState 管理会很快变得混乱。

我的经验是:表单状态用 react-hook-form,全局状态用 zustand。react-hook-form 对复杂表单的支持很好,性能优化到位(非受控组件减少重渲染)。zustand 比 Redux 轻量得多,对于控制台这种中等复杂度的应用足够了。

不要用 Context 管理频繁变化的状态,比如 Agent 运行时的实时日志。Context 的值一变,所有消费者都重渲染,日志高频更新时页面会卡。这类状态应该放在组件本地或者用 ref 管理。

6. 踩坑实录:搭建过程中最容易翻车的几个地方

6.1 模型适配层的抽象陷阱

一开始我想做一个"万能"的模型适配层,支持所有主流 LLM 的统一接口。结果发现不同模型的工具调用格式差异很大——有的用tool_calls字段,有的用function_call,参数结构也不一样。强行统一的结果是适配层变得极其复杂,加一个新模型要改一堆判断逻辑。

后来我调整了策略:适配层只统一"输入消息格式"和"输出结果格式",中间的 API 调用各写各的。每个模型一个 adapter 类,实现同一个接口,内部怎么调是它自己的事。这样加新模型的成本就降到了写一个类。

6.2 工具调用的参数校验缺失

早期版本我直接把 LLM 返回的工具参数传给函数执行,结果经常报错——LLM 有时会传多余字段,有时会漏字段,有时类型不对。后来在工具执行前加了一层 Pydantic 校验:

async def execute_tool(tool_call): tool = registry.get(tool_call.name) try: validated = tool.param_model(**tool_call.arguments) except ValidationError as e: return f"参数错误:{e}" return await tool.func(**validated.model_dump())

校验失败时不是抛异常,而是把错误信息返回给 LLM,让它自己修正参数重试。这个改动让工具调用的成功率提升了很多。

6.3 并发运行时的资源竞争

多个 Agent 同时运行时,如果它们共享某些资源(比如同一个数据库连接池、同一个浏览器实例),会出现竞争问题。我遇到过一次:两个 Agent 同时调用浏览器工具,互相抢页面控制权,结果都失败了。

解决方案是给每个 Agent 运行实例分配独立的资源上下文。浏览器实例用池化管理,每次运行从池里借一个,用完归还。数据库连接用 SQLAlchemy 的 session 隔离,每个请求一个 session。

6.4 前端 React 报错排查

热词里提到的minified react error #130是个经典问题,通常是因为组件导入方式不对——比如把具名导出当默认导出用,或者循环依赖导致组件为 undefined。在开发环境用非压缩版 React 能看到完整错误信息,生产环境只能看到错误码。建议在 Next.js 配置里开启reactStrictMode,很多问题在开发阶段就能暴露。

7. 从能跑到好用:平台化的进阶方向

7.1 Agent 版本管理

当 Agent 配置被频繁修改时,你需要知道"这次运行用的是哪个版本的配置"。做法是每次保存 Agent 时生成一个不可变的版本快照,运行记录里关联版本 ID。这样出问题时可以回溯到具体配置,也支持一键回滚。

7.2 多智能体协作

单个 Agent 能力有限,复杂任务需要多个 Agent 分工。常见的模式是"主管 Agent + 执行 Agent"——主管负责拆解任务和分配,执行 Agent 各自负责一个子领域。平台层面需要支持 Agent 之间的消息传递和任务编排。这块目前还没有特别成熟的标准化方案,MCP 在工具层面做了标准化,但 Agent 之间的协作协议还在演进中。

7.3 权限与审计

企业内部使用时,不同用户能访问的 Agent、能调用的工具应该不同。需要在平台层加 RBAC 权限模型,并且记录完整的操作审计日志——谁在什么时候运行了哪个 Agent、调用了哪些工具、产生了什么结果。这不仅是安全要求,也是排查问题的依据。

7.4 成本监控

LLM 调用是花钱的。平台应该统计每个 Agent、每个用户的 token 消耗和费用,设置预算告警。我见过一个团队因为没做成本监控,某个月账单突然涨了十倍,排查后发现是一个测试 Agent 被误配置成了无限循环。

8. 一些实操中的个人体会

搭这个平台的过程中,我最大的感受是:平台的价值不在于功能多,而在于约束清晰。一个好的 Agent 平台应该让正确的做法变得容易,让错误的做法变得困难。比如强制要求工具定义参数 Schema、强制设置最大步数、强制记录运行日志——这些约束看起来是限制,实际上是在帮业务方避坑。

另一个体会是关于抽象层级的。我一开始总想设计一个"完美"的抽象,结果反复重构。后来想明白了:抽象应该从重复中提炼,而不是从想象中设计。先让两三个 Agent 跑起来,看看哪些代码是重复的,再把重复的部分抽出来。这样抽出来的抽象是经过验证的,不会过度设计。

最后说一个具体的技巧:Agent 的系统提示词不要写死在代码里,而是作为配置项存在数据库里。这样调整提示词不需要重新部署,业务方自己就能改。但要注意加版本控制,否则改坏了没法回滚。我现在习惯在提示词编辑页加一个"对比预览"功能,能看到修改前后的差异,确认无误再保存。

这套东西搭下来,从零到能用的平台大概需要两到三周,之后每接入一个新 Agent 场景只需要一两天。关键是要忍住"一次做完美"的冲动,先跑通最小闭环,再逐步迭代。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询