☰
从0到1搭建AI Agent平台:让LLM变身能干的数字同事
2026/9/26 14:02:29 网站建设 项目流程

“从 0 到 1 搭建你的 AI Agent 平台:当 Agent 有了工厂,人人都能造同事”——这句话真正值得琢磨的,不是“Agent”这个流行词,而是“工厂”。搭一个能聊天的Agent,现在的模型能力已经足够,真正难的是把一个一个“会聊天的原型”变成“能批量上岗的数字同事”。我过去半年一直在折腾这件事,踩过的坑比写过的代码多。这篇文章不打算做概念科普,就按我从零搭平台的真实路径,把AI Agent和LLM、AI模型的区别讲清楚,把最小闭环、工具调用、记忆设计、多Agent编排、可观测性这些环节一个个拆开,顺便把Spring AI + DeepSeek这套企业级玩法完整过一遍。适合正在选型或者已经决定自建Agent平台的团队参考。

1. 先搞清楚:AI Agent、LLM、AI 模型到底差在哪

很多团队上来就喊“我们要做个Agent平台”,但拉通之后发现大家说的根本不是一回事。有人觉得Agent就是ChatGPT套壳,有人觉得把大模型API接上就完事了,还有人纠结“DeepSeek和GPT哪个是Agent”——这几个概念必须先在团队里对齐,否则后面所有架构讨论都是鸡同鸭讲。

AI模型是最底层的东西,本质是一个参数化函数:输入一串token,输出一串token或者向量。GPT系列、DeepSeek-V3/R1、Qwen这些都属于这一层。LLM是AI模型里专攻语言理解和生成的那一类,现在大家口中的“大模型”“基座模型”,通常指的就是LLM。它们本身不产生行动,只产生“下一步该说什么”的预测。

Agent是什么?它不是模型,而是拿着模型去干活的完整程序。一个Agent要有明确目标,要有记忆来存储对话和历史决策,要能调用外部工具去影响真实世界,还要有个循环调度机制来决定下一步做什么。你可以把LLM想成一个刚毕业的聪明新人,而Agent是给这个新人配好了工位、电脑、通讯录、任务清单和社保卡的完整员工。同一个新人可以干不同岗位,同一个LLM也能被多个Agent共用。

1.1 DeepSeek 到底属于哪一层

这个热搜问题很典型。DeepSeek(比如deepseek-chat、deepseek-reasoner)属于“模型层”,它就是一个LLM。你可以把它接到自己的Agent里当大脑,但如果你只是打开DeepSeek官网聊了几句,那你既没有在搭建Agent,也没有在使用Agent,你只是在调用一个聊天机器人。Agent和普通聊天的分水岭在于:模型有没有主动调用工具、有没有为了完成目标做多轮推理、有没有记住关键信息。

我经常用一句话跟团队里的人对齐:**模型是发动机,Agent是整车。**发动机可以单独卖,但你要运货,必须把发动机装进底盘、接上方向盘、加上油箱。DeepSeek很便宜、能力也不错,作为Agent平台的首个模型接入是很务实的选择,但它只解决“大脑”那一层的问题,平台要解决的是剩下所有的“身体”和“流程”。

1.2 为什么需要“Agent工厂”,而不是“一个Agent走天下”

单个Agent只能解决一个具体的任务闭环。比如“售后工单分类Agent”,你把工单文本传给它,它返回分类结果,这已经很好了。但企业要的是“造50个数字同事”:一个负责工单分类,一个负责知识库问答,一个负责定时巡检,一个负责生成日报,每个都有不同的工具权限、不同的知识库、不同的会话策略。这时候你再一个个手工去写、去调、去部署,维护成本就会爆炸。

“Agent工厂”的价值就在于把重复的部分抽象出来:模型接入统一管理、工具注册统一规范、记忆逻辑统一实现、权限审计统一治理。新同事的上线流程从“开发两周”压缩到“配置半天”,这才是平台存在的意义。所以我把“当一个Agent跑通之后,如何让第2个、第50个Agent快速产出”当成整个从0到1建平台的第一目标。

2. 从0到1拆解最小闭环:Agent必须要有的4个零件

先看一个最简单的Agent长什么样。不带花哨架构,抛掉K8s、抛掉多租户,一个能跑的Agent闭环至少要包含四个零件:

  1. 大脑(LLM):负责理解、推理、生成。比如DeepSeek、通义千问、GPT系列。
  2. 记忆:短期记忆是当前会话的上下文窗口;长期记忆是存在向量数据库里的历史事实、偏好、业务规则。
  3. 工具:让Agent具备行动能力。查天气、查订单、写数据库、发邮件、调内部API,都属于工具。
  4. 调度循环:决定“下一步做什么”的循环逻辑。先规划、再调用工具、观察结果、再规划,直到任务完成或到达最大轮数。

