news 2026/10/23 2:24:41

大模型 Agent 构建平台开发:LLM + 向量库 + NextJS 前端实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
大模型 Agent 构建平台开发:LLM + 向量库 + NextJS 前端实战

大模型 Agent 构建平台开发:LLM + 向量库 + NextJS 前端实战

一、为什么要在 NextJS 上构建 Agent 平台

2026 年,大模型 Agent 已经从技术演示走向生产落地。但一个值得深思的现象是:大量 Agent 项目在原型阶段表现惊艳,一旦进入生产环境就暴露出状态丢失、工具调用不可控、前端体验割裂等问题。根源往往不在模型能力本身,而在于工程架构的缺失。

传统方案通常采用“前端框架 + 独立后端服务”的分离架构。以 Next.js + FastAPI 为例,前端负责 UI 渲染,后端负责 Agent 编排和向量检索。这种架构在团队分工上清晰,但引入了额外的网络跳转、部署复杂度和状态同步成本。对于中小规模 Agent 平台而言,这种分离带来的收益往往不足以抵消其复杂性。

Next.js App Router 的出现改变了这一局面。它允许在同一项目中定义 API Route Handler 和 React Server Component,Agent 的推理逻辑、工具执行和流式响应可以完全运行在 Next.js 的运行时中,无需独立后端进程。LangChain 官方提供的 Next.js 部署方案正是基于这一理念:流式聊天 UI、子 Agent 和线程历史全部由 Next.js Route Handlers(HTTP + SSE)实现,“No separate backend process”。

这意味着一个全栈开发者可以在单一代码库中完成从向量检索、Agent 编排到前端交互的全部工作。本文将基于 LLM + 向量库 + NextJS 的技术栈,完整拆解 Agent 构建平台的工程实现。

二、整体架构:三层解耦的 Agent 平台

在动手写代码之前,先明确平台的架构分层。推荐采用“三横一纵”的设计:

横向三层:

  • Agent 编排层:负责 LLM 推理决策、工具调用调度、多轮对话状态管理。核心是 LangGraph 的 StateGraph 或 Vercel AI SDK 的 ToolLoopAgent;
  • 知识检索层:负责文档向量化、相似度检索、上下文注入。向量库选型和 RAG 流水线在此层实现;
  • 交互层:Next.js App Router 负责页面路由和 API Route Handler,React 负责流式 UI 渲染和用户交互。

纵向贯穿:会话状态管理。Agent 平台与普通 Chatbot 的本质区别在于它需要维护跨轮次的结构化状态——不仅包括对话历史,还包括中间执行步骤、工具调用结果和待确认的 slot 信息。

某生产级 Agent 项目的状态定义可以作为一个参考范式:状态中需要包含用户当前输入、对话历史(符合 OpenAI 消息格式)、执行轨迹(action_type, action_name, result 的元组列表)、最终输出、重试计数和当前步骤编号。这种显式状态管理是 Agent 可靠性的基石。

三、技术栈选型:为什么是这一套

3.1 LLM 接入层:Vercel AI SDK

Vercel AI SDK 是当前 Next.js 生态中最成熟的 LLM 接入方案。它提供了三个核心函数:generateText用于一次性文本生成,streamText用于流式响应,generateObject配合 Zod schema 用于结构化输出。

更关键的是,AI SDK 通过 AI Gateway 实现了多模型统一接入。开发者只需传入creator/model-name格式的模型字符串,Gateway 自动解析提供商、完成认证和路由。应用代码不需要因为从 GPT 切换到 Claude 或 Gemini 而改动。

在 Agent 场景中,streamText是使用频率最高的函数。它支持tools参数用于工具调用,配合maxSteps可以自动完成多步推理循环。

3.2 Agent 编排层:LangGraph.js vs Vercel AI SDK Agent

LangGraph.js 提供了基于状态图的显式控制流,将 Agent 推理过程建模为有向图,每个节点代表一个计算步骤(如调用 LLM、执行工具),每条边定义状态流转条件。这种设计的优势在于控制流的透明性——开发者可以精确知道每个状态节点的输入输出,便于调试和性能优化。

Vercel AI SDK 的ToolLoopAgent则更加轻量。它封装了语言模型和工具集,运行自主推理循环:调用工具、将结果反馈给模型,直到获得完整答案。适合工具数量有限、流程相对简单的场景。

