
LangChain4j 第一个对话程序手写 LLM 调用链路看清模型怎么开口作者鱼宵 LangChain4j实战精通营 · 第 1 篇上个月有个学员跑通 Spring AI 第一个 AI 接口后懵了整个工程没出现一个模型类配好 yml 就聊上天了。他反问我那模型到底怎么被调起来的我没急着解释直接把另一个工程丢给他——没有 yml模型是我手写Beannew 出来的每个参数都摆在你眼前。他跑了一遍说这下看清模型怎么开口了。这个工程就是 LangChain4j。和 Spring AI配好 yml 拎包入住不同这门课第一课就把模型对象亲手new出来HTTP 请求怎么发、Key 怎么管、温度和 maxTokens 是干嘛的一个都藏不住。代码全在仓库lesson-01/clone 下来重跑一遍十分钟后你也有一个能对话的控制台程序——最后那两道挑战题不亲手改一次你真以为调模型有多玄乎。一、核心原理一次 LLM 调用背后发生了什么一句话先撂这儿你写的那行chatModel.chat(你好)背后是框架帮你拼了一个 HTTP POST 请求模型在远端推理完再把 JSON 答案解析成 Java 字符串递回给你。1. 框架帮你干了哪段脏活不写框架直接调模型你得自己干这些事拼 JSON 请求体、管 HTTP 连接、处理各家厂商的接口差异、把返回 JSON 里choices[0].message.content那串字抠出来。就像自己做饭全流程包圆用框架就是点菜——你只管下单写业务后厨脏活归它。一次调用的链路是这样你的 Java 代码 chatModel.chat(你好) ↓ 框架把消息打包成 JSON HTTP POST https://api.deepseek.com/chat/completions ↓ 模型在远端推理这里花 token、花钱、花时间 HTTP 响应 JSON里面装着回答文本 ↓ 框架解析 你的 Java 代码拿到 String 回答面试能把这条链路讲顺就等于讲清了框架到底帮我封装了什么。2. 为什么 DeepSeek 能直接用 OpenAI 的客户端这是这门课第一个反直觉的点我代码里写的是OpenAiChatModel连的却是 DeepSeek。原因是协议兼容——DeepSeek 对外的接口路径、请求体、响应体格式跟 OpenAI 一模一样。所以 OpenAI 的客户端换个baseUrl就能连它。打个比方OpenAiChatModel像万能充电线插座规格是 OpenAI 协议插谁家都行你只要换充电头三件套——base-url服务地址 api-key密钥 model模型名。这也是为什么这门课全程只教你连 DeepSeek切模型却只改三个环境变量。3. 手写 Bean vs yml 自动装配两门课的对照点Spring AI 那门课第 1 篇是往 classpath 丢 starteryml 写spring.ai.openai.*框架自动把ChatClient创建好。这门课反过来——模型对象是我在LlmConfig里手写Bean造出来的Spring AIyml 派LangChain4j手写 Bean 派模型在哪声明yml 里spring.ai.openai.*Java 代码OpenAiChatModel.builder()...build()参数看得见吗藏在配置绑定里一行一行摆在 builder 上换模型改 yml 重启改环境变量 / builder 那几行适合纯 Spring 栈、想省心想看清每个参数、多模型灵活切说白了yml 自动装配是精装房拎包入住手写 Bean 是自己挑家具摆一遍。教学阶段先摆一遍以后住进去才不心慌。4. SystemPrompt给模型发一本入职手册这门课第二个重点概念是SystemPrompt——发给模型的第一段人设说明书比用户问题优先级还高。新员工上岗先读手册就知道自己是谁模型也一样没手册是通用助手给了手册就入戏。玩法二就是让它演 Java 面试官第四节有实测。二、动手十分钟跑通第一个对话程序环境Windows JDK 17 Maven 3.9。会SpringBootApplication、看得懂Bean就行。第 1 步30 秒检查环境。java-version# 期望 TrueJDK17 在不在[bool][Environment]::GetEnvironmentVariable(DEEPSEEK_API_KEY)# 期望 TrueKey 配了没只看存在性别打印值$env:JAVA_HOMEC:\Program Files\Java\jdk-17# 本机用户级 JAVA_HOME 指向 JDK8必须覆盖第 2 步编译工程。cd langchain4j-journey\lesson-01$env:JAVA_HOMEC:\Program Files\Java\jdk-17mvn clean install-DskipTests看到BUILD SUCCESS即通过。第一次会下载 LangChain4j 1.21.0 的依赖耐心等。第 3 步跑玩法一交互问答。java-jar target\lesson-01-1.0.0.jar看到你问就敲问题回车最多问 3 轮输exit提前退。第 4 步跑玩法二角色扮演Java 面试官。java-jar target\lesson-01-1.0.0.jar roleplay注意区别只在启动参数roleplay代码一个字没改。三、关键代码两个类看清模型怎么被造出来工程是纯控制台 Spring Boot 项目没加 web 依赖控制台课不需要 HTTP 接口真正要看的就两个类。第一段LlmConfig.java——模型是怎么被手写出来的。packagecom.langchain4j.lesson01;importdev.langchain4j.model.openai.OpenAiChatModel;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;/** * 模型配置类全课程统一套路——用 Bean 手动把模型对象 new 出来。 * 和 Spring AI 的 yml 自动装配正好相反这里每个参数都看得见。 * 安全红线API Key 只从环境变量读绝不写进代码 / yml / README / 日志。 */ConfigurationpublicclassLlmConfig{BeanpublicOpenAiChatModelopenAiChatModel(){returnOpenAiChatModel.builder()// 服务地址默认 DeepSeek环境变量 LLM_BASE_URL 可覆盖换 OpenAI/通义.baseUrl(System.getenv().getOrDefault(LLM_BASE_URL,https://api.deepseek.com))// API Key只从环境变量读写死 Key 把钱包密码贴墙上.apiKey(System.getenv(DEEPSEEK_API_KEY))// 模型名默认 deepseek-chat环境变量 LLM_MODEL 可覆盖.modelName(System.getenv().getOrDefault(LLM_MODEL,deepseek-chat))// 输出上限 200 token教学演示控成本长回答会被拦腰截断实测里能看到.maxTokens(200)// 温度 0.70严谨死板1天马行空聊天用 0.7 比较自然.temperature(0.7).build();}}这段就是手写派的精髓builder 上每行都是一个你能叫出名字的参数。getOrDefault是故意的——环境变量优先、默认值兜底这就是切模型只改配置不改代码。第二段ChatDemoApplication.java——一次 LLM 调用到底长什么样。packagecom.langchain4j.lesson01;importdev.langchain4j.model.chat.ChatModel;importorg.springframework.boot.CommandLineRunner;importorg.springframework.boot.SpringApplication;importorg.springframework.boot.autoconfigure.SpringBootApplication;importjava.util.Scanner;/** * 第 1 课主程序。两种玩法用启动参数区分 * 无参 / chat —— 交互问答控制台敲问题 * roleplay —— 角色扮演Java 面试官演示 SystemPrompt */SpringBootApplicationpublicclassChatDemoApplicationimplementsCommandLineRunner{// 注入的是接口 ChatModel1.21.0 起由旧名 ChatLanguageModel 改名// 实际对象是 OpenAiChatModel——面向接口以后换实现业务代码一行不动。privatefinalChatModelchatModel;publicChatDemoApplication(ChatModelchatModel){this.chatModelchatModel;}publicstaticvoidmain(String[]args){SpringApplication.run(ChatDemoApplication.class,args);}Overridepublicvoidrun(String...args){Stringmodeargs.length0?args[0]:chat;if(roleplay.equalsIgnoreCase(mode)){rolePlayDemo();}else{chatDemo();}}/** 玩法一交互问答最多 3 轮输 exit 退出。 */privatevoidchatDemo(){System.out.println( LangChain4j 第 1 课交互问答 );System.out.println(输入你的问题回车即可输入 exit 退出最多问 3 轮);ScannerscannernewScanner(System.in);intround0;while(round3){System.out.print(你问);System.out.flush();// 不 flush 的话输入前看不到提示语if(!scanner.hasNextLine())break;// 管道关闭就退出保证程序能自然结束Stringquestionscanner.nextLine().trim();if(question.isEmpty()||exit.equalsIgnoreCase(question))break;round;System.out.println(question);// ★★ 全课程最关键的一行问进去答出来背后是一次完整的 HTTP 调用StringanswerchatModel.chat(question);System.out.println(AI 答answer);System.out.println(---------- 第 round 轮结束 ----------);}System.out.println( 对话结束 );}/** 玩法二角色扮演。SystemPrompt 就是这里拼进去的。 */privatevoidrolePlayDemo(){System.out.println( LangChain4j 第 1 课角色扮演Java 面试官 );// 人设说明书先于用户问题发给模型优先级最高StringsystemPrompt你是资深 Java 技术面试官语气专业但友好。每次回答先用一句话点评候选人的回答再追问一个更深入的问题。;String[]questions{请问什么是 Java 里的 final 关键字,HashMap 的底层数据结构是什么};for(inti0;iquestions.length;i){System.out.println(面试官问questions[i]);// 把人设 问题拼在一段发给模型第 2 课会用正规的消息体系替代这种手拼StringanswerchatModel.chat(systemPrompt\n问题questions[i]);System.out.println(AI 答answer);System.out.println(---------- 第 (i1) 题结束 ----------);}System.out.println( 面试演练结束 );}}盯着chatModel.chat(question)这一行看三秒——你以后写的所有 AI 业务底层都是它。框架在这行里把拼请求、发 HTTP、解析 JSON 全包了。四、实测输出同一份代码加一段人设判若两人以下是 2026-10-05 本机真实运行DeepSeek 实测已省略 Spring Boot 启动 banner。玩法一交互问答。 LangChain4j 第 1 课交互问答 输入你的问题回车即可输入 exit 退出最多问 3 轮 你问用一句话介绍你自己 AI 答我是DeepSeek由深度求索公司创造的AI助手随时准备为你提供帮助 ---------- 第 1 轮结束 ---------- 你问今天郑州天气适合做什么 AI 答我无法直接查询今天的实时天气所以没法告诉你郑州今天具体适不适合出门。不过你可以这样快速判断 **先看天气再决定做什么** | 天气情况 | 适合做的事 | |---------|-----------| | ☀️ 晴/多云温度适宜 | 去公园人民公园、碧沙岗、黄河风景区、逛街、骑行 | | ️ 下雨 | 博物馆河南博物院、商场、咖啡馆、看电影、在家休息 | | ️ 雾霾/空气差 | 室内活动为主减少户外运动 | | ️ 高温35℃ | 室内场馆、傍晚再出门、注意防暑 | | 寒冷/大风 | 室内活动出门注意保暖 | **建议你打开手机天气App看一眼** - 温度多少 - 有没有雨/霾 - 风力大不大 **郑州比较值得去的地方按天气选 ---------- 第 2 轮结束 ---------- 对话结束 看出两个门道没第二问末尾那句加粗的郑州比较值得去的地方按天气选下面没下文了——这不是 bug是.maxTokens(200)生效输出到 200 token 被拦腰截断。生产环境按业务调大就行。模型张口就说我无法直接查询今天的实时天气——它不联网、不知道今天天气第 6 课给它装工具Function Calling才能查真实数据。玩法二同一个模型只多了一段 systemPrompt。 LangChain4j 第 1 课角色扮演Java 面试官 面试官问请问什么是 Java 里的 final 关键字 AI 答你的回答可以从 final 修饰变量、方法、类这三个基本用法展开这是很扎实的切入点。 那我追问一下当 final 修饰一个引用类型变量时比如 final ListString list new ArrayList();为什么我们仍然可以往 list 里 add 元素这跟 final 的语义到底是怎么对应的 ---------- 第 1 题结束 ---------- 面试官问HashMap 的底层数据结构是什么 AI 答点评这是很基础的问题答好了能体现你对集合框架的基本功。 追问那你能具体说说JDK 1.8 之后 HashMap 在什么条件下会把链表转成红黑树又会在什么条件下退化回链表吗 ---------- 第 2 题结束 ---------- 面试演练结束 对比玩法一的热情助手口吻这里它先点评一句、再追着问——SystemPrompt 的约束力实测摆在这。同一个模型、同一套代码差别就是chat()前多拼了一段人设字符串。排查提示报 401 是 Key 环境变量没生效402 是 DeepSeek 余额不足连接超时多半是公司代理挡了 api.deepseek.com。五、挑战题改个参数看看会怎样⭐把温度拨到 0 和 1.5LlmConfig里.temperature(0.7)分别改成0.0和1.5重编译同一个问题用 10 个字介绍你自己连跑两次。你会发现 0.0 的回答几乎每次一字不差1.5 的每次说法都不一样。答案就在LlmConfig.java那行 builder 上。⭐把 maxTokens 那行删掉注释掉.maxTokens(200)重跑再问一次郑州天气适合做什么——这回小作文能写完了。体会一下为什么生产环境反而要设上限成本失控。⭐⭐加一道面试题在rolePlayDemo的questions数组里加第三题比如什么是 volatile重跑roleplay看面试官是不是还保持先点评再追问的派头。答案就在ChatDemoApplication.java那个数组里。六、生产环境进阶三个加分项1. Key 只走环境变量这是红线。整个工程 grep 一遍代码、yml、日志里都不该出现真实 Key只能出现System.getenv(DEEPSEEK_API_KEY)。Key 是钱袋子贴进代码提交到 Git等于把钱包照片发群里。2. maxTokens 是天然成本刹车。它是单次输出上限回答超长会被截断。演示用 200 控成本生产按业务调大同时也是防止模型没完没了的闸。3. 切模型三件套代码零改动。OpenAI https://api.openai.com/v1gpt-4o-mini通义 https://dashscope.aliyuncs.com/compatible-mode/v1qwen-plus。三家都兼容 OpenAI 协议改LLM_BASE_URL/LLM_MODEL加对应 Key 就能换供应商。七、面试回答模板面试官一次 LLM 调用背后发生了什么一句话你的代码调chatModel.chat(问题)框架把消息打包成 JSONHTTP POST 到模型服务模型推理完返回 JSON框架再解析成字符串还给你。展开讲拼请求、发 HTTP、解析响应三段全是框架封装的业务代码只管给问题、拿答案。DeepSeek 兼容 OpenAI 协议OpenAI 客户端换个 baseUrl 就能连它。指向本课第一节 lesson-01 的ChatDemoApplication.java追问为什么 DeepSeek 能直接用 OpenAI 的 Java 客户端因为它的接口路径、请求体、响应体格式和 OpenAI 完全一致这叫兼容 OpenAI 协议。换模型只动三处base-url、api-key、model 名业务代码一行不改。指向本课第 2 小节追问LangChain4j 和 Spring AI 在模型装配上有什么区别Spring AI 靠 starter yml 自动装配配好spring.ai.openai.*注入 ChatClient 就能用像精装房拎包入住LangChain4j 手写Beanbuilder 出模型每个参数看得见更轻、厂商覆盖更广。纯 Spring 栈图省心选前者要多模型灵活切选后者。指向本课第 3 小节 Spring AI 系列第 1 篇追问temperature 和 maxTokens 各管什么temperature 管创造性0 严谨固定适合抽取分类、0.7 自然闲聊、1 发散易跑偏maxTokens 管输出上限超了就截断同时是成本刹车。指向本课LlmConfig.java八、总结表坑现象解法JAVA_HOME 指向 JDK8mvn 跑在 Java 8 上报错构建前$env:JAVA_HOMEC:\Program Files\Java\jdk-17控制台中文乱码中文输出变???运行加-Dfile.encodingUTF-8文件 UTF-8 无 BOM401 UnauthorizedKey 环境变量没生效检查$env:DEEPSEEK_API_KEY设完重启终端旧教程找不到类编译报 cannot find symbol ChatLanguageModel1.21.0 已改名ChatModel方法是chat()回答突然半截不是 bug是 maxTokens 截断教学用 200 控成本生产按需调大fat jar 报 no main manifestjava -jar起不来pom 里配start-class别删九、关于这个系列本文是「Java 后端实战精通营」系列第 1 篇原则实战驱动、由浅到深、面试向每篇文章的结论都可以亲手验证。LangChain4j 实战精通营10 课https://gitee.com/j67mk2/langchain4j-journey本文对应源码位置lesson-01/纯控制台工程内含手写模型 Bean 的LlmConfig 交互/角色扮演两个玩法的ChatDemoApplication系列文章一览按发布顺序篇主题1LangChain4j 第一个对话程序手写 LLM 调用链路看清模型怎么开口2LangChain4j 提示词工程模板 few-shot 温度提示词从写死到复用3LangChain4j 结构化输出让模型吐出 Java 对象JSON 不再手写解析4LangChain4j 对话记忆让模型记住聊过什么三种方式实测对比5LangChain4j 流式输出打字机效果 SSE回答不再干等三秒6LangChain4j 工具调用Function Calling 让模型动手查数据多工具连调实测7LangChain4j 向量检索文本怎么变成可搜索的坐标8LangChain4j RAG 流水线手写问答全流程给 AI 开卷考试9LangChain4j Agent多步推理自主调用放手让实习生自己安排工作10LangChain4j 企业智能客服RAG 工具 记忆 流式 兜底验收 5 项全过下一篇预告《LangChain4j 提示词工程模板 few-shot 温度提示词从写死到复用》——本课你还在systemPrompt \n问题 question这种手拼字符串下一课把它升级成带{{占位符}}的正规模板再塞几个示例few-shot让模型照猫画虎。跑完有任何报错把终端输出发评论区一起排查。标签建议LangChain4j、大模型、DeepSeek摘要建议≤256 字调大模型非得配 yml 黑魔法LangChain4j 这门课反着来——手写 Bean 把模型对象造出来base-url / api-key / temperature 每个参数都摊在你眼前。本文带你用最小控制台工程跑通第一个对话程序逐行拆解一次 LLM 调用的 HTTP 链路实测对比 SystemPrompt 怎么让同一个模型从热情助手秒变Java 面试官附 3 道改参数挑战题与面试回答模板源码在 lesson-01 可 clone 直接跑。