在这四件套里,工具和调度是普通对话系统不会碰的东西,也是Agent能够“干活”而不是“聊天”的关键。

2.1 工具调用的真实流程:一个“查天气”的例子

以“查天气”为例。用户问“北京今天适合穿短袖吗?”,没有工具时,模型只能凭训练数据里的模糊记忆回答,而且大概率是错的。接入工具后,调用链是这样的:

  • 用户提问进入Agent;
  • 调度器把“当前问题 + 可用的工具列表”发给LLM;
  • LLM判断需要实时天气数据,返回一个结构化指令:调用getWeather,参数{city: "北京"};
  • Agent平台捕获这个指令,执行真实天气API,拿到“晴、28℃、北风3级”;
  • 把工具结果连同原始问题一起回传给LLM;
  • LLM最终给出“北京今天28℃,体感偏热,建议穿短袖或薄衬衫”。

这个流程很多人叫Function Calling,本质上是模型在你定义好的工具清单里做选择题。工具描述写得越清晰,模型选对的概率越高;工具返回的数据越规范,最终回答质量越可控。

2.2 技术选型:Java 系还是 Python 系

这是我在各个团队被问最多的问题。我的答案其实很直接:如果你的团队主要搞业务系统、现有技术栈是Spring Cloud,那就别犹豫,走Spring AI。如果你的目标是快速做研究和算法验证、团队以Python为主,那就走LangChain/LlamaIndex。

Python生态在Agent领域确实起步早,LangChain、LangGraph、LlamaIndex、CrewAI你都能找到大量参考资料。但Python体系下做生产级平台,工程化成本一点都不低。LangChain的API大版本之间经常改接口,升级一次想骂人;多Agent编排在Python里往往要靠自己写状态机。而Spring AI后发有个好处,就是背靠Spring Boot的成熟工程体系:配置管理、依赖注入、监控埋点、网关安全通通可以复用。

我用Spring AI的一个很直接的原因是:企业内部已经有很多Java写的服务,Agent平台要调用订单系统、CRM、ERP的API,Java这边直接封装SDK就能接入,不用跨语言再包一层HTTP。统一技术栈带来的维护收益,在平台进入长期迭代后会越来越明显。

3. 亲手造一个“同事”:Spring AI + DeepSeek 实现可对话的Agent

光讲概念没有用,直接上手把第一个Agent跑起来。这里用Spring AI接入DeepSeek,因为DeepSeek API兼容OpenAI协议,所以可以直接用OpenAI的starter来对接,成本低、接入快。

3.1 环境准备与依赖引入

基础环境:

  • JDK 17 或 21
  • Spring Boot 3.2 或更高版本
  • Maven 3.8+
  • DeepSeek API Key(没有就去官方平台申请,充值几块钱就够测试)

Maven依赖,核心就这两个:

<dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-model-openai</artifactId> <version>1.0.0</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>

这里要注意,Spring AI版本迭代很快,1.0.0之后的API和之前0.8.x有较大差异。建议直接用一个稳定版本,后面所有代码示例都基于Spring AI 1.0.0。

然后配置application.yml:

spring: application: name: agent-factory ai: openai: base-url: https://api.deepseek.com/v1 api-key: ${DEEPSEEK_API_KEY} chat: options: model: deepseek-chat temperature: 0.6

base-url指向DeepSeek的OpenAI兼容接口就行。如果你后面要接其他模型(比如通义千问也兼容OpenAI协议),改这个base-url和model就完事,平台支撑多模型的能力从第一行配置就开始铺垫了。

3.2 注册工具:让Agent具备行动力

注册一个“查询天气”的工具。在Spring AI里,工具通过FunctionCallback注册,然后交给ChatClient调用。

先定义入参对象:

public record WeatherRequest(String city) {}

再定义返回对象:

public record WeatherResponse(String city, String condition, Integer temperature) {}

接着实现一个工具类:

