
1. 为什么 Java 后端一碰 Agent 就“精神分裂”1.1 一个真实到肉疼的场景去年底我接手一个客服工单自动分类的需求业务侧的原话是“让大模型读工单自动打标签、自动派单”。听起来简单我一开始也是这么想的Java 后端嘛调个 HTTP 接口把工单文本塞进去拿回 JSON解析入库完事。结果上线第一周就翻车了。同一个工单“用户反馈 App 登录后白屏重启无效”今天模型返回“客户端渲染异常”明天返回“网络连接问题”后天干脆返回“建议联系人工客服”。标签飘忽不定派单规则直接乱套。更离谱的是模型偶尔会“脑补”出一个根本不存在的错误码还煞有介事地写进 JSON 里。这就是典型的Agent 幻觉——模型在缺乏确定性约束的情况下会自由发挥输出看似合理但实际错误的结果。Java 后端的思维方式和 LLM 的思维方式本质上是冲突的。Java 讲究确定性输入 A 必然得到 B异常有明确的堆栈事务要么提交要么回滚。而 LLM 是概率性的同样的输入温度参数稍微一动输出就变了。你让一个写惯了if-else和try-catch的后端工程师去驯服一个“会做梦”的模型这中间的鸿沟不是调个 API 就能填平的。1.2 幻觉到底从哪来要驯服它先得搞清楚它为什么会产生幻觉。我踩了几个月坑总结下来无非三个来源。第一是上下文缺失。模型不知道你的业务规则你只给它一段工单文本它只能靠通用知识猜。比如“白屏”这个词在 Web 前端语境下是页面渲染问题在移动端可能是 WebView 崩溃在桌面端可能是显卡驱动。你不告诉它你的业务边界它就只能瞎猜。第二是输出格式不受约束。你让模型“返回一个标签”它可能返回“标签客户端异常”也可能返回“我认为这是客户端异常”还可能返回一段解释文字。Java 后端拿到这种非结构化输出解析起来就是灾难。第三是多步推理的累积误差。如果让模型自己决定“先分类再派单再生成回复”每一步都有概率出错三步下来正确率可能从 90% 掉到 70%。这就是为什么很多 Agent 在 demo 里表现惊艳一上生产就拉胯。1.3 确定性工作流的核心思路我的解法是把 Agent 的“自由发挥”压缩到最小范围用工作流把确定性逻辑接管过来。具体来说就是让 LLM 只做它最擅长的事——理解自然语言、做语义判断而把流程控制、格式校验、异常处理、重试逻辑全部交给工作流引擎。这里我选的是n8n。为什么不用纯 Java 写因为纯 Java 写工作流你得自己实现节点调度、状态管理、失败重试、可视化调试工作量巨大。n8n 是一个开源的工作流自动化工具它把“节点编排”这件事做成了可视化拖拽同时支持自定义代码节点Java 后端可以通过 HTTP 接口和它对接。更关键的是n8n 的每个节点都是确定性的上一个节点的输出格式不对下一个节点直接报错不会“猜”。配合MCPModel Context Protocol协议我们可以把 Java 后端的业务能力封装成标准化的工具让 Agent 按需调用而不是让模型凭空生成。这样一来Token 消耗也大幅下降——因为模型不需要在提示词里塞一大堆业务规则它只需要知道“有哪些工具可用”具体规则由工具本身保证。我实测下来同样的工单分类任务纯提示词方案平均每次消耗 3200 Token改成 n8n 工作流 MCP 工具调用后降到 640 Token 左右直降 80%。这不是玄学是因为提示词从“塞满业务规则”变成了“只描述任务意图”上下文长度大幅缩短。2. 整体架构设计让 Java 管确定性让 n8n 管编排2.1 分层架构的取舍我把整个系统分成四层每一层的职责边界非常清晰。层级职责技术选型确定性程度接入层接收工单、鉴权、限流Spring Boot JWT完全确定编排层流程控制、节点调度、重试n8n完全确定能力层业务工具、数据查询、规则校验Java MCP Server完全确定推理层语义理解、意图识别、文本生成LLM概率性这个架构的核心思想是推理层只负责“理解”不负责“决策”。模型输出的是“意图”和“参数”而不是最终结果。最终结果由能力层根据确定性规则计算得出。举个例子。用户工单说“我昨天买的会员今天怎么没了”。模型的任务是识别出意图是“会员状态异常”并提取出关键实体“会员”“昨天购买”。至于这个用户到底是不是会员、会员什么时候到期、该不该退款这些全部由 Java 能力层查数据库、跑规则引擎来决定。模型不碰这些也就没机会幻觉。2.2 为什么选 n8n 而不是纯 Java 编排有人会问我 Java 后端自己写个状态机不就行了为什么要引入 n8n我一开始也是这么想的用 Spring StateMachine 写了一个版本。问题在于状态机的可视化调试太痛苦了。工单分类有 12 个分支每个分支有 3 到 5 个步骤状态转移图复杂到我自己都看不懂。每次加一个新规则都要改代码、重新部署、写单元测试迭代速度极慢。n8n 的优势在于流程即配置。我在画布上拖拽节点连线设置条件分支保存即生效。调试的时候每个节点的输入输出都看得一清二楚哪个节点返回了意外数据一眼就能定位。而且 n8n 支持 Webhook 触发Java 后端只需要发一个 HTTP 请求剩下的编排逻辑全部由 n8n 处理。当然n8n 也不是银弹。它的自定义代码节点性能不如原生 Java复杂计算还是得回调 Java 服务。所以我的做法是n8n 负责流程编排和轻量数据处理重计算和数据库操作全部通过 MCP 工具回调 Java。2.3 MCP 协议在其中的角色MCP 是 Anthropic 推出的一个开放协议全称 Model Context Protocol。它的作用是标准化“模型如何调用外部工具”。在没有 MCP 之前每个模型厂商的工具调用格式都不一样OpenAI 用 function callingClaude 用 tool use切换模型就要重写一遍适配层。MCP 把这些统一了。你只需要按照 MCP 协议实现一个 Server暴露工具列表和调用接口任何支持 MCP 的客户端都能直接调用。在 n8n 里有一个 MCP 节点可以连接到你的 MCP Server把工具列表拉过来让 Agent 按需调用。我的 Java 能力层就是一个 MCP Server暴露了这些工具queryMemberStatus(userId)查询会员状态getOrderDetail(orderId)查询订单详情checkRefundEligibility(orderId)检查退款资格createTicket(category, priority, assignee)创建工单validateCategory(category)校验分类是否合法模型在推理时只需要输出“我要调用 queryMemberStatus参数是 userId12345”n8n 就会自动执行这个工具把结果返回给模型。模型不需要知道会员状态的判断规则它只需要知道“有这个工具可用”。2.4 Token 直降 80% 的账怎么算很多人好奇 Token 是怎么降下来的。我拿实际数据算一笔账。纯提示词方案的 System Prompt 大概长这样你是一个客服工单分类助手。请根据以下规则分类 1. 如果用户提到登录、密码、验证码归类为“账号问题” 2. 如果用户提到支付、退款、订单归类为“交易问题” 3. 如果用户提到闪退、白屏、卡顿归类为“性能问题” ...此处省略 2000 字业务规则 请返回 JSON 格式{category: ..., priority: ..., assignee: ...}这个 System Prompt 本身就有 2500 Token 左右加上用户工单文本 200 Token模型输出 500 Token总计 3200 Token。n8n MCP 方案的 System Prompt 简化成你是一个客服工单助手。你可以调用以下工具来完成任务 - queryMemberStatus: 查询会员状态 - getOrderDetail: 查询订单详情 - createTicket: 创建工单 请先理解用户意图然后调用合适的工具。这个 System Prompt 只有 300 Token 左右。用户工单文本 200 Token模型输出工具调用请求 100 Token工具返回结果 200 Token模型最终生成回复 100 Token总计 900 Token。等等我说的是降 80%3200 降到 640 才是 80%。那 640 是怎么来的因为在实际运行中很多工单根本不需要调用 LLM。比如“查询订单状态”这种n8n 工作流直接根据关键词路由到 Java 接口压根不走模型。只有真正需要语义理解的工单才走 LLM。综合下来平均每次工单处理的 Token 消耗就是 640 左右。3. 核心细节解析Java 侧和 n8n 侧各自要做什么3.1 Java 侧MCP Server 的实现要点Java 实现 MCP Server核心是三个东西工具注册、参数校验、结果序列化。工具注册我用的是注解扫描的方式。定义一个McpTool注解标注在方法上启动时扫描所有带这个注解的方法把工具名、描述、参数 schema 注册到一个全局注册表里。McpTool(name queryMemberStatus, description 根据用户ID查询会员状态) public MemberStatus queryMemberStatus( McpParam(name userId, description 用户ID, required true) String userId) { // 实际查询逻辑 return memberService.getStatus(userId); }参数校验这块我踩过一个坑。MCP 协议要求参数 schema 是 JSON Schema 格式但 Java 的类型系统跟 JSON Schema 不是一一对应的。比如 Java 的Long对应 JSON Schema 的integer但long基本类型在反射时拿不到泛型信息。我的做法是统一用包装类型并且在注解里显式声明类型。结果序列化也有讲究。MCP 要求返回结果是一个 content 数组每个元素有 type 和 text 字段。我封装了一个McpResponse类自动把 Java 对象转成 JSON 字符串再包装成 MCP 格式。注意MCP Server 的接口必须做幂等设计。因为 n8n 在失败重试时可能会重复调用同一个工具如果createTicket不是幂等的就会创建重复工单。我的做法是让每个工具调用都带一个requestId服务端根据requestId去重。3.2 n8n 侧工作流节点的编排逻辑n8n 工作流的核心节点类型我用到了这几种Webhook 节点接收 Java 后端发来的工单数据Switch 节点根据工单关键词做初步路由MCP 节点连接 Java MCP Server拉取工具列表Agent 节点调用 LLM 做语义理解和工具调用决策Code 节点做轻量数据转换和格式校验HTTP Request 节点回调 Java 接口执行最终操作工作流的整体逻辑是这样的Webhook 收到工单提取content和userIdSwitch 节点判断如果内容包含“订单”“物流”“退款”等关键词走交易分支如果包含“登录”“密码”“验证码”走账号分支否则走通用分支交易分支和账号分支直接调用对应的 Java 接口不经过 LLM通用分支进入 Agent 节点由 LLM 决定调用哪个 MCP 工具Agent 节点返回工具调用请求n8n 执行 MCP 工具工具返回结果后再交给 LLM 生成最终回复Code 节点校验最终回复的格式确保符合预期HTTP Request 节点把结果写回 Java 后端这个设计的关键在于大部分工单根本不走 LLM。只有那些无法通过关键词路由的“模糊工单”才需要语义理解。这就是 Token 降下来的核心原因。3.3 提示词工程怎么让模型“少说话”在 n8n 的 Agent 节点里提示词的设计非常关键。我的原则是能不说就不说能少说就少说。System Prompt 只保留三部分角色定义、工具列表、输出格式要求。业务规则全部下沉到工具实现里。你是一个工单处理助手。你的任务是理解用户意图并调用合适的工具。 可用工具 {{ $json.tools }} 输出要求 1. 如果需要调用工具只输出 JSON{tool: 工具名, params: {...}} 2. 如果不需要调用工具直接输出{reply: 回复内容} 3. 不要输出任何解释性文字这个提示词只有 150 Token 左右。工具列表由 MCP 节点动态注入不需要硬编码在提示词里。还有一个技巧是限制输出长度。在 Agent 节点的参数里把max_tokens设成 256。因为工具调用请求本身很短不需要模型长篇大论。输出长度限制住了Token 消耗自然就下来了。3.4 格式校验把幻觉挡在入库之前即使做了这么多约束模型偶尔还是会输出格式不对的 JSON。比如多了一个逗号或者把params写成了parameters。这时候就需要 Code 节点做校验。我的 Code 节点逻辑是这样的const output $input.first().json; try { const parsed JSON.parse(output.text); if (parsed.tool) { // 校验工具名是否在允许列表中 const allowedTools [queryMemberStatus, getOrderDetail, createTicket]; if (!allowedTools.includes(parsed.tool)) { throw new Error(Invalid tool: parsed.tool); } return { valid: true, data: parsed }; } else if (parsed.reply) { return { valid: true, data: parsed }; } else { throw new Error(Missing tool or reply field); } } catch (e) { // 格式错误走降级分支 return { valid: false, error: e.message }; }如果校验失败工作流会走降级分支直接创建一个“待人工处理”的工单不再重试 LLM。因为重试大概率还是错的不如直接转人工。实操心得格式校验的降级策略一定要有。我见过太多团队模型输出格式错了就无限重试结果 Token 烧光了问题还没解决。正确的做法是重试一次还失败就降级。4. 实操过程从零搭一套可运行的工作流4.1 环境准备与依赖安装先列一下我用的环境JDK 17Spring Boot 3.2n8n 1.30Docker 部署PostgreSQL 15n8n 的持久化存储一个支持 MCP 的 LLM 服务n8n 的部署我用的是 Docker Compose配置文件如下version: 3.8 services: n8n: image: n8nio/n8n:1.30.0 ports: - 5678:5678 environment: - DB_TYPEpostgresdb - DB_POSTGRESDB_HOSTpostgres - DB_POSTGRESDB_DATABASEn8n - DB_POSTGRESDB_USERn8n - DB_POSTGRESDB_PASSWORDn8n_password - N8N_BASIC_AUTH_ACTIVEtrue - N8N_BASIC_AUTH_USERadmin - N8N_BASIC_AUTH_PASSWORDadmin_password volumes: - n8n_data:/home/node/.n8n depends_on: - postgres postgres: image: postgres:15 environment: - POSTGRES_DBn8n - POSTGRES_USERn8n - POSTGRES_PASSWORDn8n_password volumes: - pg_data:/var/lib/postgresql/data volumes: n8n_data: pg_data:注意生产环境一定要改掉默认密码并且把 n8n 放在内网通过反向代理暴露 Webhook 接口。n8n 的 Basic Auth 只是最基础的防护企业级部署还需要加 IP 白名单和请求签名校验。Java 侧的 MCP Server我用的依赖是dependency groupIdcom.github.mcp/groupId artifactIdmcp-java-sdk/artifactId version0.8.0/version /dependency这个 SDK 提供了 MCP 协议的基础实现包括工具注册、参数解析、结果序列化。如果没有这个 SDK也可以自己实现核心就是处理 JSON-RPC 格式的请求和响应。4.2 Java MCP Server 的完整实现先定义工具注册表Component public class McpToolRegistry { private final MapString, McpToolDefinition tools new ConcurrentHashMap(); PostConstruct public void init() { // 扫描所有带 McpTool 注解的方法 ApplicationContext ctx SpringContextHolder.getContext(); MapString, Object beans ctx.getBeansWithAnnotation(Component.class); for (Object bean : beans.values()) { for (Method method : bean.getClass().getDeclaredMethods()) { McpTool annotation method.getAnnotation(McpTool.class); if (annotation ! null) { registerTool(bean, method, annotation); } } } } private void registerTool(Object bean, Method method, McpTool annotation) { McpToolDefinition def new McpToolDefinition(); def.setName(annotation.name()); def.setDescription(annotation.description()); def.setParameters(buildParamSchema(method)); def.setHandler((params) - { try { Object[] args bindParams(method, params); return method.invoke(bean, args); } catch (Exception e) { throw new McpToolException(Tool execution failed: e.getMessage()); } }); tools.put(annotation.name(), def); } }然后是 MCP 协议的 HTTP 端点RestController RequestMapping(/mcp) public class McpController { Autowired private McpToolRegistry registry; PostMapping(/list-tools) public McpResponse listTools() { ListMcpToolInfo toolInfos registry.getAllTools().stream() .map(def - new McpToolInfo(def.getName(), def.getDescription(), def.getParameters())) .collect(Collectors.toList()); return McpResponse.success(toolInfos); } PostMapping(/call-tool) public McpResponse callTool(RequestBody McpToolCallRequest request) { McpToolDefinition def registry.getTool(request.getToolName()); if (def null) { return McpResponse.error(Tool not found: request.getToolName()); } try { Object result def.getHandler().apply(request.getParams()); return McpResponse.success(result); } catch (McpToolException e) { return McpResponse.error(e.getMessage()); } } }工具的具体实现Service public class MemberToolService { Autowired private MemberRepository memberRepository; McpTool(name queryMemberStatus, description 根据用户ID查询会员状态返回会员等级、到期时间、是否有效) public MemberStatus queryMemberStatus( McpParam(name userId, description 用户ID, required true) String userId) { Member member memberRepository.findByUserId(userId); if (member null) { return MemberStatus.notFound(userId); } return MemberStatus.builder() .userId(userId) .level(member.getLevel()) .expireTime(member.getExpireTime()) .valid(member.getExpireTime().isAfter(LocalDateTime.now())) .build(); } }4.3 n8n 工作流的搭建步骤第一步创建 Webhook 节点。设置 HTTP 方法为 POST路径为/webhook/ticket。这个节点会接收 Java 后端发来的工单数据。第二步添加 Switch 节点做关键词路由。配置三个输出分支分支一content包含订单或物流或退款分支二content包含登录或密码或验证码分支三其他默认分支第三步分支一和分支二直接连接 HTTP Request 节点调用 Java 的对应接口。分支三连接 MCP 节点。第四步MCP 节点配置。填入 Java MCP Server 的地址比如http://java-backend:8080/mcp。节点会自动调用/list-tools拉取工具列表。第五步Agent 节点配置。选择 LLM 模型把 MCP 节点拉取的工具列表注入到 System Prompt 里。设置max_tokens为 256温度设为 0.1越低越确定。第六步Code 节点做格式校验。把 Agent 节点的输出解析成 JSON校验工具名和参数格式。第七步根据校验结果分流。校验通过则执行 MCP 工具调用校验失败则走降级分支创建人工工单。第八步HTTP Request 节点把最终结果写回 Java 后端。整个工作流大概有 15 个节点连线逻辑清晰调试的时候每个节点的输入输出都能在 n8n 的界面上看到。4.4 参数计算与阈值选择有几个关键参数需要根据实际情况调整。温度参数Agent 节点的温度我设的是 0.1。温度越低模型输出越确定。但也不能设成 0因为有些模型在温度为 0 时会出现重复输出的问题。0.1 是一个比较稳妥的值。最大输出长度256 Token。工具调用请求本身很短256 足够。如果设成 1024模型可能会在输出工具调用请求之前先写一段解释文字浪费 Token。重试次数MCP 工具调用失败重试 2 次Agent 节点调用失败重试 1 次。重试次数不能太多否则 Token 消耗会失控。超时时间MCP 工具调用超时设 5 秒Agent 节点超时设 30 秒。超时后直接走降级分支。关键词路由的覆盖率我统计了一周的工单数据关键词路由能覆盖 68% 的工单。剩下 32% 走 LLM。这个比例直接决定了 Token 消耗的降幅。如果关键词覆盖率达到 80%Token 降幅会更大。5. 常见问题与排查技巧实录5.1 工具调用返回 403 或超时怎么办这是最常见的问题。n8n 调用 Java MCP Server 时如果返回 403通常是鉴权配置有问题。我的 MCP Server 加了一个简单的 API Key 校验n8n 的 HTTP Request 节点需要在 Header 里带上这个 Key。如果返回超时先检查网络连通性。n8n 和 Java 服务如果在不同的 Docker 网络里需要用容器名互相访问而不是localhost。我踩过这个坑n8n 容器里写localhost:8080永远连不上必须写 Java 容器的服务名。还有一个隐蔽的问题是Token 失效。如果 MCP Server 的鉴权 Token 有有效期n8n 的凭证需要定期刷新。我的做法是让 Java 侧提供一个/mcp/refresh-token接口n8n 在工作流开始时先调用这个接口刷新 Token。5.2 模型不按格式输出 JSON这个问题我遇到过很多次。模型有时候会输出 Markdown 代码块包裹的 JSON有时候会在 JSON 前后加解释文字。解决办法有两个一是在 System Prompt 里明确要求“只输出 JSON不要用代码块包裹不要加任何解释”。二是在 Code 节点里做容错解析先用正则提取 JSON 部分再解析。function extractJson(text) { // 去掉 Markdown 代码块标记 let cleaned text.replace(/json\n?/g, ).replace(/\n?/g, ); // 尝试找到第一个 { 和最后一个 } const start cleaned.indexOf({); const end cleaned.lastIndexOf(}); if (start -1 || end -1) { throw new Error(No JSON found); } return JSON.parse(cleaned.substring(start, end 1)); }避坑技巧不要指望模型 100% 按格式输出。永远要在代码侧做容错。我现在的原则是模型输出格式错误是常态格式正确是惊喜。5.3 Token 消耗突然飙升怎么排查Token 飙升通常有三个原因。第一是提示词膨胀。检查 Agent 节点的 System Prompt 是不是不小心把工具列表硬编码进去了或者业务规则又加回来了。我的做法是每周 review 一次提示词确保没有冗余内容。第二是重试次数过多。如果 MCP 工具频繁失败n8n 会不断重试每次重试都会重新调用 LLM。检查 MCP 工具的失败率如果超过 10%需要先解决工具本身的稳定性问题。第三是路由失效。如果关键词路由的规则被误改大量工单会走到 LLM 分支。检查 Switch 节点的条件配置确保路由逻辑没有被意外修改。我建了一个简单的监控看板每天统计 Token 消耗、LLM 调用次数、工具调用成功率。一旦发现异常当天就能定位。5.4 常见问题速查表问题现象可能原因排查方法解决方案MCP 工具返回 403API Key 错误或过期检查 n8n 凭证配置刷新 Token重新配置凭证工具调用超时网络不通或服务未启动在 n8n 容器内 curl 测试检查 Docker 网络用容器名访问模型输出非 JSON提示词约束不够查看 Agent 节点原始输出加强提示词约束Code 节点容错Token 消耗飙升提示词膨胀或重试过多检查 System Prompt 和重试日志精简提示词限制重试次数工单分类错误关键词路由规则不准统计路由命中率和准确率调整关键词增加路由分支重复创建工单工具未做幂等检查 requestId 去重逻辑在 Java 侧加 requestId 去重5.5 独家避坑经验第一个经验不要试图让模型做数学计算。我一开始让模型根据订单金额和折扣规则计算退款金额结果它算错了。后来改成模型只提取订单号退款金额由 Java 侧计算。模型做语义理解是强项做数值计算是弱项别搞反了。第二个经验工具描述要写得像给新人看的文档。MCP 工具的 description 字段模型会读。如果描述写得太简略模型可能不知道怎么用。比如queryMemberStatus的描述我写的是“根据用户ID查询会员状态返回会员等级、到期时间、是否有效。如果用户不存在返回 notFound 状态”。这样模型就知道什么时候该调用这个工具以及怎么处理返回结果。第三个经验降级策略比优化策略更重要。不要指望工作流 100% 成功。我设计了三层降级LLM 调用失败降级到关键词路由工具调用失败降级到人工工单格式校验失败降级到人工工单。每一层降级都有明确的触发条件和处理逻辑。这样即使某个环节出问题整体系统也不会崩溃。第四个经验定期回放历史工单。我每周会把过去一周的工单重新跑一遍工作流对比实际处理结果和预期结果。这样能发现路由规则的偏差和模型输出的漂移。模型是会“漂移”的今天表现好不代表明天表现好定期回放是保持稳定性的必要手段。5.6 性能与并发处理Java 后端扛并发是强项但 n8n 的并发能力有限。n8n 默认是单进程处理工作流如果并发请求太多会排队。我的做法是在 Java 侧加一个队列把工单请求先入队然后由固定数量的消费者线程从队列里取任务调用 n8n 的 Webhook。这样 n8n 的并发压力就可控了。队列我用的是 Redis List消费者线程数设为 4。实测下来4 个消费者能支撑每秒 20 个工单的处理速度足够应对日常流量。如果流量再大可以水平扩展 n8n 实例但要注意工作流的状态同步问题。还有一个优化点是缓存工具列表。MCP 的/list-tools接口不需要每次调用都请求可以在 n8n 启动时拉取一次缓存起来。工具列表变化频率很低没必要每次都拉。6. 这套方案还能怎么扩展6.1 接入更多 MCP 工具目前我只接了会员、订单、工单三个工具。实际上任何 Java 服务都可以封装成 MCP 工具。比如把风控服务封装成checkRiskLevel(userId)把推荐服务封装成getRecommendations(userId)。工具越多模型能做的事情越多但要注意工具列表不能太长否则提示词又会膨胀。我的经验是控制在 10 个工具以内超过 10 个就要考虑分组或者按场景动态加载。6.2 多 Agent 协作单个 Agent 处理复杂任务时容易顾此失彼。可以拆成多个 Agent每个 Agent 负责一个子任务。比如一个 Agent 负责意图识别一个 Agent 负责实体提取一个 Agent 负责回复生成。n8n 支持多个 Agent 节点串联每个节点的输出作为下一个节点的输入。这样每个 Agent 的提示词都可以很精简Token 消耗反而更低。6.3 工作流版本管理n8n 的工作流是存在数据库里的修改后直接生效。这在生产环境有风险。我的做法是把工作流导出成 JSON 文件纳入 Git 管理。每次修改都走代码评审评审通过后再导入 n8n。这样出问题了可以快速回滚到上一个版本。导出工作流的命令n8n export:workflow --idyour-workflow-id --output./workflows/ticket-classifier.json导入的命令n8n import:workflow --input./workflows/ticket-classifier.json6.4 监控与告警我在 Java 侧加了一个简单的监控端点统计每天的 Token 消耗、LLM 调用次数、工具调用成功率、降级次数。这些指标推送到 Prometheus用 Grafana 做看板。一旦 Token 消耗超过阈值或者降级次数突增就触发告警。告警规则我设了三条单日 Token 消耗超过 100 万告警工具调用成功率低于 90%告警降级次数超过总请求量的 5%告警这三条规则帮我提前发现了好几次问题。有一次是 MCP Server 的数据库连接池满了工具调用成功率骤降告警及时触发避免了更大范围的影响。6.5 成本核算与优化方向最后算一笔账。按目前的方案每处理一个工单平均消耗 640 Token。假设 Token 单价是每百万 Token 10 元那么每个工单的 LLM 成本是 0.0064 元。一天处理 10000 个工单成本是 64 元。一个月下来不到 2000 元。如果不用 n8n MCP纯提示词方案每个工单 3200 Token一天成本是 320 元一个月接近 1 万元。省下来的钱足够覆盖 n8n 的服务器成本和开发维护成本。当然成本只是其中一个维度。更重要的是稳定性。纯提示词方案的分类准确率大概在 75% 左右而且波动很大。改成工作流方案后准确率稳定在 92% 以上因为大部分工单走的是确定性路由只有少数模糊工单才依赖模型判断。这个方案后续还可以继续优化。比如把关键词路由升级成轻量级分类模型进一步提高路由覆盖率。或者把 MCP 工具做成本地缓存减少网络往返。再或者引入工作流级别的熔断机制当某个工具连续失败时自动跳过避免雪崩。我在实际使用中发现这套方案最大的价值不是省了多少钱而是让 Java 后端团队能够用自己熟悉的方式去驾驭 AI 能力。你不需要成为提示词工程师也不需要理解 Transformer 的注意力机制你只需要把业务逻辑封装成工具把流程画成工作流剩下的交给 n8n 和模型去协作。这种分工方式让 AI 能力真正融入了现有的后端架构而不是另起炉灶搞一套不可维护的“AI 中台”。