选型建议:如果 Agent 需要条件分支、并行执行、循环重试等复杂控制模式,选择 LangGraph.js;如果是标准的工具调用型 Agent,Vercel AI SDK 的 ToolLoopAgent 更简洁高效。

3.3 向量库选型:pgvector 是大多数场景的最优解

2026 年向量库选型的核心结论很明确:如果已经在使用 PostgreSQL,pgvector 是最务实的选择。它通过 Postgres 扩展添加向量数据类型和相似度搜索操作符,向量嵌入与关系数据共处同一数据库,可以执行跨系统的 JOIN 查询。

pgvector 在 v0.7 版本加入 HNSW 索引后,性能已经可以与独立向量数据库竞争。默认 HNSW 配置即可达到 95%+ 的召回率。对于 RAG 场景,pgvector 通过 Prisma 或 Drizzle 集成后,在 1000 万向量规模以下完全够用。

其他选项的适用场景:Qdrant 适合需要自托管且对 ANN 搜索速度有极致要求的场景,支持亚毫秒级查询;Pinecone 适合追求零基础设施、快速上线的团队。如果使用 Supabase,其内置的 pgvector 是 Next.js 项目的自然选择——不需要新增服务,且可以将向量检索与用户数据通过标准 SQL JOIN 关联。

四、核心实现一:Agent 推理循环与工具调用

4.1 定义工具集

工具是 Agent 的“手脚”。在 Vercel AI SDK 中,工具通过 Zod schema 定义参数结构,SDK 自动将其转换为 LLM 可理解的 JSON Schema。

// lib/agent/tools.tsimport{tool}from'ai';import{z}from'zod';import{searchDocuments}from'@/lib/rag/search';import{queryDatabase}from'@/lib/db';exportconstagentTools={search_knowledge_base:tool({description:'在知识库中检索相关文档片段。当用户询问需要事实依据的问题时使用。',parameters:z.object({query:z.string().describe('检索关键词或语义查询'),topK:z.number().default(5).describe('返回的文档片段数量'),}),execute:async({query,topK})=>{constresults=awaitsearchDocuments(query,topK);returnresults.map(r=>({content:r.content,source:r.source,score:r.score,}));},}),query_structured_data:tool({description:'查询结构化数据库。当用户询问统计数据、列表或需要精确数值时使用。',parameters:z.object({sql:z.string().describe('SQL 查询语句'),}),execute:async({sql})=>{returnawaitqueryDatabase(sql);},}),};

工具描述的质量直接影响 Agent 的决策准确性。描述应该清晰说明“什么时候用这个工具”,而不仅仅是“这个工具做什么”。

4.2 构建推理循环

在 API Route Handler 中,使用streamText配合工具集和maxSteps启动 Agent 循环:

// app/api/chat/route.tsimport{streamText}from'ai';import{agentTools}from'@/lib/agent/tools';exportconstruntime='nodejs';exportconstdynamic='force-dynamic';exportasyncfunctionPOST(req:Request){const{messages,threadId}=awaitreq.json();constresult=streamText({model:'openai/gpt-4o',system:`你是一个企业知识助手。回答问题时优先使用 search_knowledge_base 工具检索知识库。 如果检索结果不足以回答,明确告知用户,不要编造信息。`,messages,tools:agentTools,maxSteps:5,});returnresult.toDataStreamResponse();}

maxSteps: 5限制了 Agent 的最大推理步数,防止无限循环。runtime = 'nodejs'确保 Route Handler 运行在 Node.js 运行时而非 Edge Runtime,因为向量库的数据库连接通常不支持 Edge。

4.3 使用 LangGraph 实现复杂控制流

当 Agent 需要条件分支和循环重试时,LangGraph 的状态图模型更合适。以下是一个带工具执行节点的循环图:

// lib/agent/graph.tsimport{StateGraph,END}from'@langchain/langgraph';import{ChatOpenAI}from'@langchain/openai';import{ToolExecutor}from'@langchain/langgraph/prebuilt';interfaceAgentState{messages:BaseMessage[];intermediateSteps:Array<[string,string,any]>;finalOutput?:string;retryCount:number;}constgraph=newStateGraph<AgentState>({channels:{messages:{value:(a,b)=>a.concat(b)},intermediateSteps:{value:(a,b)=>a.concat(b)},retryCount:{value:(a,b)=>b??a},},}).addNode('agent',agentNode)// LLM 推理节点.addNode('tools',toolNode)// 工具执行节点.addEdge('__start__','agent').addConditionalEdges('agent',shouldContinue,{tools:'tools',[END]:END,}).addEdge('tools','agent');// 工具执行后回到推理exportconstagentGraph=graph.compile();