import org.springframework.ai.model.function.FunctionCallback; import org.springframework.ai.model.function.FunctionCallbackWrapper; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class AgentToolsConfig { @Bean public FunctionCallback weatherFunction() { return FunctionCallbackWrapper.builder("getWeather", (WeatherRequest req) -> { // 真实项目中这里调用天气API,比如和风天气、高德天气 // 测试阶段可以先返回固定数据 return new WeatherResponse(req.city(), "晴", 28); }) .setDescription("查询指定城市的实时天气情况") .setInputType(WeatherRequest.class) .build(); } }

这个description特别重要。模型不理解Java方法名,它只读这段描述来决定“要不要用这个工具”。描述要写清楚“什么时候用、参数含义是什么”,比如“当用户询问某个城市当前天气、温度、是否适合出行时,调用此工具”。千万不要只写“查询天气”。

3.3 写一个带工具的ChatClient

用ChatClient完成对话循环:

import org.springframework.ai.chat.client.ChatClient; import org.springframework.stereotype.Service; @Service public class AgentService { private final ChatClient chatClient; public AgentService(ChatClient.Builder builder) { this.chatClient = builder.build(); } public String chat(String userMessage) { return chatClient.prompt() .user(userMessage) .functions("getWeather") // 绑定天气工具 .call() .content(); } }

写个Controller跑起来:

@RestController public class AgentController { private final AgentService agentService; public AgentController(AgentService agentService) { this.agentService = agentService; } @PostMapping("/chat") public String chat(@RequestBody Map<String, String> body) { return agentService.chat(body.get("message")); } }

启动后,POST一个{"message": "北京现在适合穿短袖吗?"},Agent会先通过getWeather拿到北京天气,再结合温度回答你。如果你没有绑定functions("getWeather"),模型就只会瞎猜。这一个区别就是“聊天机器人”和“Agent”的分水岭。

3.4 给Agent加上记忆,别让它“转身就忘”

这个阶段的问题马上会出现:用户说“那上海呢?”,Agent不知道“那”指的是什么。因为每次请求都是独立的,没有上下文。

短期记忆最简单的实现是维护一个Conversation会话对象:

public class Conversation { private String conversationId; private List<Map<String, Object>> messages = new ArrayList<>(); public void addUserMessage(String content) { messages.add(Map.of("role", "user", "content", content)); } public void addAssistantMessage(String content) { messages.add(Map.of("role", "assistant", "content", content)); } }

调用时把历史消息一起传进去。Spring AI的ChatClient支持messages()传历史:

public String chat(String conversationId, String userMessage) { Conversation conv = conversationStore.get(conversationId); conv.addUserMessage(userMessage); String reply = chatClient.prompt() .messages(conv.toMessageList()) .functions("getWeather") .call() .content(); conv.addAssistantMessage(reply); conversationStore.save(conversationId, conv); return reply; }

长期记忆更复杂。简单说就是把业务事实提取出来,向量化后存进向量数据库(比如Redis,或独立的Milvus、pgvector),下次用户提问时先做相似度检索,把相关记忆塞进提示词里。这个在平台设计里再接,但不能不做,因为Agent最怕“每句话都当第一次见面”。

4. 从“单个Agent”到“Agent工厂”:平台化的六个核心设计

一个Agent跑通是第一步,但离“工厂”还有很大距离。下面这六个设计,是我认为平台化过程中最核心、也最容易踩坑的地方。

4.1 多Agent编排与消息路由

50个Agent上线后,用户应该找谁?这需要一个路由层。常见的做法是“主管Agent + 专业Agent”模式。主管Agent主要负责意图识别,把任务分发给对应的专业Agent;专业Agent各自维护自己的提示词、工具集和知识库。比如:

  • 售后Agent:负责退换货、订单问题,工具权限绑定订单API、售后API;
  • 知识Agent:负责员工制度问答,挂载HR制度知识库;
  • 运维Agent:负责日志查询、告警摘要,工具权限绑定日志平台、监控平台。

主管Agent本身不做具体业务,它只做“应该让谁处理这件事”的分类。这个设计能大幅降低单个Agent提示词的复杂度,也方便后续新增Agent:新同事注册到工厂,主管Agent的工具列表里多一项选择即可。

4.2 任务队列与异步执行

不是所有Agent都需要实时响应。批量生产日报、定时巡检、大数据量分析,这些任务如果同步跑,用户会一直卡在加载里。平台需要一个统一的异步任务队列。

我用RabbitMQ做任务分发的时候,核心流程是:

  1. 用户提交任务,API返回taskId;
  2. Agent编排器把任务发送到对应队列;
  3. 消费者拉取任务,调用Agent执行;
  4. 执行进度写回任务状态表;
  5. 用户通过轮询或WebSocket查询进度,完成后获取结果。

任务状态机至少要设计这几个状态:

状态含义常见流转
PENDING排队中提交 -> PENDING
RUNNING执行中PENDING -> RUNNING
WAITING_TOOL等待工具调用结果RUNNING -> WAITING_TOOL -> RUNNING
SUCCEEDED成功RUNNING -> SUCCEEDED
FAILED失败RUNNING -> FAILED
CANCELLED取消任意 -> CANCELLED

为什么状态机这么重要?因为Agent执行过程中有大量外部工具调用,一个HTTP请求可能几十秒,如果进程重启,至少要知道任务跑到哪一步了。

4.3 可观测性与成本控制

Agent平台有一个非常现实的问题:不可观测。传统接口慢,你打一个日志就能看到耗时。Agent慢,可能是模型推理慢、可能是工具调用慢、可能是规划循环太多轮,而且每次调用都在花钱。

我强制要求平台记录以下指标:

  • 每次调用的模型名称;
  • 输入Token数和输出Token数;
  • 工具名称和每次工具的耗时;
  • 执行总耗时;
  • 总轮数;
  • 估算成本;
  • 用户和租户标识。

用OpenTelemetry把追踪接到企业现有的监控体系里,同时在日志中输出这次Agent调用的完整轨迹:

[AgentTrace] taskId=63a91f, userId=u1002 -> plan: need weather -> tool: getWeather(city=北京), cost=120ms, ok -> plan: final answer -> llm: model=deepseek-chat, promptTokens=680, completionTokens=210 -> total: 1.84s, cost=0.0021 CNY

这样每次用户投诉“这个Agent怎么这么慢”,你都不用猜,直接看轨迹链路是卡在哪个环节。成本控制也一样,按租户聚合Token消耗,月底一算清清楚楚。成本失控往往不是模型贵,而是提示词里塞了太多历史记录和没用的检索上下文,后面排查章节会说。

4.4 权限模型与安全隔离

Agent能调用什么工具,必须跟着用户权限走,不能跟着Agent自己的“热情”走。一个普通员工问“帮我查询所有人的工资”,工资查询工具虽然存在,但如果这个用户没权限,Agent就算生成了调用意图,平台也要拦截。

工具权限要做两层校验:

  • 注册时声明工具可见范围:比如salaryQuery工具标记为role=HR_MANAGER;
  • 执行时校验用户身份:拿到当前用户上下文,校验角色、部门、数据权限。

另外,API Key绝对不能出现在前端代码里。平台统一管理模型API Key,用户只能通过后端的Agent服务间接使用。如果Agent配置了写数据库这种破坏性工具,建议加一个人工审批节点:Agent生成SQL和执行计划后,先推送给管理员审核,确认后才真正执行。

4.5 知识库与RAG的接入

Agent如果没有企业知识库支撑,就是个“通才但不是行家”。要让Agent回答“公司报销流程是什么”,光靠提示词写进去是不现实的,得接入RAG。

这里的标准链路是:文档上传 -> 文本切片 -> 向量化 -> 存入向量库 -> 用户提问时检索TopK -> 拼进提示词。

我做RAG时被教育了两次,值得说一下:

  • 切片大小不能一刀切。制度文档适合按章节切,技术手册适合按标题+代码块切,500字固定切片效果通常一般。
  • 检索结果必须标注来源。多文档之间有冲突时,Agent如果选错了信息源,后果很严重。把来源文档名和段落编号一起传给模型,能让它在引用时更谨慎。

4.6 Agent注册中心与统一生命周期管理

工厂的最后一块拼图是“注册中心”。每上线一个Agent,应该像一个应用服务一样被登记:

agent: id: after-sale-assistant name: 售后助手 model: deepseek-chat systemPrompt: 你是售后客服,负责处理订单和退换货问题 tools: - getOrderDetail - createReturnOrder - queryLogistics knowledgeBaseId: kb_after_sale maxIterations: 5 owner: customer-service-team

这个配置应该DB化、平台化管理,而不是写死在代码里。新Agent上线 = 填一张配置表 + 做好工具权限评审;下线 = 关闭一个开关。这样“造同事”才真正变成流水线操作。

5. 常见问题与排查技巧实录

这节全是我自己踩过的坑,每个都有血有泪,但踩完之后能形成平台的反面教材清单。

5.1 工具调用失效:模型不调用,或者乱调用

症状:用户问“今天天气怎么样?”,模型完全忽略你注册的getWeather工具,直接凭记忆回答。

排查思路:

  • 确认模型是否支持function calling。DeepSeek的deepseek-chat是支持的,但有些小模型不支持;
  • 确认工具描述写得是否具体。查询天气和当用户询问某个城市的天气、气温、风力、是否适合出行时,调用此工具,参数city为城市名,命中率高一个量级;
  • 确认FunctionCallback有没有绑定到当前ChatClient,我经常改了配置却忘了重启服务。

另一个常见情况是参数格式不匹配。LLM返回的参数和你的Java入参类型对不上,Spring AI会在执行时报JSON parse error。解决办法是把入参结构写得尽量简单,用String接后再解析,比强类型对象更抗变。

5.2 上下文越塞越满,Token成本失控

这是我最心疼的教训。第一次上线Agent时,为了让它“记住”用户说的每一句话,我把整个对话历史每次请求都完整发给模型。结果用户聊了20句之后,一次请求的输入Token飙到8000多,成本直线上升,而且模型响应变慢,反而会遗忘早期的重要信息。

可行的方案:

  • 滑动窗口:只保留最近N轮对话,老对话截断;
  • 历史摘要:每过N轮,让模型把之前的对话压缩成一段摘要,只保留关键信息(用户目标、已确认事实、未解决问题);
  • 长期记忆单独存向量库,需要时才检索回来。

个人建议从滑动窗口开始,够用、简单、不折腾。等使用量上来,再考虑摘要和向量记忆。

5.3 Agent陷入死循环

症状:Agent反复调用同一个工具,拿着相同的结果规划下一步,一跑就是几十轮,费用狂飙。

原因一般是模型在复杂任务里“绕不出去”,或者工具的返回信息不足以让它做出决策。

我采用的兜底策略:

  • 强制最大迭代次数(比如5轮),超过就停;
  • 重复动作检测:如果最近3次都是同一个工具+相同参数,主动打断,把“已尝试过同样的方法”写进提示词再试一次,还不行就放弃;
  • 设置单任务成本上限:执行中累计Token成本超过阈值,自动终止,返回人工介入。

这三条直接落到平台层面,根本不给单次任务无限跑的机会。

5.4 Agent输出格式不稳定

症状:你要求“用JSON格式返回”,模型有时候给你返回Markdown代码块包裹的JSON,有时候给JSON后面还带一句“以上是我的回答”。下游程序一解析就抛异常。

靠提示词约束格式不稳定,一定要用结构化输出。Spring AI里可以要求模型返回指定类型的对象,DeepSeek也有JSON Output模式来强制输出合法JSON。另外提醒一句:即使开了JSON模式,返回的仍然可能被套在json代码块里,解析前先做一次字符串清洗,把起止标记去掉。

String raw = content; raw = raw.replaceFirst("^```(json|JSON)?\\s*", ""); raw = raw.replaceFirst("\\s*```$", "");

这个小处理,帮我省掉了无数次解析报错。

5.5 多Agent之间互相混淆

症状:售后服务Agent突然回答员工制度问题,或者知识Agent被用户绕过去查订单,一片混乱。

原因多半是路由Agent的判定不严格,或者专业Agent的系统提示词边界不清。我的经验是,每个专业Agent在系统提示词里必须明确写“你负责处理什么、不负责什么、遇到超出范围的内容怎么回答”。如果主管Agent把任务分错了,专业Agent要能拒绝而不是硬答。

6. 我对Agent平台落地的一些体会

平台跑了大半年,最深的感受是:Agent平台的工程难点,90%不在模型,而在模型之外。工具注册、权限校验、任务状态机、Token成本监控、记忆管理,这些工作没有AI含量,但每一个都能让一个看似聪明的模型变成真正靠谱的同事。

第二点体会是:不要一上来追求“全自动”。平台前期可以先做“人审环节”兜底,比如Agent生成的对外回复,关键操作先人工确认再发出去。这不是倒退,这是给系统上保险,等运行数据积累够了,再把人工占比慢慢降下来。我见过太多团队一上来就搞全自动化,结果出了一次严重事故,整个项目被叫停。

第三点,从0到1搭建的时候,选一个顺手的框架,然后专注在业务工具和平台能力上,别总想着从底层自研模型编排。当前阶段的竞争不在模型,而在于谁把Agent和业务流程、组织权限、知识资产结合得更顺滑。

最后分享一个实用小技巧:给每个Agent起一个“人设”版本号。提示词和工具配置改了一个字,都要在后台留下变更记录。Agent是你造出来的“同事”,但它不是一个可以随便口头交代两句就能改明白的人。把每次变更都当成一次发布,这是让Agent工厂稳定运转的重要习惯。

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

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

立即咨询