☰
从0到1搭建AI Agent平台:React+Next.js+Python实战指南
2026/9/26 13:35:17 网站建设 项目流程

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_turns=10): messages = load_history(session_id) messages.append({"role": "user", "content": user_input}) for turn in range(max_turns): response = call_llm(messages, tools=get_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( name="search_competitor_news", description="搜索指定公司的最新新闻,返回标题和摘要列表", parameters={ "type": "object", "properties": { "company": {"type": "string", "description": "公司名称"}, "days": {"type": "integer", "description": "查询最近几天的新闻,默认7"} }, "required": ["company"] } ) def search_competitor_news(company, days=7): # 实际搜索逻辑 return results

这里的关键是description和parameters的写法。模型完全靠这两样东西来决定调不调、怎么调。描述要写得像给新人交代任务一样清楚,参数要标明类型和是否必填。

我踩过一个坑:早期把工具描述写得太简略,比如“搜索新闻”,结果模型经常在不该调的时候调它。后来改成“搜索指定公司的最新新闻,返回标题和摘要列表,适用于了解竞品近期动态”,调用准确率明显提升。

参数校验我放在执行前做一层。虽然模型大部分时候会按 schema 传参,但偶尔会漏字段或者类型不对。我的做法是用 Pydantic 做一层校验,校验不过就返回错误信息给模型,让它重新调。

3.3 上下文管理与 token 控制策略

上下文管理是 Agent 平台里最容易被忽视、但最影响成本和效果的部分。

一个会话跑久了,消息列表会越来越长。模型有上下文窗口限制,超了就报错。我的策略是三层处理:

第一层是滑动窗口。保留最近 N 条消息,更早的丢弃。N 根据模型窗口大小和平均消息长度来定,我一般留最近 20 条。

第二层是摘要压缩。丢弃之前,把早期消息交给模型做一次摘要,把摘要作为一条系统消息放在最前面。这样既控制了长度,又保留了关键信息。

第三层是工具结果截断。工具返回的内容可能很长,比如一次搜索返回几十条结果。我设了一个阈值,超过就只保留前若干条,并在末尾注明“已截断”。模型需要更多可以再调一次工具。

注意:摘要压缩会增加一次模型调用,有额外成本。我的经验是,会话超过 30 条消息再触发摘要比较划算,太早触发反而浪费。

4. 前端交互层的搭建要点

4.1 用 React 做流式消息展示

Agent 干活的过程,用户需要看得见。我用的是 SSE(Server-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 和会话 ID,useReducer 管理消息列表的增删改。这样组件层级再深,取状态也不用一层层传 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-stream

6. 从最小可用到可扩展的演进路径

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 完全没法用的。先把要解决的问题想透,再动手写代码,能省掉大量返工。

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

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

立即咨询