这种图结构的核心价值在于:每一次“思考-行动-观察”循环都是显式的状态转换,可以精确追踪 Agent 在每一步做了什么决策、基于什么信息。

五、核心实现二:向量库集成与 RAG 检索

5.1 文档向量化流水线

文档从上传到可检索,需要经历分块、嵌入、存储三个阶段。在 Next.js 中,这一过程通过 Server Action 或 Route Handler 异步执行:

// lib/rag/ingest.tsimport{OpenAIEmbeddings}from'@langchain/openai';import{RecursiveCharacterTextSplitter}from'langchain/text_splitter';import{prisma}from'@/lib/db';constembeddings=newOpenAIEmbeddings({model:'text-embedding-3-small'});exportasyncfunctioningestDocument(content:string,metadata:{source:string;tenantId:string}){constsplitter=newRecursiveCharacterTextSplitter({chunkSize:800,chunkOverlap:200,});constchunks=awaitsplitter.createDocuments([content]);for(constchunkofchunks){constvector=awaitembeddings.embedQuery(chunk.pageContent);awaitprisma.$executeRaw`INSERT INTO document_chunks (content, embedding, source, tenant_id) VALUES (${chunk.pageContent},${vector}::vector,${metadata.source},${metadata.tenantId})`;}}

5.2 RAG 检索工具

检索工具是 Agent 工具集中最核心的一个。它需要处理查询嵌入、向量相似度搜索和结果格式化:

// lib/rag/search.tsimport{OpenAIEmbeddings}from'@langchain/openai';import{prisma}from'@/lib/db';constembeddings=newOpenAIEmbeddings({model:'text-embedding-3-small'});exportasyncfunctionsearchDocuments(query:string,topK:number,tenantId:string){constqueryVector=awaitembeddings.embedQuery(query);constresults=awaitprisma.$queryRaw`SELECT content, source, 1 - (embedding <=>${queryVector}::vector) AS score FROM document_chunks WHERE tenant_id =${tenantId}ORDER BY embedding <=>${queryVector}::vector LIMIT${topK}`;returnresults.filter(r=>r.score>0.7);}

注意WHERE tenant_id = ${tenantId}这个条件——多租户隔离必须嵌入检索层,而非在生成后过滤。这确保 Agent 的推理过程中不会“看到”不属于当前用户的内容。

5.3 上下文注入策略

检索到的文档片段需要以合适的方式注入 LLM 上下文。推荐的做法是将检索结果作为工具返回的消息,而非直接拼接到 System Prompt 中。这样做的好处是 Agent 可以自主决定何时检索、检索什么,而不是每轮对话都强制注入:

// 工具返回的结果会自动以 tool result 消息的形式进入对话历史// LLM 在下一步推理中会看到这些结果并据此生成回答// 不需要手动拼接 Prompt

六、核心实现三:流式交互与前端渲染

6.1 使用 useChat Hook 管理流式状态

Vercel AI SDK 的useChatHook 是前端流式交互的核心。它自动管理与 API Route 的流式连接、消息状态和输入处理:

// app/chat/page.tsx 'use client'; import { useChat } from 'ai/react'; export default function ChatPage() { const { messages, input, handleInputChange, handleSubmit, isLoading } = useChat({ api: '/api/chat', onToolCall: ({ toolCall }) => { console.log('Agent 正在调用工具:', toolCall.toolName); }, }); return ( <div className="flex flex-col h-screen max-w-3xl mx-auto p-4"> <div className="flex-1 overflow-y-auto space-y-4"> {messages.map(m => ( <div key={m.id} className={m.role === 'user' ? 'text-right' : 'text-left'}> <div className={`inline-block p-3 rounded-lg ${ m.role === 'user' ? 'bg-blue-500 text-white' : 'bg-gray-100' }`}> {m.content} </div> </div> ))} {isLoading && <div className="text-gray-400">Agent 思考中...</div>} </div> <form onSubmit={handleSubmit} className="mt-4 flex gap-2"> <input value={input} onChange={handleInputChange} className="flex-1 border rounded-lg px-4 py-2" placeholder="输入你的问题..." /> <button type="submit" className="bg-blue-500 text-white px-6 py-2 rounded-lg"> 发送 </button> </form> </div> ); }

