ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

从零搭建AI Agent平台:React+Next.js+FastAPI实战指南

从零搭建AI Agent平台:React+Next.js+FastAPI实战指南 1. 为什么Agent 工厂这个思路值得认真对待过去一年我接触了不少团队做 AI Agent 落地发现一个很普遍的现象大家把 80% 的精力花在写 Prompt 和调模型上剩下 20% 才用来搭工程骨架。结果就是每个业务线各自造轮子A 团队做了一个客服 AgentB 团队做了一个数据分析 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 循环 终止条件这里要特别提一下MCPModel 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 自带的服务端路由可以做 BFFBackend 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_steps10): messages build_initial_messages(agent_config, user_input) for step in range(max_steps): # 1. 调用 LLM 获取下一步决策 response await llm_client.chat( messagesmessages, toolsagent_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( namequery_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 控制台让非技术人员也能配置 Agent5.1 页面结构规划控制台的核心页面不多但每个都要想清楚Agent 列表页展示所有 Agent支持搜索、筛选、状态标识Agent 编辑页配置系统提示、选择模型、挂载工具、设置参数调试页实时对话测试能看到每一步的工具调用和中间结果运行记录页历史任务列表可查看每次运行的完整 trace调试页是最有价值的因为它把 Agent 的思考过程可视化了。用户能看到 Agent 调用了哪个工具、传了什么参数、拿到了什么结果、下一步又做了什么决策。这种透明度对于排查问题至关重要。5.2 实时日志推送的实现Agent 运行时产生的中间步骤需要实时推送到前端。方案有两种SSEServer-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 场景只需要一两天。关键是要忍住一次做完美的冲动先跑通最小闭环再逐步迭代。
返回列表