你是不是遇到过这种尴尬AI 客服聊得头头是道可一到帮我查下这个商品有没有货就哑火了因为它根本碰不到你的业务数据本文用 LangChain4j 1.19.0 的 Tool Calling工具调用 结构化输出 会话记忆从零搭一个真正能干活的电商 AI 客服——它能自己查库存、算运费、算总价、确认下单并且全程对话上下文不丢。一、这个问题到底是什么先讲清楚我们说的AI 客服到底卡在哪。传统聊天机器人是个话痨只会把问题重复一遍再扔给你一句和稀泥的答复。但企业要的客服是办事员——用户问这个耳机现在多少钱包邮吗它得真的去数据库里查价格、查库存、查运费规则然后给出准确的数字。难点在于大模型LLM天生只会说不会查。它脑子里那点知识是训练时学的根本不知道你系统里今天的实时库存是多少。要让它干活就得给它一只手——这就是 Tool Calling工具调用让模型学会声明自己想调哪个函数你的 Java 程序去真正执行再把结果喂回给模型让它基于真实数据继续回答。但光有 Tool 还不够会踩三个真实坑工具返回结果没人管模型说我要查库存你的代码是查了但查完的结果怎么组织回给模型没有规范的管道上下文就断了。多步调用接不上用户问买 3 个运费多少模型得先查单价、再算小计、再套运费规则——这不是一次调用是连续多轮问工具→拿结果→再问工具的循环。上一句忘了用户刚说完要 3 个 AirPods下一句加上那个手机壳呢如果客服不记得刚才聊了什么就直接垮掉。这篇文章就是来解决这三件事的。读完你能用 LangChain4j 手写一个可运行的 AI 客服原理讲透代码能直接跑。二、底层原理到底怎么回事要理解 LangChain4j 怎么让大模型干活先忘掉AI把它当成一个会下命令的高管。原理一工具调用 高管给下属下指令大模型不是一个能随便调你代码的魔法盒。它工作方式是你提前注册一批 Java 方法作为工具比如checkStock、calcShipping然后模型在回答时如果发现自己需要数据它会输出一段结构化的调用请求——类似调用 checkStock 方法参数是 productIdairpods-pro-3。你的程序收到这个请求后去执行真正的 Java 方法拿到返回值再把返回值作为工具结果塞回给模型的上下文。模型看到真实数据才继续组织自然语言回答。整个过程像高管下指令、下属干活、高管根据结果拍板。用一张表格看这个循环轮次谁在说话内容1用户“AirPods Pro 3 现在什么价买 3 个包邮吗”2大模型输出调用checkStock(airpods-pro-3)3你的程序查数据库返回库存 120单价 18994大模型输出调用calcShipping(1899*3, 上海)5你的程序返回运费 0满 5000 包邮6大模型整理成自然语言“单价 18993 个共 5697已包邮。”这个模型输出调用请求 → 程序执行 → 结果喂回的循环就是 Tool Calling 的核心。原理二AiServices 帮你打工的马仔如果让你手工写上面这套循环你要处理解析模型的调用请求、匹配方法、处理异常、把结果格式化回填、判断要不要继续调用——代码又多又容易错。LangChain4j 的AiServices就是来干这个脏活的。你只要做三件事定义一个接口比如Assistant里面声明一个方法用Tool注解标记那些要让模型调用的 Java 方法用AiServices.builder(Assistant.class).chatLanguageModel(model).tools(myService).build()把它们组装起来。然后你调assistant.chat(买 3 个 AirPods)背后那个模型调用↔程序执行的循环就自动跑完了。原理三结构化输出 让模型闭嘴说人话之前先填表大部分时候模型回答是自由文本可企业系统要的不是大概两千左右而是要能直接入库的精确数字。LangChain4j 1.19 支持让模型按指定结构你定义一个 Java record/class返回结果——模型被要求先把答案填进这张表再说话。这样你既能拿到totalAmount5697这样的结构化字段又能拿到它的自然语言解释。一句话总结原理Tool Calling 解决模型能查数据结构化输出解决结果能进系统会话记忆ChatMemory解决多轮对话不串线。三样合起来AI 客服才从话痨变成办事员。三、实战手把手写代码先定版本基座每次都实测 Maven Central2026-08-23 查得langchain4j核心库1.19.0GAlangchain4j-open-ai1.19.0GASpring Boot3.5.x不强制用 Spring这里用纯 Java 演示依赖最少JDK21说明这里刻意不用langchain4j-spring-boot-starter——它在 Maven 上的最新 GA 还是 0.36.21.x 的 starter 目前是 beta用核心库 open-ai 两个 GA 模块最稳也最贴近 1.x API。示例一让你的模型拥有查库存的手先建一个 Maven 工程pom.xml只加必需依赖?xml version1.0 encodingUTF-8?projectxmlnshttp://maven.apache.org/POM/4.0.0xmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsdmodelVersion4.0.0/modelVersiongroupIdcom.example/groupIdartifactIdai-assistant/artifactIdversion1.0.0/versionpackagingjar/packagingpropertiesmaven.compiler.release21/maven.compiler.releaseproject.build.sourceEncodingUTF-8/project.build.sourceEncodinglangchain4j.version1.19.0/langchain4j.version/propertiesdependencies!-- LangChain4j 核心库GA 1.19.0 --dependencygroupIddev.langchain4j/groupIdartifactIdlangchain4j/artifactIdversion${langchain4j.version}/version/dependency!-- OpenAI 模型接入GA 1.19.0注意 API Key 需要你自己配 --dependencygroupIddev.langchain4j/groupIdartifactIdlangchain4j-open-ai/artifactIdversion${langchain4j.version}/version/dependency/dependencies/project这段代码在干什么声明工程依赖。langchain4j是主库langchain4j-open-ai让你能连 OpenAI 家的模型也可以换国产模型后面说。两个版本号都锁到 1.19.0 GA确保用的是 1.x 新 API。下面写第一个完整可运行的类——一个能查库存的客服。核心是Tool注解它告诉 LangChain4j“这个方法可以给模型调用”。packagecom.example.assistant;importdev.langchain4j.agent.tool.Tool;importdev.langchain4j.memory.chat.MessageWindowChatMemory;importdev.langchain4j.model.openai.OpenAiChatModel;importdev.langchain4j.service.AiServices;importjava.time.Duration;importjava.util.HashMap;importjava.util.Map;/** * 示例一让模型拥有一只查库存的手。 * 跑法配置好 OPENAI_API_KEY 环境变量直接运行 main。 */publicclassStockAssistantDemo{// ------- 业务层伪装成数据库的库存表 -------// 真实项目里这里换成查 MySQL / Redis 的代码privatestaticfinalMapString,IntegerSTOCKnewHashMap();static{STOCK.put(airpods-pro-3,120);STOCK.put(iphone-17,45);STOCK.put(charger-20w,500);}/** * Tool 标注的方法会被注册成模型可调用的工具。 * description 写给模型看告诉它这个方法什么时候该用、参数是什么含义。 * 这段描述直接决定模型会不会正确调用要写得像给同事的交接说明。 */Tool(查询指定商品productId的当前库存数量。用户问有没有货、库存多少时调用。)publicintcheckStock(StringproductId){System.out.println( → [工具已执行] checkStock(productId));returnSTOCK.getOrDefault(productId,0);}// ------- 用户入口接口声明一个方法AiServices 自动补全实现 -------interfaceStockAssistant{Stringchat(StringuserMessage);}publicstaticvoidmain(String[]args){// 1. 配置大模型。换成国产模型时OpenAiChatModel.builder()// 也可以指向兼容 OpenAI 协议的端点见踩坑部分。OpenAiChatModelmodelOpenAiChatModel.builder().apiKey(System.getenv(OPENAI_API_KEY)).modelName(gpt-4o-mini).temperature(0.0)// 开客服建议温度设 0少胡说.timeout(Duration.ofSeconds(30)).build();// 2. 实例化工具服务就是上面带 Tool 方法的那个类的实例StockAssistantDemostockServicenewStockAssistantDemo();// 3. 用 AiServices 组装接口 模型 工具 会话记忆StockAssistantassistantAiServices.builder(StockAssistant.class).chatLanguageModel(model).tools(stockService)// 注册工具.chatMemory(MessageWindowChatMemory.withMaxMessages(20))// 记最近20条保上下文.build();// 4. 连续对话测试System.out.println(用户: AirPods Pro 3 现在有货吗);System.out.println(客服: assistant.chat(AirPods Pro 3 现在有货吗));System.out.println();System.out.println(用户: 那 iPhone 17 呢);System.out.println(客服: assistant.chat(那 iPhone 17 呢));}}这段代码在干什么逐段说Tool(描述...)—— 最关键的一行。没有它模型根本不知道有checkStock这个方法可用。后面的描述文字是给模型看的说明书写清楚什么时候用、参数代表什么。interface StockAssistant { String chat(String userMessage); }—— 你不写实现AiServices.builder(...).build()会自动生成一个实现把model tools memory串起来。.tools(stockService)—— 把带Tool方法的对象注册进去。.chatMemory(MessageWindowChatMemory.withMaxMessages(20))—— 记住最近 20 条消息。这就是为什么用户第二句问那 iPhone 17 呢时模型记得前面聊的是 AirPods。跑起来的效果大致是第一句有货吗触发checkStock(airpods-pro-3)返回 120模型回答有货还剩 120 个第二句那 iPhone 17 呢因为记忆还在模型知道是接着问库存触发checkStock(iphone-17)返回 45。示例二多步调用——让它自己算价格和运费上一个例子只调一次工具。真实客服往往要连续调好几次先查单价再算数量小计再套运费规则。下面的类演示 LangChain4j 会自动完成这个多轮循环你完全不用写循环代码。packagecom.example.assistant;importdev.langchain4j.agent.tool.Tool;importdev.langchain4j.model.openai.OpenAiChatModel;importdev.langchain4j.service.AiServices;importjava.time.Duration;importjava.util.HashMap;importjava.util.Map;/** * 示例二多步工具调用——模型自动连续调用多个方法算出准确价格。 */publicclassPricingAssistantDemo{// 商品单价表元privatestaticfinalMapString,DoublePRICEnewHashMap();static{PRICE.put(airpods-pro-3,1899.0);PRICE.put(iphone-17,5999.0);PRICE.put(charger-20w,149.0);}/** 工具1查单价 */Tool(查询商品单价元。用户问多少钱、什么价时先调用这个。)publicdoublepriceOf(StringproductId){System.out.println( → [工具已执行] priceOf(productId) PRICE.getOrDefault(productId,0.0));returnPRICE.getOrDefault(productId,0.0);}/** 工具2算运费。满 5000 包邮否则按 20 元收。 */Tool(根据商品金额小计subtotal元计算运费元。满 5000 元包邮返回0否则收20元。)publicdoubleshippingOf(doublesubtotal){System.out.println( → [工具已执行] shippingOf(subtotal));returnsubtotal5000?0.0:20.0;}/** 工具3算订单总价小计 运费 */Tool(计算订单总价。传入小计 subtotal 和运费 shipping返回 subtotalshipping。)publicdoubletotalOf(doublesubtotal,doubleshipping){System.out.println( → [工具已执行] totalOf(subtotal, shipping) (subtotalshipping));returnsubtotalshipping;}interfacePricingAssistant{Stringchat(StringuserMessage);}publicstaticvoidmain(String[]args){OpenAiChatModelmodelOpenAiChatModel.builder().apiKey(System.getenv(OPENAI_API_KEY)).modelName(gpt-4o-mini).temperature(0.0).timeout(Duration.ofSeconds(30)).build();PricingAssistantDemoservicenewPricingAssistantDemo();PricingAssistantassistantAiServices.builder(PricingAssistant.class).chatLanguageModel(model).tools(service).build();System.out.println(用户: 买 3 个 iPhone 17运费多少总共多少钱);System.out.println(客服: assistant.chat(买 3 个 iPhone 17运费多少总共多少钱));}}这段代码干了一件漂亮事你只提了需求但后台模型会自动连打三次工具。先priceOf(iphone-17)得 5999自己心算5999 × 3 17997模型会算不用额外工具再shippingOf(17997)得 0满 5000 包邮最后totalOf(17997, 0)得 17997。整个过程 LangChain4j 自动维护调用→回填→再调的循环你一行循环代码都没写——这就是AiServices最值钱的地方。示例三结构化输出——让答案能直接进系统自由文本客服说到大概五千多吧系统没法入库。用结构化输出让模型必须把关键字段填进你定义的record再配一句话解释。这样订单信息能直接序列化进数据库。packagecom.example.assistant;importdev.langchain4j.model.openai.OpenAiChatModel;importdev.langchain4j.service.AiServices;importdev.langchain4j.service.SystemMessage;importjava.time.Duration;/** * 示例三结构化输出——模型既回答自然语言又填好结构化订单字段。 */publicclassStructuredOutputDemo{/** * 定义模型必须返回的结构。 * SystemMessage 用 {xxx} 占位符注入买家姓名。 */interfaceOrderExtractor{SystemMessage(你是电商订单信息提取器。请从用户的对话中提取订单信息填入结构化字段。)OrderInfoextract(StringuserMessage);}// record 就是返回结构。LangChain4j 会让模型按这几个字段填值并配 explain 说明。publicrecordOrderInfo(StringproductName,// 商品名称intquantity,// 数量doubleunitPrice,// 单价doubletotalAmount,// 总价Stringshipping,// 是否包邮是/否Stringexplain// 给用户看的自然语言说明){}publicstaticvoidmain(String[]args){OpenAiChatModelmodelOpenAiChatModel.builder().apiKey(System.getenv(OPENAI_API_KEY)).modelName(gpt-4o-mini).temperature(0.0).timeout(Duration.ofSeconds(30)).build();OrderExtractorextractorAiServices.builder(OrderExtractor.class).chatLanguageModel(model).build();OrderInfoinfoextractor.extract(我要买 3 个 iPhone 17单价是 5999帮我算下总价看看包不包邮。);// 结构化字段可直接入库System.out.println(商品名 : info.productName());System.out.println(数量 : info.quantity());System.out.println(单价 : info.unitPrice());System.out.println(总价 : info.totalAmount());System.out.println(是否包邮 : info.shipping());System.out.println(给用户的话: info.explain());}}这段代码的关键接口方法返回类型是你自己定义的OrderInforecord而不是String。LangChain4j 一看到返回类型是自定义结构就会自动要求模型按这个结构填 JSON再帮你反序列化成 Java 对象。record的每个字段名都会被模型当作要填的栏位所以字段命名要清晰。四、踩坑经验和最佳实践写这类 AI 客服下面这些坑我基本都踩过给你排掉坑 1工具描述写太烂模型压根不调用。Tool的 description 是模型唯一的说明书。写成public int checkStock(String id)这种没描述的模型常常不知道什么时候该用它。最佳实践描述里写清触发场景 每个参数含义比如用户问有没有货时调用productId 是商品编码。描述越像给同事的交接说明调用准确率越高。坑 2模型编造不存在的工具参数或商品。用户说我要最新款耳机但你系统里只有airpods-pro-3。模型可能瞎猜个airpods-pro导致查不到。对策参数校验 容错返回。工具方法里对不认识的 id 返回 0 或抛业务异常并在描述里明确只接受系统存在的商品编码。坑 3真实项目不要直接 new OpenAiChatModel要复用 Bean。示例里每次 new 一个模型对象。真实 Spring 项目里应把它做成Bean单例避免重复建连接。同样AiServices.builder(...).build()生成的 assistant 也应是单例别每请求建一次会话记忆ChatMemory一般按用户维度各建一个。坑 4客服别忘配temperature(0.0)。客服是事实型任务temperature 设 0 能极大减少一本正经胡说八道。做创意文案才调高客服场景默认 0。坑 5会话记忆不要无限长。用MessageWindowChatMemory.withMaxMessages(20)这种窗口式记忆只留最近 N 条。又省 token 又防止上下文被旧内容污染。如果要做长期记忆记住用户上一周的偏好需要配合持久化记忆方案那超出本文范围。坑 6国产/自建模型也能用。上面所有例子接的是 OpenAI但OpenAiChatModel.builder()只要模型兼容 OpenAI 协议DeepSeek、通义千问、Kimi 等都兼容就能通过配置baseUrl指向你自己的端点代码不用改。这是 LangChain4j 接入国产大模型最省事的方式。五、性能对比和技术选型Tool Calling vs 纯 Prompt让模型猜答案维度纯 Prompt 让模型猜Tool Calling 真查数据库存/价格准确性靠模型蒙不可靠查真实数据库准确能不能操作业务下单不能能通过工具写库开发成本低中要多写工具方法适用场景闲聊、通用知识问答企业业务客服、查询、下单结论很明确只要涉及实时数据或业务操作必须用 Tool Calling。纯 Prompt 只适合不承载业务责任的通用问答。记忆方案怎么选记忆方案记住多久成本适用MessageWindowChatMemory最近 N 条消息低单次会话内的上下文持久化记忆存 DB/Redis跨会话、长期中高会员画像、长期偏好对话窗口记忆是够用就好的默认选择只有当你要做跨会话记住老客户时才上持久化。工具方法数量怎么控制工具不是越多越好。工具太多会让模型在选哪个工具上犹豫反而降准。最佳实践把业务方法按职责收拢成少量高内聚的Tool比如库存类订单类各一个服务对象而不是每个细粒度方法暴露成一个工具。六、总结一句话回顾让 AI 客服从话痨变办事员的三板斧是 Tool Calling、结构化输出、会话记忆而 LangChain4j 的 AiServices 把这三样自动串成一个循环。Tool Calling解决模型查不到你的实时数据——用Tool注册 Java 方法模型需要数据时自动声明调用你的代码执行后把结果喂回。结构化输出解决答案没法进系统——接口返回类型定义成 record/class模型按要求填字段直接反序列化成 Java 对象。会话记忆解决多轮对话串线——MessageWindowChatMemory记住最近消息加上那个呢这种指代也能接得上。AiServices是胶水自动维护模型调用 → 程序执行 → 结果回填的循环你只需要定义接口、写工具、一行装配。按本文的示例一、二、三你三块代码分别学会了查库存“多步算价”“填结构化结果”组合起来就是一个能查、能算、能入库的最小电商 AI 客服骨架。把它接到你真实的订单服务、库存表上就能落地到业务里了。记住配置环境变量OPENAI_API_KEYJDK 用 21LangChain4j 锁 1.19.0 GA就能直接动手跑。