onToolCall回调让前端可以感知 Agent 正在执行什么工具——这在构建 Agent 平台时尤其重要,用户需要看到 Agent 的“工作过程”而不仅仅是最终答案。

6.2 展示 Agent 的工具调用过程

Agent 平台与普通 Chatbot 的 UI 差异在于,需要展示中间的推理过程。Vercel AI SDK 的useChat会自动在消息中包含工具调用信息,前端可以据此渲染一个执行时间线:

// 在消息渲染中增加工具调用展示 {m.toolInvocations?.map((invocation, i) => ( <div key={i} className="text-sm text-gray-500 my-1 pl-4 border-l-2 border-gray-300"> {invocation.state === 'call' && `🔍 正在调用 ${invocation.toolName}...`} {invocation.state === 'result' && ( <details> <summary>📄 ${invocation.toolName} 返回结果</summary> <pre className="text-xs mt-1">{JSON.stringify(invocation.result, null, 2)}</pre> </details> )} </div> ))}

6.3 流式渲染的性能优化

LLM 逐 token 生成响应,传统方案需要等待完整生成后才能展示。useChat通过 SSE(Server-Sent Events)将 token 实时推送到浏览器,用户在网络往返的瞬间即可看到输出。

对于 Agent 平台,需要注意一个额外问题:工具调用会中断流式输出。当 Agent 决定调用工具时,流式响应会暂停,等待工具执行完成后再继续。前端应该通过onToolCall回调展示“正在检索”等状态提示,避免用户误以为系统卡死。

七、状态持久化:从内存到生产级

7.1 开发环境的会话管理

LangChain 的 Next.js 部署示例默认使用MemorySaver作为 checkpointer,会话状态存储在进程内存中。这在本地开发中完全够用,但在 Vercel 这样的 Serverless 环境中,冷启动和多实例部署会导致会话状态丢失。

7.2 生产环境的状态持久化

生产环境需要替换为持久化 checkpointer。LangChain 提供了多个选项:Redis(@langchain/langgraph-checkpoint-redis)、PostgreSQL(@langchain/langgraph-checkpoint-postgres)和 SQLite(@langchain/langgraph-checkpoint-sqlite)。

对于基于 pgvector 的架构,PostgreSQL checkpointer 是最自然的选择——不需要引入新的存储服务:

// lib/agent/index.tsimport{PostgresSaver}from'@langchain/langgraph-checkpoint-postgres';import{agentGraph}from'./graph';constcheckpointer=PostgresSaver.fromConnString(process.env.DATABASE_URL);awaitcheckpointer.setup();exportconstagent=agentGraph.compile({checkpointer});

7.3 多租户隔离

Agent 平台通常需要支持多个用户或团队。向量库层面的隔离通过tenant_id字段和 WHERE 条件实现,会话层面的隔离则需要在线程 ID 中嵌入租户标识。LangChain 的 Agent Protocol 支持通过/api/threads/:threadId/state端点读取和恢复检查点状态,每个 threadId 天然对应一个独立的会话空间。

八、部署与运维

8.1 Docker 容器化

将 Next.js Agent 平台容器化时,需要注意多阶段构建以减小镜像体积,以及环境变量的正确注入:

FROM node:22-alpine AS builder WORKDIR /app COPY package*.json ./ RUN npm ci COPY . . RUN npm run build FROM node:22-alpine AS runner WORKDIR /app COPY --from=builder /app/.next/standalone ./ COPY --from=builder /app/public ./public COPY --from=builder /app/.next/static ./.next/static ENV NODE_ENV=production ENV HOSTNAME="0.0.0.0" EXPOSE 3000 CMD ["node", "server.js"]

Docker 官方提供了基于 Vercel AI SDK 的 Next.js Chatbot 模板的容器化方案,其 Dockerfile 已经过生产验证。

8.2 Vercel 部署的注意事项

