LangChain4j实战:从结构化输出到Function Calling
做一个让大模型输出稳定JSON的Service通常需要三步第一步定义数据模型。不管最终要的是对象还是列表都要先把结构定义清楚。结合工单系统的实际场景我定义了TicketAnalysis类里面包含category、urgency、summary、suggestedAction几个字段分别对应分类结果、紧急程度、问题摘要和处理建议。字段类型尽量用String和枚举别用太复杂的嵌套结构模型输出也简单解析也不容易出错。public record TicketAnalysis(String category, String urgency, String summary, String suggestedAction) { }第二步把方法声明定义在接口里。这里的重点是方法名、参数名要起得足够语义化参数上的注解是给模型看的不是给编译器看的要把业务约束写清楚。interface TicketAnalyzer { SystemMessage(你是一个IT工单分类助手请根据工单内容输出结构化分析结果。) TicketAnalysis analyze(UserMessage(工单标题{{title}}\n工单描述{{description}}) String text); }第三步通过AiServices把这个接口绑定到模型上生成实现类调用方法拿结果。ChatLanguageModel model OpenAiChatModel.builder() .apiKey(sk-xxx) .modelName(gpt-4o-mini) .build(); TicketAnalyzer analyzer AiServices.builder(TicketAnalyzer.class) .chatLanguageModel(model) .build(); TicketAnalysis analysis analyzer.analyze(无法登录系统提示密码错误); System.out.println(analysis.category()); // 账号问题 System.out.println(analysis.urgency()); // 高实测下来这个方案比手动拼JSON Schema稳定得多。模型输出格式不对的时候框架会自动重试修正不需要开发者处理。SystemMessage和UserMessage里的占位符会自动做模板替换不用手动塞值。6.2 从JSON走向行动Function Calling主动调用业务接口结构化输出解决了模型说了什么的问题但很多场景还要求模型做些什么。比如工单系统里模型判断出这是一个账号锁定问题就应该自动调用解锁接口而不是只输出一段建议文本等人工操作。LangChain4j把Function Calling封装成了Tool注解开发者只需要把业务方法注册给模型即可。public class TicketTools { Tool(根据工单ID查询用户账号状态) public String checkAccountStatus(ToolParam(工单ID) Long ticketId) { return accountService.getStatus(ticketId); } Tool(解锁指定用户账号仅限安全风控审核通过后调用) public String unlockAccount(ToolParam(工单ID) Long ticketId) { return accountService.unlock(ticketId); } }注册方式很简单AiServices里加一个tools配置。模型在对话过程中会自己判断什么时候需要调用工具、应该传什么参数。比如用户说帮我看看账号为什么被锁模型会先调用checkAccountStatus拿到状态结果后再根据情况决定是否调用unlockAccount。整个过程是模型自主决策的开发者只需要把工具能力和约束描述清楚。这里有个关键点Tool里的描述文字直接决定了模型会不会在正确的时机调用这个工具描述写得越明确误用率越低。比如仅限安全风控审核通过后调用这种限制条件一定要写在描述里否则模型可能一遇到账号问题就直接解锁风控逻辑就被绕过了。6.3 场景串联把零散能力组合成完整业务流结构化输出和Function Calling从来不是孤立使用的。真正落地的场景要求它们协同工作先是模型理解输入再是工具查询信息最后是结构化输出结果。还以工单自动分类为例完整链路是这样的用户提交工单原始文本传给TicketAnalyzer。模型分析后调用checkAccountStatus获取账号状态信息。模型综合工单内容和账号状态通过analyze方法输出结构化结果。系统根据category和urgency字段自动路由到对应处理队列。如果判定为高优账号问题系统自动触发unlockAccount工具。把这个流程展开为代码配置TicketAnalyzer analyzer AiServices.builder(TicketAnalyzer.class) .chatLanguageModel(model) .tools(new TicketTools()) .build();核心还是在AiServices。这一个类同时承载了结构化输出、工具调用、RAG检索、多轮记忆等能力而且它们之间天然协作。工单系统上线后分类准确率稳定在百分之九十以上人工处理量大幅下降。LangChain4j在Java后端落地的价值正是通过这种组合能力体现出来的。7. 避坑指南新手最容易踩的十个坑7.1 版本、依赖与构建层面的坑先看应用构建问题这是新手最常遇到的第一道坎。第一个坑是版本兼容性。LangChain4j的依赖是按模块拆分的langchain4j核心包、langchain4j-open-ai或者你用的其他模型供应商包、以及langchain4j相关的扩展包彼此之间有版本对应关系。新手经常只引入核心包结果找不到OpenAiChatModel类。正确做法是引入langchain4j-bom统一管控版本然后按需引入具体模块。建议父工程或BOM里显式声明langchain4j.version属性所有模块都引用该属性避免版本混乱。第二个坑是JDK版本。LangChain4j某些内部实现依赖Java 17以上的特性如果项目还停留在JDK 8大概率会报各种奇怪的错误。实测下来JDK 17最稳妥JDK 21也行但有些老版本依赖可能没跟上。第三个坑是API Base URL配置错误。这个问题在接入自建模型网关或本地模型时特别常见网关地址少写一个斜杠、路径不对都会导致连接失败或401。排查时先确认地址能通再确认模型名完全一致。7.2 运行时与模型调用层面的坑构建问题过了之后运行时才是真正消耗精力的大头。第四个坑是上下文爆炸。前面的任务示例中如果每轮对话都把所有历史消息塞给模型Token很容易冲上几万费用和延迟同时飙升。解决办法是设置maxMessages或自定义ChatMemory控制历史长度。第五个坑是超时配置。默认超时时间偏短大模型响应稍慢就会触发超时异常。建议根据业务场景调整OpenAiChatModel.builder() .apiKey(sk-xxx) .modelName(gpt-4o-mini) .timeout(Duration.ofSeconds(60)) .maxRetries(2) .build();第六个坑是流式输出被网关截断。后端通过SSE向浏览器推送流式响应时如果中间有Nginx或其他网关必须关闭代理缓冲否则前端看到的是一段段卡顿的数据甚至连接被中断。第七个坑是异步线程池没有隔离。LangChain4j的StreamingChatLanguageModel是异步回调如果多个业务共用线程池某个慢模型会影响其他业务。建议按业务维度拆分线程池并设置合理的拒绝策略。第八个坑是依赖冲突。项目同时引用了不同版本的OKHttp、Jackson等基础库时容易在运行时出现奇怪的方法签名错误。排查思路是先看mvn dependency:tree确认有没有重复依赖再决定排除哪一方的传递依赖。提示遇到NoClassDefFoundError或NoSuchMethodError优先怀疑依赖冲突而不是代码逻辑问题。7.3 内容质量与业务效果层面的坑最后这部分问题和模型能力相关但根子通常在应用设计上。第九个坑是Prompt约束力不足。给模型的指令太模糊输出就漂浮不定。比如请分析工单和请根据工单标题和描述从故障类型、紧急程度、处理建议三个维度输出JSONJSON键名必须为category、urgency、suggestion效果天差地别。Prompt写得不清晰再好的模型框架也救不了。第十个坑是盲目追求大模型而忽视小模型。实测在结构化抽取这类任务上小模型的稳定性和性价比往往更好。同一个分类任务用更大参数的模型反而容易输出过度发散。模型选型要结合任务复杂度不能只盯着参数规模。8. 从入门到落地我的心得体会LangChain4j真正说服我的是它让LLM应用开发回归到了Java工程师熟悉的轨道上。它没有发明一套全新的世界观而是把AI能力包装成了普通Java库的形态有接口、有注解、有工具类、有配置项一切都那么亲切。Java开发者不需要先学Python不需要重构技术栈不需要改变工程习惯就能自然地把大模型接入到现有业务系统里。我个人在实际操作中的体会是刚开始接触LangChain4j时最容易产生生态不成熟的印象因为社区规模和周边生态确实和Python版本有差距。但实际做完两三个项目后会意识到恰恰是这种克制让它更适合Java后端。RAG检索、上下文管理、函数调用这些核心抽象已经足以支撑绝大多数真实业务场景。与其等待生态完善不如先把手头的工单助手、知识库问答、报表生成器做起来在真实需求中理解框架的设计哲学。最后还想分享一个小技巧在本地调试LangChain4j应用时建议把logback日志级别调到DEBUG重点观察每次请求实际发送的Prompt内容和模型响应结构。很多看似玄学的问题看完日志就一目了然了。如果后续想在这个方向继续深入可以沿着三条线扩展第一把ChatMemory从内存实现换成Redis或数据库支撑多实例部署第二把RAG链路中的Embedding模型换成私有化部署实现数据不出域第三用AiServices封装更多业务Agent让模型和工具的组合形成自动化工作流。每一条线踩下去都会有新的收获。