ARTICLE DETAIL

资讯详情

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

从0到1搭建AI Agent平台:React+Next.js+Python实战指南

从0到1搭建AI Agent平台:React+Next.js+Python实战指南 1. 为什么我要自己搭一个 AI Agent 平台去年年底我开始认真琢磨一件事手头重复性的工作太多了。写周报、整理会议纪要、盯竞品更新、回复常见问题、跑数据做初步分析这些事情单拎出来都不难但叠在一起每天能吃掉我三四个小时。市面上的 AI 工具我几乎试了个遍单点能力都很强可问题是它们彼此不通——聊天窗口里问完的东西换个工具又得重新喂一遍上下文流程根本串不起来。这就是我决定从 0 到 1 搭一个 AI Agent 平台的直接原因。不是要做一个多宏大的产品而是想给自己造几个“数字同事”一个专门盯竞品动态的、一个负责把会议录音转成结构化纪要的、一个帮我做数据初筛的。它们能各自独立干活也能互相传话我只需要在最后验收。这篇文章面向的是有一定前端基础、想动手做 AI Agent 但不知道从哪下手的开发者。我会把整个搭建过程拆开讲技术选型为什么是 React Next.js Python 这套组合Agent 的核心循环怎么设计工具调用怎么接状态怎么管以及我在实操中踩过的那些坑。看完你应该能照着搭出一个能跑起来的最小可用版本再按自己的需求往上加东西。需要先说明一点AI Agent 这个词现在被用得很泛。我这里说的 Agent指的是能自主决定调用哪些工具、按什么顺序调用、并根据结果决定下一步动作的程序而不是简单的“输入问题返回答案”的问答机器人。这个区别很关键后面所有的设计都是围绕它展开的。2. 整体架构设计与技术选型思路2.1 为什么是 React Next.js Python 这套组合技术选型这件事我的原则是前端用最熟的后端用最合适的中间用最省事的。前端我选 React Next.js理由很实在。React 的组件模型天然适合做 Agent 的对话流和工具调用可视化每个 Agent 的状态、每条消息、每次工具调用都可以是一个独立组件状态变化直接驱动 UI 更新。Next.js 则帮我省掉了大量样板代码——路由、API 路由、服务端渲染、静态资源优化开箱即用。特别是它的 API Routes 功能让我可以在同一个项目里写前端和后端的轻量接口不用单独起一个服务。后端我选 Python这个没什么好纠结的。AI 生态里 Python 是绝对主力无论是调用大模型 API、做文本处理、跑数据分析还是接各种向量数据库Python 的库最全、社区最活跃。而且 Python 写 Agent 的核心循环逻辑非常直观几十行就能把“思考-行动-观察”这个循环跑通。中间层我用 Next.js 的 API Routes 做转发和聚合。前端不直接调 Python 服务而是先打到 Next.js 的接口由它去调 Python 后端。这样做的好处是前端只需要面对一套统一的接口Python 服务的地址、鉴权、错误处理都收在中间层后面要换后端实现或者加缓存、加限流都不用动前端代码。提示如果你团队里 Python 人手不足后端也可以用 Node.js 写但 AI 相关的库和示例会少很多遇到问题查资料的时间成本会明显上升。我的建议是后端尽量用 Python前端用你最顺手的框架。2.2 Agent 平台的核心模块拆解一个能跑的 Agent 平台拆到最细其实就四个核心模块第一个是 Agent 运行时。这是心脏负责执行“思考-行动-观察”循环。它接收用户输入拼装上下文调用大模型解析模型返回的工具调用请求执行工具把结果塞回上下文再调模型直到模型给出最终答案或者达到最大轮次。第二个是工具注册与执行层。Agent 能干什么取决于你给它注册了哪些工具。每个工具就是一个函数有名字、有描述、有参数定义。模型根据描述来决定什么时候调哪个工具。这一层要处理参数校验、执行隔离、超时控制和错误捕获。第三个是会话与状态管理。Agent 不是一问一答就结束的它需要记住之前发生了什么。会话历史、工具调用记录、中间结果这些都要存下来。我用的是“会话 ID 消息列表”的结构每条消息带角色用户/助手/工具和内容工具调用单独存一份结构化记录。第四个是前端交互层。这部分最容易被低估。Agent 干活的时候用户需要看到它在干什么——正在调哪个工具、参数是什么、返回了什么、下一步准备干嘛。如果只是转个圈等结果体验会很差。我用 React 做了一套流式展示Agent 每产生一个动作就推一条消息到前端用户能实时看到进度。2.3 数据流设计一次请求到底经历了什么把一次完整的请求拆开看数据流是这样的用户在 React 界面输入问题前端把问题加上会话 ID 发给 Next.js 的 API Route。API Route 做两件事校验参数、把请求转发给 Python 后端。Python 后端收到请求后从数据库或内存里取出这个会话的历史消息拼成模型能理解的格式然后进入 Agent 循环。循环里模型返回的内容分两种一种是直接回答那就结束循环把答案返回另一种是要求调用工具那就解析出工具名和参数执行对应函数把结果作为一条“工具消息”追加到上下文然后再次调用模型。这个过程可能重复多轮直到模型给出最终答案。最终答案返回给 Next.js再返回给前端。前端把整个过程中的消息按顺序渲染出来用户看到的就是一个完整的“思考-行动-观察”链条。这个设计里有一个关键决策Agent 循环放在后端前端只负责展示。我试过把循环放前端用浏览器直接调模型 API结果是密钥暴露、上下文管理混乱、工具执行受限。放后端之后这些问题一次性解决前端只需要处理展示逻辑职责清晰很多。3. Agent 核心循环的实现细节3.1 思考-行动-观察循环的代码骨架Agent 的核心循环用 Python 写出来大概长这样def run_agent(session_id, user_input, max_turns10): messages load_history(session_id) messages.append({role: user, content: user_input}) for turn in range(max_turns): response call_llm(messages, toolsget_tool_schemas()) if response.type final_answer: save_history(session_id, messages) return response.content if response.type tool_call: tool_name response.tool_name tool_args response.tool_args messages.append({ role: assistant, content: None, tool_calls: [response.raw_tool_call] }) try: result execute_tool(tool_name, tool_args) except Exception as e: result f工具执行失败: {str(e)} messages.append({ role: tool, tool_call_id: response.tool_call_id, content: str(result) }) return 达到最大轮次限制任务未完成这段代码看着简单但每一行背后都有讲究。max_turns这个参数是必须的。我一开始没设上限结果有一次模型陷入死循环反复调同一个工具烧了不少 token。后来设成 10 轮大部分任务够用极端情况也能兜住。工具执行必须包在 try-except 里。工具函数可能因为各种原因失败——网络超时、参数不对、外部服务挂了。如果异常直接抛出去整个循环就断了。我的做法是把异常转成一条工具消息返回给模型让模型自己决定是重试、换工具还是放弃。实测下来模型处理这种“工具报错”的能力比想象中强很多时候它能自己纠正。tool_call_id这个字段不能省。模型可能一次返回多个工具调用每个调用有独立的 ID工具结果必须带上对应的 ID模型才能把结果和请求对上。我早期版本漏了这个字段导致模型经常“张冠李戴”把 A 工具的结果当成 B 工具的。3.2 工具注册机制与参数校验工具注册我用的是装饰器模式写起来最顺手TOOL_REGISTRY {} def tool(name, description, parameters): def decorator(func): TOOL_REGISTRY[name] { function: func, schema: { name: name, description: description, parameters: parameters } } return func return decorator tool( namesearch_competitor_news, description搜索指定公司的最新新闻返回标题和摘要列表, parameters{ type: object, properties: { company: {type: string, description: 公司名称}, days: {type: integer, description: 查询最近几天的新闻默认7} }, required: [company] } ) def search_competitor_news(company, days7): # 实际搜索逻辑 return results这里的关键是description和parameters的写法。模型完全靠这两样东西来决定调不调、怎么调。描述要写得像给新人交代任务一样清楚参数要标明类型和是否必填。我踩过一个坑早期把工具描述写得太简略比如“搜索新闻”结果模型经常在不该调的时候调它。后来改成“搜索指定公司的最新新闻返回标题和摘要列表适用于了解竞品近期动态”调用准确率明显提升。参数校验我放在执行前做一层。虽然模型大部分时候会按 schema 传参但偶尔会漏字段或者类型不对。我的做法是用 Pydantic 做一层校验校验不过就返回错误信息给模型让它重新调。3.3 上下文管理与 token 控制策略上下文管理是 Agent 平台里最容易被忽视、但最影响成本和效果的部分。一个会话跑久了消息列表会越来越长。模型有上下文窗口限制超了就报错。我的策略是三层处理第一层是滑动窗口。保留最近 N 条消息更早的丢弃。N 根据模型窗口大小和平均消息长度来定我一般留最近 20 条。第二层是摘要压缩。丢弃之前把早期消息交给模型做一次摘要把摘要作为一条系统消息放在最前面。这样既控制了长度又保留了关键信息。第三层是工具结果截断。工具返回的内容可能很长比如一次搜索返回几十条结果。我设了一个阈值超过就只保留前若干条并在末尾注明“已截断”。模型需要更多可以再调一次工具。注意摘要压缩会增加一次模型调用有额外成本。我的经验是会话超过 30 条消息再触发摘要比较划算太早触发反而浪费。4. 前端交互层的搭建要点4.1 用 React 做流式消息展示Agent 干活的过程用户需要看得见。我用的是 SSEServer-Sent Events做流式推送前端用 React 的useState和useEffect接收。核心思路是后端每产生一个事件开始思考、调用工具、工具返回、生成答案就推一条消息到前端。前端维护一个消息列表每收到一条就追加界面自动更新。const [messages, setMessages] useState([]); useEffect(() { const eventSource new EventSource(/api/agent/stream?sessionId${sessionId}); eventSource.onmessage (event) { const data JSON.parse(event.data); setMessages(prev [...prev, data]); }; eventSource.onerror () { eventSource.close(); }; return () eventSource.close(); }, [sessionId]);每条消息带一个type字段前端根据类型渲染不同组件thinking显示“正在思考”tool_call显示工具名和参数tool_result显示返回内容answer显示最终答案。这样做的好处是用户全程有反馈不会觉得卡住了。实测下来同样的等待时间有流式展示的体验比转圈好太多。4.2 会话状态管理与多 Agent 切换一个平台上有多个 Agent 的时候会话管理要稍微设计一下。我的做法是每个 Agent 有独立的会话空间切换 Agent 时切换对应的消息列表。状态我用的是 React 的 Context useReducer。Context 存当前 Agent ID 和会话 IDuseReducer 管理消息列表的增删改。这样组件层级再深取状态也不用一层层传 props。多 Agent 切换时我会把每个 Agent 的消息列表缓存在内存里切回来的时候直接恢复不用重新拉。如果会话很多就只缓存最近几个更早的从后端拉。4.3 工具调用结果的可视化处理工具返回的结果格式五花八门有的是纯文本有的是 JSON有的是表格数据。前端需要做一层适配把不同格式渲染成用户能看懂的样子。我的做法是给每个工具定义一个renderType比如text、json、table、link。前端根据renderType选对应的渲染组件。JSON 用折叠面板展示表格用表格组件链接用可点击的卡片。这样工具开发者只需要关心返回数据展示的事情交给前端统一处理。5. 实操中踩过的坑与排查技巧5.1 模型不调工具或乱调工具怎么办这是最常见的问题。模型要么该调工具的时候不调要么不该调的时候乱调。排查思路分三步第一步检查工具描述。描述是不是太模糊参数说明是不是不清楚我遇到过描述里写“查询数据”模型完全不知道查什么数据自然不调。改成“根据公司名查询最近7天的新闻标题和摘要”之后调用就正常了。第二步检查系统提示词。系统提示词里要明确告诉模型“你有以下工具可用当用户问题需要外部信息时优先调用工具”。我早期提示词写得太含蓄模型以为自己在做纯文本问答。第三步检查工具数量。工具太多超过 20 个的时候模型选择困难准确率会下降。我的做法是按场景分组每个 Agent 只挂它需要的工具一般控制在 10 个以内。5.2 工具执行超时与异常兜底工具执行可能很慢比如调外部 API 或者跑数据库查询。如果不设超时一个慢工具能把整个 Agent 卡死。我的做法是给每个工具设一个超时时间默认 30 秒特殊工具单独配置。超时就用signal中断返回一条“工具执行超时”的消息给模型。异常兜底分两层工具内部捕获可预期的异常比如参数错误、资源不存在返回友好提示外层捕获不可预期的异常比如代码 bug、依赖挂了记录日志并返回通用错误信息。两层都做才能保证 Agent 不会因为一个工具挂掉而整个崩掉。5.3 会话数据持久化的选型对比会话数据存哪里我试过三种方案方案优点缺点适用场景内存快零配置重启丢失不能多实例本地开发、演示SQLite轻量单文件够用并发写入弱个人使用、小团队PostgreSQL稳定并发好功能全需要单独部署生产环境、多用户我最后选的是 PostgreSQL因为要支持多用户和多 Agent 并发。如果只是自己用SQLite 完全够别过度设计。表结构就两张sessions存会话元信息ID、Agent ID、创建时间messages存消息会话 ID、角色、内容、工具调用记录、时间戳。查询按会话 ID 加时间排序简单直接。5.4 常见问题速查表问题现象可能原因排查方向解决方法模型不调工具描述模糊、提示词缺失检查工具 schema 和系统提示补充描述明确工具使用场景模型乱调工具工具太多、描述重叠检查工具列表按场景分组精简工具数量工具结果对不上缺 tool_call_id检查消息结构补全 tool_call_id 字段循环不结束无最大轮次限制检查循环条件设 max_turns超限返回提示上下文超长消息未压缩检查消息列表长度滑动窗口 摘要压缩前端收不到流SSE 配置问题检查响应头设置Content-Type: text/event-stream6. 从最小可用到可扩展的演进路径6.1 先跑通一个 Agent 再谈平台我见过太多人一上来就想做“平台”结果卡在架构设计上几个月跑不起来。我的建议是反着来先写一个能跑的 Agent哪怕只有一个工具、一个会话、一个前端页面。最小可用版本只需要一个 Python 文件写 Agent 循环一个工具函数一个 Next.js 页面做输入输出。跑通之后你自然知道哪里需要抽象、哪里需要扩展。平台是长出来的不是设计出来的。6.2 工具生态的扩展方式工具多了之后管理是个问题。我的做法是把工具按领域分目录每个目录一个__init__.py负责注册。新增工具只需要在对应目录加文件平台启动时自动扫描注册。工具之间还可以组合。比如“竞品分析”这个工具内部可以调“搜索新闻”和“提取关键信息”两个基础工具。这样上层 Agent 只需要挂一个组合工具逻辑更清晰。6.3 多 Agent 协作的初步尝试单个 Agent 能力有限多个 Agent 协作能处理更复杂的任务。我试过的最简单模式是“主管-执行者”一个主管 Agent 负责拆解任务把子任务分给执行者 Agent执行者干完把结果交回来主管汇总。实现上主管 Agent 的工具列表里挂一个“调用其他 Agent”的工具参数是 Agent ID 和任务描述。执行者 Agent 独立跑自己的循环返回结果。这样一层套一层理论上可以搭出很复杂的协作网络。不过要提醒一句多 Agent 协作的调试难度是指数级上升的。我建议先把单 Agent 跑稳再考虑协作。单 Agent 都没跑通就上多 Agent大概率是一团乱麻。6.4 部署与运维的注意事项部署我走的是最简路线前端 Next.js 部署到 Vercel后端 Python 用 Docker 打包部署到一台云服务器数据库用云数据库。这样前端有 CDN 加速后端和数据库在内网互通延迟低。环境变量管理要严格。模型 API 密钥、数据库连接串这些绝对不能进代码仓库。我用的是.env文件加.gitignore部署时通过平台的环境变量功能注入。日志要打全。Agent 循环的每一步、工具调用的入参出参、异常堆栈都要记下来。出问题的时候日志是唯一能还原现场的东西。我吃过亏早期日志打得太少一个偶发问题查了两天才定位到。监控方面我主要盯三个指标单次请求的 token 消耗、工具调用成功率、平均响应时间。这三个指标异常基本能定位到大部分问题。最后分享一个我自己的体会搭 Agent 平台这件事技术难度其实没有想象中高真正难的是想清楚“让 Agent 干什么”。工具设计得好不好、提示词写得清不清楚、任务拆解合不合理这些才是决定 Agent 好不好用的关键。代码只是载体对业务的理解才是核心。我见过工具写得很糙但 Agent 很好用的也见过代码很漂亮但 Agent 完全没法用的。先把要解决的问题想透再动手写代码能省掉大量返工。
返回列表