部署到 Vercel 时,有两点需要特别注意。第一,SSE 路由需要设置dynamic = 'force-dynamic',否则 Vercel 会尝试静态优化流式路由。第二,向量库的区域选择——如果向量库部署在特定区域,Next.js Functions 也应配置为同一区域,以最小化检索延迟。

8.3 成本控制策略

Agent 平台的成本主要来自三部分:LLM token 消耗、向量嵌入计算和数据库查询。工程层面的优化包括:对高频重复查询增加 Redis 缓存层,将简单问题路由到小模型处理,以及在非实时场景使用批处理。n8n 提供的 RAG 工作流模板中包含了完整的 Redis 缓存方案——在向量检索之前先检查缓存,命中则直接返回。

九、总结

在 Next.js 上构建 LLM Agent 平台的核心优势在于架构的简洁性:Agent 编排、向量检索和前端交互可以在同一代码库中完成,无需维护独立的后端服务。但这并不意味着可以忽略工程细节。状态持久化必须从开发环境的 MemorySaver 切换到生产级的 Redis 或 PostgreSQL checkpointer;多租户隔离必须嵌入检索层而非生成层;工具描述的质量直接决定 Agent 决策的准确性。

从技术趋势看,Agent 平台的工程化正在向“可观测、可恢复、可审计”的方向演进。LangChain 的 Agent Streaming Protocol 将流式事件标准化为协议层的事件序列,使得前端可以精确追踪 Agent 的每一步推理。Vercel 的 Workflow SDK 则让 Agent 在崩溃和超时后能够恢复执行。这些能力正在从“加分项”变为“及格线”。

对于正在构建 Agent 平台的团队,建议从最小可行架构开始:Next.js App Router + Vercel AI SDK + pgvector,先跑通单 Agent 的工具调用和 RAG 检索,再逐步叠加多租户、持久化和可观测性。工程化的价值不在于一次性搭建完美系统,而在于让系统在真实负载下持续可靠地运行。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/20 18:44:02

假肢矫形方向的科研绘图:五类图的必填项清单

假肢矫形方向的论文&#xff0c;图往往比字更吃重。研究对象是人体的力学交互&#xff0c;很多结论必须靠图来说。但图件参数多、系统杂&#xff0c;缺一项标注&#xff0c;图就等于没画。 下面按五类常见图件&#xff0c;把必填项列清楚。 一、步态曲线图 横轴必须归一化为…

作者头像 李华
网站建设 2026/10/22 13:40:40

Vue3+Vite项目CryptoJS导入报错?从ESM模块原理到4种解决方案

接手这个需求前&#xff0c;我正好在给一个 Vue3 项目做登录模块改造&#xff0c;同事把网上拷下来的 crypto-js.js 直接丢进 src/utils 里&#xff0c;然后用import * as CryptoJS from ./crypto-js.js引入。结果npm run dev一启动&#xff0c;控制台就飘红&#xff0c;加解密…

作者头像 李华
网站建设 2026/10/23 9:55:15

Access 2007数据分析指南:从Excel瓶颈到高效查询与清洗

简介&#xff1a;这是一份面向Access用户的数据分析经典教程&#xff0c;由微软认证应用开发师迈克尔亚历山大撰写&#xff0c;旨在帮助读者借助Access 2007完成从数据整理到查询分析的完整流程。书中系统对比了Access与Excel在可扩展性、分析透明度、数据与呈现分离等方面的差…

作者头像 李华
网站建设 2026/10/22 12:01:50

5%的人在寻找最稳的锚,他们在做什么

《国考热已死&#xff0c;基层热崛起》——真正稀缺的不是编制&#xff0c;而是在变化周期里站对位置的人3.57万个岗位&#xff0c;2.69万个在县以下。先别急着算竞争比。这组数字里藏着一个很多人没看懂的变化&#xff1a;过去拼的是进入体系&#xff0c;现在拼的是进入哪里。…

作者头像 李华
网站建设 2026/10/22 12:49:54

小恶意代码识别器(3)

数据校验&#xff1a;把坏数据挡在门外 上一篇写完读取函数&#xff0c;这篇写第二个函数 validate_dataset。 为什么需要单独一个校验函数 读取函数里其实已经挡了两类问题&#xff1a;拆不出六个数的行会被跳过&#xff0c;超过数组上限会停下来。但数值本身合不合格&#xf…

作者头像 李华