拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

从0到1搭建AI Agent平台:FastAPI+Next.js全栈实战与架构设计

从0到1搭建AI Agent平台:FastAPI+Next.js全栈实战与架构设计 1. 为什么我要自己搭一个 Agent 平台而不是直接用现成产品2025 年下半年到 2026 年初这段时间我陆续试用了市面上十几款 AI Agent 产品从通用型助手到垂直领域的编码 Agent、数据分析 Agent几乎都摸了一遍。用下来的感受很复杂单点能力确实惊艳但一旦想把 Agent 接入自己的业务流程问题就全冒出来了——数据出不去、权限控不住、定制改不动、成本算不清。尤其是团队协作场景每个人都在各自的对话框里养自己的 Agent经验无法沉淀能力无法复用。这就是我决定从 0 到 1 搭一个 Agent 平台的直接动机。核心思路一句话概括把 Agent 当成流水线上的产品平台就是那座工厂。工厂里有原材料模型、工具、知识库、有生产线编排引擎、执行沙箱、有质检评测、日志、回放最后产出的是一个个可以上岗的数字同事。当 Agent 有了工厂造同事这件事就不再是少数人的手艺活而是团队里每个人都能参与的标准动作。这篇文章面向三类人一是想理解 Agent 平台底层架构的技术负责人二是有 React / Next.js / FastAPI 基础、想动手做一个练手项目的开发者三是正在做企业级 Agent 选型、想搞清楚自建 vs 采购边界的产品和架构同学。我会把整个搭建过程拆开讲包括技术选型的理由、目录结构的设计、核心模块的实现逻辑以及我在实操中踩过的坑。所有代码和配置都是可复现的你可以直接抄作业。先明确一个概念边界因为后台被问太多次了AI Agent、LLM、AI 模型这三者不是一回事。AI 模型是底层的大脑比如 DeepSeek、GPT 系列、Claude 系列它们本质上是输入文本、输出文本的概率模型LLM 是大语言模型的统称属于 AI 模型里最主流的一类而 Agent 是在 LLM 之上加了一层手脚和记忆——它能调用工具、能规划步骤、能记住上下文、能根据结果反思重试。打个比方LLM 是一个博学但只会动嘴的顾问Agent 是给他配了电脑、电话、档案柜和一双能干活的手之后的员工。所以常说的 DeepSeek 属于 AI 模型 / LLM 这一层它是 Agent 的发动机但不是 Agent 本身。理解了这层关系平台要做什么就清楚了管好发动机、造好手脚、建好档案柜、定好工位规则。2. 平台的能力边界先想清楚工厂要生产什么动手写代码之前我花了整整一周时间只做一件事——画能力地图。因为 Agent 平台最容易犯的错就是一上来堆功能最后做成一个四不像。我见过太多团队平台里塞了十几个模型接入、二十几个工具结果没有一个 Agent 能稳定跑完一个完整任务。2.1 一个 Agent 平台必须回答的四个问题我把平台的核心能力收敛成四个问题每个问题对应一个模块核心问题对应模块关键设计点Agent 由什么组成编排引擎角色定义、工具绑定、记忆策略、终止条件Agent 怎么执行运行时沙箱隔离、超时控制、重试、流式输出Agent 怎么变强知识与工具层RAG 检索、工具注册、MCP 协议接入Agent 怎么管治理层权限、配额、日志、评测、版本这四个问题里编排引擎是灵魂运行时是命脉。很多人做 Agent 平台把 80% 精力花在接模型和写 Prompt 上结果运行时一塌糊涂——任务跑一半卡死、工具调用死循环、上下文爆掉。我踩过最惨的一次是一个数据分析 Agent 在调用某个接口时超时因为没有超时控制整个请求挂了 15 分钟才返回前端用户早就跑了。2.2 自建 vs 采购什么情况下该自己造不是所有团队都需要自建平台。我的判断标准很粗暴看三条数据敏感度如果 Agent 要接触核心业务数据、客户隐私、内部知识库且这些数据不能出内网那基本只能自建或私有化部署。定制深度如果只是问答、总结、翻译这类通用任务现成产品足够但如果要深度嵌入业务流程比如自动处理工单、联动内部系统、按公司规范写代码现成产品的定制能力往往不够。规模与成本当 Agent 调用量上来之后按 token 计费的 SaaS 模式成本会指数级上升自建 开源模型 缓存优化长期看能省一大笔。我自己的项目属于前两条都命中所以自建是唯一选择。技术栈上前端选React Next.js后端选FastAPI这是我在多个项目里验证过的最顺手组合下面会详细讲为什么。2.3 技术选型背后的真实理由为什么前端用 Next.js 而不是纯 React SPA因为 Agent 平台有大量需要 SSR 的场景——Agent 列表页、运行历史、评测报告这些页面需要 SEO 友好内部知识库搜索场景和首屏速度。Next.js 的 App Router 配合 Server Components能把数据预获取放在服务端前端只负责交互首屏体验比纯 SPA 好太多。而且 Next.js 的 Route Handlers 可以直接做 BFF 层省掉一个中间服务。为什么后端用 FastAPI 而不是 Node.js 或 Spring三个原因一是 Python 生态在 AI 领域无可替代LangChain、LlamaIndex、各种模型 SDK 都是 Python 优先二是 FastAPI 的异步性能足够撑住 Agent 这种 IO 密集型场景配合 Pydantic 做参数校验非常舒服三是 FastAPI 的依赖注入和后台任务机制天然适合做 Agent 的异步执行。至于企业级 Java 方案Spring AI如果你的团队是 Java 技术栈且已有 Spring Cloud 体系那用 Spring AI 也合理但纯从 AI 生态丰富度看Python 还是更优。为什么不用现成的 Agent 框架一把梭LangChain 这类框架确实能快速跑通 Demo但做平台级产品时框架的抽象层反而会成为负担——你想改一个执行细节得翻半天源码。我的做法是核心编排逻辑自己写工具调用、向量检索这些成熟能力用库。这样既可控又不重复造轮子。3. 后端骨架FastAPI 项目目录结构与核心模块后端是整个平台的地基。我见过很多 FastAPI 项目写着写着就变成一坨——所有路由堆在 main.py业务逻辑和数据库操作混在一起。所以第一步是把目录结构定死。3.1 我实际使用的目录结构agent-platform/ ├── app/ │ ├── main.py # 应用入口注册路由和中间件 │ ├── core/ │ │ ├── config.py # 配置管理Pydantic Settings │ │ ├── security.py # 鉴权、权限校验 │ │ └── logging.py # 结构化日志 │ ├── api/ │ │ ├── deps.py # 依赖注入 │ │ └── v1/ │ │ ├── agents.py # Agent CRUD │ │ ├── runs.py # 执行与流式输出 │ │ ├── tools.py # 工具注册 │ │ └── knowledge.py # 知识库 │ ├── models/ # SQLAlchemy ORM 模型 │ ├── schemas/ # Pydantic 请求/响应模型 │ ├── services/ │ │ ├── orchestrator.py # 编排引擎核心 │ │ ├── runtime.py # 执行运行时 │ │ ├── memory.py # 记忆管理 │ │ └── evaluator.py # 评测 │ ├── tools/ # 内置工具实现 │ └── workers/ # 后台任务Celery / ARQ ├── tests/ ├── alembic/ # 数据库迁移 └── pyproject.toml这个结构的关键在于services 层和 api 层彻底分离。api 层只做参数校验和响应封装所有业务逻辑在 services 里。这样单元测试可以直接测 services不用起 HTTP 服务。3.2 编排引擎Agent 的大脑怎么设计编排引擎是整个平台最核心的部分。我的设计是一个ReAct 循环 状态机的混合体。为什么不用纯 ReAct因为纯 ReAct 在复杂任务里容易跑飞——它会一直思考-行动-观察循环下去直到 token 耗尽。状态机的作用是给循环加上边界明确什么条件下该结束、什么条件下该重试、什么条件下该转人工。核心执行逻辑大致是这样# app/services/orchestrator.py from enum import Enum from typing import AsyncGenerator class RunState(str, Enum): PLANNING planning ACTING acting OBSERVING observing REFLECTING reflecting DONE done FAILED failed class Orchestrator: def __init__(self, agent, llm, tools, memory, max_steps15): self.agent agent self.llm llm self.tools tools self.memory memory self.max_steps max_steps async def run(self, task: str) - AsyncGenerator[dict, None]: state RunState.PLANNING step 0 scratchpad [] while state not in (RunState.DONE, RunState.FAILED): if step self.max_steps: state RunState.FAILED yield {type: error, msg: 超过最大步数} break if state RunState.PLANNING: plan await self._plan(task, scratchpad) scratchpad.append(plan) state RunState.ACTING elif state RunState.ACTING: action await self._decide_action(scratchpad) if action[type] final: state RunState.DONE yield {type: final, content: action[content]} break result await self._execute_tool(action) scratchpad.append(result) state RunState.OBSERVING elif state RunState.OBSERVING: state RunState.REFLECTING elif state RunState.REFLECTING: should_continue await self._reflect(scratchpad) state RunState.ACTING if should_continue else RunState.DONE step 1 yield {type: step, state: state, step: step}这段代码有几个设计要点值得展开。第一用异步生成器做流式输出。Agent 执行是长任务用户不可能等 30 秒才看到结果所以每一步的状态都要实时推给前端。FastAPI 的StreamingResponse配合 SSE 就能实现。第二max_steps 是硬性护栏。我设的是 15 步超过就判定失败。这个值不是拍脑袋定的是统计了几百次真实任务后得出的——95% 的正常任务在 10 步内完成超过 15 步的基本都是陷入了循环。第三scratchpad 是 Agent 的草稿纸记录每一步的思考和观察它和长期记忆是两回事任务结束就清空。3.3 运行时沙箱、超时与重试运行时是 Agent 的手脚负责真正执行工具调用。这里最容易出问题我踩过的坑几乎都集中在这一层。超时控制每个工具调用必须设超时而且要有两层——单次调用超时和整个任务超时。单次调用我一般设 30 秒整个任务设 5 分钟。实现上用asyncio.wait_forimport asyncio async def execute_with_timeout(coro, timeout30): try: return await asyncio.wait_for(coro, timeouttimeout) except asyncio.TimeoutError: return {error: 工具调用超时, timeout: timeout}重试策略不是所有失败都值得重试。我的规则是——网络类错误超时、连接失败重试 2 次指数退避业务类错误参数错误、权限不足不重试直接返回给 Agent 让它自己调整。这个区分很重要我早期无脑重试所有错误结果一个参数写错的工具被重试了 5 次白白烧了一堆 token。沙箱隔离如果 Agent 要执行代码比如数据分析场景必须放在隔离环境里。我用的是子进程 资源限制的方式限制 CPU 时间、内存和文件系统访问。生产环境更稳妥的做法是容器化沙箱每次执行起一个临时容器跑完就销毁。注意沙箱这块千万别图省事直接exec()用户或模型生成的代码我见过真实案例一个 Agent 生成的代码把服务器上的文件删了。隔离不是可选项是底线。3.4 记忆管理短期、长期、工作记忆三层Agent 的记忆分三层很多人只做了第一层就以为够了。短期记忆当前对话的上下文就是消息列表。这层用滑动窗口 摘要压缩控制长度。长期记忆跨会话的知识存在向量库里通过语义检索召回。比如用户上次说过我们公司报销标准是 500 元这次问报销相关问题时应该能想起来。工作记忆当前任务的中间结果就是上面说的 scratchpad。任务结束即销毁。三层记忆的读写时机完全不同。短期记忆每轮都读长期记忆按需检索工作记忆只在任务内流转。我早期把三者混在一起塞进 context结果 token 消耗爆炸一个简单任务能烧掉几万 token。分开管理之后同样的任务 token 消耗降了 60%。4. 前端交互Next.js 如何把 Agent 的思考过程可视化Agent 平台的前端和普通 CRUD 应用最大的区别在于它要展示一个动态的、流式的、有状态的执行过程。用户需要看到 Agent 在想什么、调用了什么工具、得到了什么结果。这对前端架构提出了特殊要求。4.1 App Router 下的数据预获取方案Next.js 的 App Router 提供了几种数据获取方式我根据页面类型分别选用页面类型数据获取方式理由Agent 列表页Server Component 直接 fetch数据变化不频繁SSR 首屏快Agent 详情页Server Component 客户端 hydration需要 SEO同时有交互运行历史客户端 fetch SWR数据实时变化需要轮询执行过程SSE 流式必须实时推送这里有个坑要提醒Server Component 里不能直接用浏览器 API也不能用 useState。我一开始想把执行过程的流式展示放在 Server Component 里折腾半天发现根本行不通因为 SSE 是客户端行为。正确做法是 Server Component 负责首屏静态数据客户端组件负责流式交互。4.2 用 SSE 实现 Agent 执行过程的实时展示Agent 执行是长任务用 WebSocket 有点重SSEServer-Sent Events刚好够用——单向推送、基于 HTTP、自动重连。后端 FastAPI 侧from fastapi.responses import StreamingResponse router.post(/agents/{agent_id}/run) async def run_agent(agent_id: str, payload: RunRequest): async def event_stream(): async for event in orchestrator.run(payload.task): yield fdata: {json.dumps(event, ensure_asciiFalse)}\n\n return StreamingResponse(event_stream(), media_typetext/event-stream)前端用EventSource或 fetch 的 ReadableStream 接收。我推荐用 fetch ReadableStream因为 EventSource 不支持 POST 请求而 Agent 执行需要传任务参数。// 客户端组件里 async function runAgent(task: string) { const res await fetch(/api/agents/${agentId}/run, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ task }), }); const reader res.body!.getReader(); const decoder new TextDecoder(); while (true) { const { done, value } await reader.read(); if (done) break; const chunk decoder.decode(value); // 解析 SSE 格式更新 UI 状态 parseSSEChunk(chunk); } }4.3 把思考链做成可折叠的时间线这是我在用户体验上花心思最多的地方。Agent 的执行过程如果全部平铺展示用户会被大量中间步骤淹没。我的做法是做成可折叠的时间线默认只展示最终结果和关键节点工具调用、重要决策中间的思考细节折叠起来用户想深究再展开。这个设计参考了代码编辑器的折叠思路。每个步骤是一个时间线节点节点上有类型图标思考、工具调用、观察、结果、耗时、token 消耗。用户一眼能看出 Agent 在哪一步花了最多时间、哪一步调用了什么工具。这个可视化对调试 Agent 特别有用——我经常通过看时间线发现某个 Agent 在反复调用同一个工具从而定位到 Prompt 里的逻辑问题。4.4 React 状态管理的取舍Agent 执行过程的状态很复杂当前步骤、历史步骤、流式文本、工具结果、错误信息。我一开始用 Redux后来发现太重了改成Zustand React Query的组合。Zustand 管执行过程的实时状态React Query 管服务端数据的缓存和同步。这个组合的好处是实时状态用轻量的 store服务端数据用成熟的缓存方案各司其职。有个细节值得说流式文本的渲染要做节流。Agent 每吐一个字就 setState 会导致 React 疯狂重渲染页面卡顿。我的做法是用requestAnimationFrame做批量更新或者干脆用一个 ref 累积文本每 100ms 刷一次 UI。5. 工具层与知识层让 Agent 真正能干活一个只会聊天的 Agent 没有价值能调用工具、能查知识库的 Agent 才是同事。这一层是平台从玩具变成工具的分水岭。5.1 工具注册机制统一接口 自动发现工具层的设计目标是新增一个工具只需要写一个函数不用改任何框架代码。我用的方案是装饰器 自动注册# app/tools/registry.py TOOL_REGISTRY {} def tool(name: str, description: str, schema: dict): def decorator(func): TOOL_REGISTRY[name] { name: name, description: description, schema: schema, func: func, } return func return decorator # 使用 tool( namequery_database, description查询业务数据库返回结构化结果, schema{type: object, properties: {sql: {type: string}}} ) async def query_database(sql: str): # 实际实现 ...这个设计的关键是schema 和 description 的质量。LLM 决定调用哪个工具完全依赖这两个字段。我踩过的坑是description 写得太模糊导致 Agent 该调 A 工具时调了 B 工具。后来我定了个规矩——每个工具的 description 必须包含什么时候用和什么时候不用schema 里每个参数都要有清晰的描述和示例。5.2 RAG 检索知识库不是塞进去就行知识库这块很多人以为把文档切块、向量化、存库就完事了。实际做下来检索质量才是决定 Agent 表现的关键。我总结了几个实操要点切块策略要按文档类型区分技术文档按标题层级切合同按条款切聊天记录按对话轮次切。一刀切按固定长度切检索效果会差很多。混合检索优于纯向量检索向量检索擅长语义匹配但对精确关键词比如产品型号、错误码不敏感。我的做法是向量检索 BM25 关键词检索结果用 RRF 融合。重排序不能省召回 20 条用重排序模型精排到 5 条效果比直接召回 5 条好得多。提示知识库的更新策略也要想清楚。是全量重建索引还是增量更新我用的增量方案文档变更时只重新向量化变更的部分配合版本号管理避免全量重建的性能开销。5.3 MCP 协议工具生态的USB 接口2025 年之后MCPModel Context Protocol逐渐成为 Agent 工具接入的事实标准。它的价值在于把工具接入标准化——以前每接一个外部服务都要写适配代码现在只要对方支持 MCP直接配置就能用。我在平台里做了一个 MCP 客户端层支持动态加载外部 MCP Server 提供的工具。这样平台的内置工具和外部工具用同一套调用接口Agent 完全感知不到区别。这个设计让平台的扩展性上了一个台阶——用户想接自己的内部系统只要写个 MCP Server 就行不用改平台代码。5.4 工具调用的安全边界工具层是 Agent 和真实世界交互的接口也是风险最集中的地方。我设了三道防线权限校验每个工具声明所需权限Agent 执行前校验当前用户是否有权限。参数校验用 Pydantic 严格校验工具入参防止注入类问题。敏感操作二次确认删除、修改、发送这类不可逆操作必须人工确认后才执行。第三点特别重要。我见过一个 Agent 因为理解偏差把用户的测试数据当成垃圾数据删了。加了二次确认之后这类事故再没发生过。6. 治理层Agent 平台的质检和考勤平台能跑起来只是第一步能管好才是关键。治理层解决的是Agent 上线之后怎么保证它一直靠谱的问题。6.1 评测体系怎么量化一个 Agent 好不好没有评测的 Agent 平台就是耍流氓。我的评测体系分三层单元评测针对单个工具调用验证参数正确性、返回格式。任务评测给定标准任务和期望结果跑一批测试用例算通过率。回归评测每次修改 Prompt 或换模型后重跑历史用例防止能力退化。评测用例的积累是个长期活。我的做法是把线上真实任务中用户点了赞和用户点了踩的案例自动沉淀成评测集这样评测集始终贴近真实场景。6.2 日志与回放出问题能查、能复现Agent 的日志和普通应用日志不一样它需要记录完整的执行轨迹每一步的输入、输出、工具调用、耗时、token 消耗。我用的结构化日志每个任务一个 trace_id所有步骤串在一起。回放功能是调试利器。给定一个 trace_id能完整重现 Agent 当时的执行过程包括每一步的 Prompt 和模型输出。这个功能帮我定位过很多偶发问题——有些问题只在特定上下文下出现没有回放根本查不出来。6.3 成本控制token 是要花钱的Agent 平台的成本大头是 token。我做了几个优化优化手段效果实现方式Prompt 缓存省 30-50%相同前缀的 Prompt 复用缓存上下文压缩省 40%历史消息摘要化模型分级省 60%简单任务用小模型复杂任务用大模型结果缓存省 20%相同查询直接返回缓存结果模型分级这块特别值得说。不是所有任务都需要最强模型我做了个路由层先让便宜的小模型判断任务复杂度简单任务直接小模型处理复杂任务才升级到大模型。实测下来整体成本降了一半多效果几乎没损失。6.4 版本管理Agent 也要能回滚Agent 的配置Prompt、工具绑定、模型参数是会不断迭代的。我做了版本管理每次修改生成一个新版本可以随时回滚到历史版本。这个功能在 Prompt 调优时特别有用——改坏了能一键回退不用手动记改了什么。7. 实操中踩过的坑与经验总结前面讲的都是应该怎么做这一节讲讲我实际踩过什么坑。这些经验在官方文档里找不到都是真金白银换来的。7.1 上下文爆炸一个任务烧掉 8 万 token项目早期我做一个数据分析 Agent测试时发现一个简单任务烧了 8 万 token。排查后发现Agent 每调用一次工具就把完整的历史消息包括所有工具返回的原始数据塞进 context。一个查询返回几千行数据几轮下来 context 就爆了。解决方案工具返回结果做截断和摘要。原始数据存起来只把摘要和关键字段给 Agent。需要详细数据时Agent 再按需查询。这个改动让 token 消耗降了 70%。7.2 死循环Agent 反复调用同一个工具有个 Agent 在某个任务里反复调用同一个查询工具调了十几次还在调。原因是工具返回的结果格式和 Agent 预期的不一致Agent 以为没查到就一直重试。解决方案一是工具返回格式要稳定且明确二是加重复调用检测——如果连续 3 次调用同一工具且参数相同强制中断并提示 Agent 换策略。7.3 流式输出的乱码问题SSE 推送中文时出现过乱码排查后发现是编码问题。解决方案后端json.dumps时加ensure_asciiFalse响应头明确charsetutf-8前端用TextDecoder解码时指定编码。7.4 前端白屏React Native 项目的教训虽然这个平台是 Web 端但我同时在做一个移动端 React Native 版本遇到过启动白屏。原因是首屏渲染时同步做了太多初始化工作加载配置、初始化 SDK。解决方案把非关键初始化改成异步首屏先渲染骨架屏数据加载完再替换。这个思路在 Web 端同样适用。7.5 工具权限的最小惊讶原则早期我给 Agent 配工具时本着多给点总没错的心态结果 Agent 经常调用一些不该调的工具。后来改成最小权限原则——只给完成任务必需的工具需要时再申请。这个改动让 Agent 的行为可预测多了。8. 从练手项目到企业级平台的演进路径如果你只是想练手前面讲的核心模块跑通就够了。但如果要往企业级演进还有几件事要做。8.1 多租户与权限体系企业级平台必须支持多租户——不同团队、不同项目的 Agent 和数据要隔离。我的做法是在数据模型层加 tenant_id所有查询强制带租户过滤配合行级权限控制。这块设计要一开始就考虑后期加会非常痛苦。8.2 高可用与水平扩展Agent 执行是 IO 密集型水平扩展相对容易。关键是把无状态的服务和有状态的执行分开API 服务无状态可以随便扩执行任务放到消息队列由 worker 消费worker 可以按负载动态扩缩容。8.3 可观测性企业级平台需要完整的可观测性指标QPS、延迟、错误率、token 消耗、日志结构化、可检索、链路追踪每个任务的完整调用链。我用的是 Prometheus Grafana 做指标OpenTelemetry 做链路追踪。8.4 多智能体协作单个 Agent 能力有限复杂任务需要多个 Agent 协作。我的平台支持Agent 编排——一个主 Agent 可以把子任务分派给专业 Agent最后汇总结果。这其实就是数字同事团队的雏形。实现上要注意的是通信协议和冲突解决——多个 Agent 同时操作同一资源时怎么协调这块我还在持续打磨。9. 一些掏心窝子的实操建议最后分享几条我在这个项目里最深的体会都是踩坑踩出来的。第一先把运行时做扎实再谈智能。我见过太多团队Prompt 调得飞起但运行时一塌糊涂任务跑一半就挂。Agent 平台的竞争力一半在运行时。第二评测要趁早。不要等 Agent 上线了才想怎么评测。从第一天起就积累评测用例这是你后续所有优化的依据。第三成本意识要贯穿始终。token 是要花钱的每个设计决策都要问一句这会不会让成本翻倍。缓存、分级、压缩这些优化越早做越好。第四安全边界不能妥协。沙箱隔离、权限校验、二次确认这些看起来麻烦的东西是平台能上生产的前提。第五别追求一步到位。我的平台也是从能跑通一个 Agent 开始的慢慢加工具、加记忆、加评测、加治理。先让它能用再让它好用最后让它可靠。这个项目我还在持续迭代最近在做的方向是 Agent 的自动评测和 Prompt 自动优化——让平台自己发现哪个 Agent 表现不好自动调整。等有阶段性成果了再写一篇分享。如果你也在做类似的事欢迎交流这个领域变化太快一个人闷头做容易走弯路。
返回列表