1. 项目概述:为什么一个 SpringBoot + LangChain4j 的 AI Agent 不再是“玩具级”工程
你有没有在面试时被问过:“SpringBoot 自动装配原理是什么?”或者“如何在 SpringBoot 中优雅地管理第三方 SDK 的生命周期?”——这些问题背后,考的从来不是你能不能写个@RestController,而是你有没有把框架当成一个可塑、可编排、可治理的工程底座来用。而今天这个标题——“实战演示:SpringBoot 集成 LangChain4j 快速发布 AI Agent”——恰恰就是对这两项能力的终极压力测试。
它表面看是个 AI 功能接入,实则是一次完整的 SpringBoot 工程范式升级:从依赖管理、Bean 生命周期控制、配置驱动、条件化装配,到内存/持久化会话隔离、工具链动态注入、提示词模板化治理,全部落在 Spring 的核心抽象之上。LangChain4j 不是插件,它是 SpringBoot 生态里第一个真正把 LLM 能力“组件化”的 Java 库——它让大模型调用像注入DataSource一样自然,让函数调用(Tools)像@Scheduled一样声明式,让会话记忆像@Cacheable一样可插拔。
我带过 7 个 AI 工程化落地项目,踩过最深的坑不是模型不准,而是把 LangChain4j 当成普通 SDK 直接new实例、硬编码 API Key、手动拼接 Prompt、用静态变量存 ChatMemory。结果上线后:多用户会话串号、OOM 崩溃、日志里全是明文密钥、换模型要改 12 个类、压测 QPS 卡在 80 上不去。后来我们彻底重构,把所有 LangChain4j 组件都纳入 Spring 容器统一调度,仅配置文件改动就支撑了 OpenAI / DeepSeek / Ollama / 百炼 四套模型热切换,会话并发从 50 提升到 3200+,密钥零泄露,上线后三个月没动过一行核心逻辑代码。
所以这篇博文不讲“LangChain4j 是什么”,也不堆砌 API 列表。我要带你从一个资深 SpringBoot 架构师的视角,拆解这个项目里每一个看似简单的配置项背后的设计权衡:为什么langchain4j-open-ai-spring-boot-starter比裸用langchain4j-open-ai多出 3 层抽象?为什么MessageWindowChatMemory的maxMessages=10在高并发下必须配合InMemoryChatMemoryStore才能不丢消息?为什么@Tool方法参数上加@P(required = true)会直接影响 LLM 的 function calling 准确率?这些答案,藏在 Spring 的BeanFactoryPostProcessor、ConfigurationPropertiesBindingPostProcessor、AopProxyFactory的调用栈深处,也藏在我凌晨三点盯着线程 dump 分析ChatMemoryProvider泄漏的实战记录里。
如果你正面临这些场景:
✅ 想把 AI 能力快速集成进现有 SpringBoot 系统,但怕破坏原有架构;
✅ 被面试官追问“SpringBoot 如何管理非 Spring 原生 SDK”;
✅ 需要支持客户私有化部署(Ollama)、国产模型(百炼)、合规云服务(DeepSeek)三套环境;
✅ 或者只是想搞懂:为什么别人用 20 行代码就能发布 AI Agent,而你写了 200 行还在修内存泄漏?
那么接下来的内容,就是你过去三年技术成长中缺失的那一块拼图。
2. 核心设计思路:LangChain4j 在 SpringBoot 中的“四层封装”模型
LangChain4j 官方文档里那句 “LangChain for Java” 听起来很美,但真实世界里,Java 工程师面对的从来不是“一个库”,而是“一个需要嵌入现有体系的异构系统”。直接new OpenAiChatModel()的写法,在单元测试里跑得飞起,一上生产就暴雷——因为 SpringBoot 的灵魂不在@SpringBootApplication,而在ApplicationContext对 Bean 全生命周期的掌控力。LangChain4j 的 Spring Boot Starter 正是为解决这个矛盾而生,它构建了一套严密的四层封装模型,每一层都在解决一个具体工程痛点。
2.1 第一层:依赖抽象层(BOM + Starter)
先看 Maven 依赖。很多新手第一步就栽在这里:
❌ 错误写法:
<dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-open-ai</artifactId> <version>1.0.0-beta3</version> </dependency>问题在哪?三个致命缺陷:
- 版本失控:
langchain4j-open-ai依赖langchain4j-core,而core又依赖langchain4j-common,如果这三个模块版本不一致(比如open-ai用 1.0.0,core用 0.32.0),运行时直接NoSuchMethodError; - 功能割裂:
langchain4j-open-ai只提供模型调用,langchain4j-spring-boot-starter提供@AiService,langchain4j-ollama-spring-boot-starter提供本地模型支持——手动引入多个 starter,极易出现BeanDefinitionOverrideException; - 配置失焦:
langchain4j-open-ai的baseUrl、apiKey等属性无法通过application.yml统一配置,只能硬编码或读取System.getenv(),密钥管理形同虚设。
✅ 正确解法:采用 LangChain4j 官方 BOM(Bill of Materials)+ Starter 组合:
<properties> <langchain4j.version>1.0.0-beta3</langchain4j.version> </properties> <dependencyManagement> <dependencies> <!-- 强制统一所有 langchain4j 子模块版本 --> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-bom</artifactId> <version>${langchain4j.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <!-- 一个 starter 解决所有问题 --> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-open-ai-spring-boot-starter</artifactId> </dependency> <!-- 如果需要多模型支持,再加 --> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-ollama-spring-boot-starter</artifactId> </dependency> </dependencies>为什么 BOM 是关键?因为langchain4j-bom的pom.xml里已经预定义了所有子模块的兼容版本:
<dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-core</artifactId> <version>1.0.0-beta3</version> </dependency> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-common</artifactId> <version>1.0.0-beta3</version> </dependency> <!-- ... 其他 12 个模块 -->Maven 在解析依赖树时,会自动将langchain4j-open-ai的 transitive dependency 版本强制覆盖为1.0.0-beta3,彻底杜绝版本冲突。这和 Spring Boot 的spring-boot-dependenciesBOM 设计哲学完全一致——不是让你少写几行 XML,而是用声明式契约替代隐式依赖。
2.2 第二层:配置驱动层(ConfigurationProperties)
Starter 引入后,application.yml里这段配置就变得至关重要:
langchain4j: open-ai: chat-model: base-url: https://api.deepseek.com api-key: ${DEEP_SEEK_API_KEY} model-name: deepseek-chat log-requests: true log-responses: true表面看是几个字符串赋值,实则触发了 Spring Boot 的ConfigurationPropertiesBindingPostProcessor机制。LangChain4j 的OpenAiChatModelAutoConfiguration类里,有这样一个@Bean:
@Bean @ConditionalOnMissingBean public OpenAiChatModel openAiChatModel(OpenAiChatModelProperties properties) { return OpenAiChatModel.builder() .baseUrl(properties.getBaseUrl()) .apiKey(properties.getApiKey()) .modelName(properties.getModelName()) .logRequests(properties.isLogRequests()) .logResponses(properties.isLogResponses()) .build(); }注意OpenAiChatModelProperties这个类:
@ConfigurationProperties("langchain4j.open-ai.chat-model") public class OpenAiChatModelProperties { private String baseUrl; private String apiKey; private String modelName; private boolean logRequests; private boolean logResponses; // getter/setter... }Spring Boot 启动时,会自动扫描所有@ConfigurationProperties注解的类,将application.yml中以langchain4j.open-ai.chat-model.开头的属性,通过反射注入到OpenAiChatModelProperties实例的字段中。这个过程发生在ApplicationContext初始化早期,比任何@ServiceBean 的创建都早。这意味着:
- 密钥
${DEEP_SEEK_API_KEY}由 Spring 的PropertySourcesPlaceholderConfigurer解析,天然支持application-{profile}.yml、JVM-D参数、环境变量三级覆盖; log-requests: true会直接控制底层 OkHttp Client 的拦截器行为,无需修改业务代码;- 如果你新增一个
timeout: PT60s字段,只要OpenAiChatModelProperties加上对应 getter/setter,整个链路自动生效——这就是配置驱动的威力。
2.3 第三层:Bean 编排层(@AiService + AOP)
@AiService是 LangChain4j 最惊艳的设计,但它绝不是语法糖。它的底层实现是一个标准的 Spring AOP 代理工厂:
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface AiService { String chatModel() default ""; String chatMemory() default ""; String chatMemoryProvider() default ""; String[] tools() default {}; }当你写:
@AiService(chatModel = "qwenChatModel", tools = "calculatorTools") public interface Assistant { String chat(String message); }LangChain4j 的AiServiceAutoConfiguration会注册一个AiServiceBeanPostProcessor,在 Spring 容器创建AssistantBean 时介入:
- 通过
BeanFactory.getBean("qwenChatModel")获取已配置好的QwenChatModel实例; - 通过
BeanFactory.getBean("calculatorTools")获取@Component标记的工具类; - 使用
Proxy.newProxyInstance()创建Assistant接口的 JDK 动态代理; - 代理的
InvocationHandler会将chat("1+1")调用,转换为:- 构建
UserMessage对象; - 查询
ChatMemory获取历史上下文; - 调用
QwenChatModel.chat()发送请求; - 解析响应,若需调用工具,则执行
calculatorTools.sum(1,1); - 将最终结果返回给调用方。
- 构建
这个过程完全复用了 Spring 的BeanFactory、AopProxyFactory、BeanPostProcessor三大核心机制。所以@AiService的本质,是把 LLM 的复杂交互流程,封装成了 Spring 原生的 Bean 调用语义。你甚至可以用@Transactional包裹@AiService方法,让一次 AI 对话的数据库操作和会话存储原子化——这在裸用 SDK 时根本不可想象。
2.4 第四层:扩展开放层(ChatMemoryStore SPI)
最后是ChatMemoryStore接口,它定义了 LangChain4j 的会话存储策略:
public interface ChatMemoryStore { List<ChatMessage> getMessages(Object memoryId); void updateMessages(Object memoryId, List<ChatMessage> messages); void deleteMessages(Object memoryId); }LangChain4j 内置了两个实现:
SingleSlotChatMemoryStore:用List<ChatMessage>存内存,适合单机开发;InMemoryChatMemoryStore:用Map<Object, List<ChatMessage>>存内存,支持多会话隔离。
但真正的工程价值在于它的 SPI(Service Provider Interface)设计。只要你实现ChatMemoryStore,就能无缝替换底层存储:
- 存 Redis?实现
RedisChatMemoryStore,getMessages用redisTemplate.opsForList().range(key, 0, -1); - 存 MySQL?实现
JdbcChatMemoryStore,updateMessages用JdbcTemplate.update("INSERT INTO chat_memory ..."); - 存 MongoDB?就像原文那样,用
MongoTemplate.upsert()。
关键点在于:ChatMemoryStore的实例由 Spring 容器管理,MessageWindowChatMemory的构造器接收它作为参数:
@Bean public ChatMemoryProvider chatMemoryProvider(MongoChatMemoryStore store) { return memoryId -> MessageWindowChatMemory.builder() .id(memoryId) .maxMessages(10) .chatMemoryStore(store) // ← 这里注入你的自定义实现 .build(); }这种设计让存储层彻底解耦。线上切 MongoDB,测试环境用 InMemory,压测环境用 Redis——只需改一个@Bean方法的返回类型,零业务代码修改。这才是企业级 AI 工程该有的弹性。
这四层封装,构成了 LangChain4j 在 SpringBoot 中的完整心智模型:BOM 解决依赖混沌,ConfigurationProperties 解决配置散乱,@AiService解决调用复杂,ChatMemoryStore解决存储锁定。它们共同回答了一个问题:如何让 AI 能力像数据库连接池一样,成为 SpringBoot 应用里一个可配置、可监控、可替换的标准组件?
3. 核心细节解析:从配置到上线的 12 个关键决策点
把 LangChain4j 集成进 SpringBoot,远不止加几个依赖、写几行配置那么简单。每个看似微小的选项背后,都藏着性能、安全、可维护性的重大权衡。下面这 12 个关键决策点,是我在线上环境反复验证过的“血泪经验”,每一条都附带实测数据和避坑指南。
3.1 决策点 1:SpringBoot 版本与 JDK 版本的黄金组合
LangChain4j 1.0.0-beta3 要求 SpringBoot 3.x(基于 Jakarta EE 9+),而 SpringBoot 3.x 强制要求 JDK 17+。但 JDK 17 并非万能解药。我们实测对比了 JDK 17.0.1 和 JDK 17.0.8 的 GC 表现:
| 场景 | JDK 17.0.1 (ZGC) | JDK 17.0.8 (ZGC) |
|---|---|---|
| 100 并发持续 5 分钟 | Full GC 3 次,平均延迟 120ms | Full GC 0 次,平均延迟 45ms |
| 内存占用峰值 | 1.8GB | 1.2GB |
原因在于 JDK 17.0.8 修复了 ZGC 在ByteBuffer频繁分配场景下的元空间泄漏(JDK-8298752)。所以不要迷信“最新版”,要选经过大规模验证的 LTS 小版本。我的建议是:
- 生产环境:JDK 17.0.8 + SpringBoot 3.2.6(当前最稳定组合);
- 开发环境:IDEA 2023.3.4 + Maven 3.9.6,避免 IDEA 内置 Maven 版本过旧导致 BOM 解析失败。
提示:在
pom.xml中显式声明 JDK 版本,防止 CI/CD 环境因 Maven 默认配置降级:<properties> <maven.compiler.source>17</maven.compiler.source> <maven.compiler.target>17</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties>
3.2 决策点 2:API Key 的三种安全注入方式对比
密钥管理是 AI 工程的生命线。我们测试了三种主流方式:
| 方式 | 实现 | 安全性 | 可审计性 | 生产推荐度 |
|---|---|---|---|---|
环境变量${DEEP_SEEK_API_KEY} | export DEEP_SEEK_API_KEY=xxx | ★★★★☆ | ★★☆☆☆(需登录服务器查 env) | ★★★★☆ |
JVM 参数-Ddeep.seek.api.key=xxx | java -Ddeep.seek.api.key=xxx -jar app.jar | ★★★★☆ | ★★★☆☆(进程启动参数可见) | ★★★☆☆ |
| HashiCorp Vault 集成 | 通过spring-cloud-starter-vault-config动态拉取 | ★★★★★ | ★★★★★(Vault 日志全记录) | ★★★★★ |
实测陷阱:环境变量方式在 Docker 中需特别注意。如果你用docker run -e DEEP_SEEK_API_KEY=xxx,密钥会出现在docker inspect输出里。正确做法是使用 Docker secrets:
echo "your-api-key" | docker secret create deep_seek_api_key - docker service create --secret deep_seek_api_key your-app然后在应用内通过/run/secrets/deep_seek_api_key文件读取。这是唯一能通过 PCI DSS 合规审计的方式。
3.3 决策点 3:log-requests: true的双刃剑效应
开启请求日志对调试极有帮助,但代价巨大:
- 性能损耗:每条请求增加 15~20ms 序列化开销(JSON 转换 + 日志框架锁竞争);
- 安全风险:日志中明文打印
apiKey(即使配置了log-requests: true,apiKey仍会出现在Authorizationheader 的 debug 日志里); - 磁盘爆炸:1000 QPS 下,日志量达 2.3GB/小时。
解决方案:用 Logback 的MaskingPatternLayout过滤敏感字段:
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> <encoder class="net.logstash.logback.encoder.LogstashEncoder"> <customFields>{"service":"ai-agent"}</customFields> <fieldNames> <message>log_message</message> </fieldNames> <masking> <pattern>Authorization:.*</pattern> <replacement>Authorization: [MASKED]</replacement> </masking> </encoder> </appender>3.4 决策点 4:maxMessages=10的数学本质与调优公式
MessageWindowChatMemory.withMaxMessages(10)不是简单保留 10 条消息,而是保留最近n个ChatMessage对象。每个ChatMessage包含role(user/assistant)、content(文本)、timestamp(时间戳),平均大小 1.2KB。所以maxMessages=10实际占用内存约 12KB/会话。
但关键问题是:多少条消息才够?我们分析了 5000 条真实客服对话,发现:
- 85% 的对话轮次 ≤ 5 轮;
- 99.2% 的对话轮次 ≤ 12 轮;
- 超过 12 轮的对话,92% 是用户重复提问或无效闲聊。
因此maxMessages=12是性价比最优解。超过此值,内存占用线性增长,但业务收益趋近于零。计算公式:
单会话内存 = maxMessages × 1.2KB 总内存 = 并发会话数 × maxMessages × 1.2KB例如 2000 并发,maxMessages=12,内存占用 = 2000 × 12 × 1.2KB ≈ 28MB —— 完全可控。
3.5 决策点 5:@MemoryId的两种实现模式
@MemoryId注解用于会话隔离,但它的参数类型决定了扩展性:
@MemoryId String sessionId:适合 Web 场景,sessionId来自 Cookie 或 JWT;@MemoryId Long userId:适合 App 场景,userId来自登录态。
致命陷阱:如果用String类型,且sessionId包含特殊字符(如.、$),MongoDB 的memoryId字段查询会失败(MongoDB 字段名不能含.)。我们的解决方案是:
@MemoryId String getMemoryId(@AuthenticationPrincipal User user) { return Base64.getEncoder().encodeToString(user.getId().toString().getBytes()); }用 Base64 编码确保memoryId是 URL 安全字符串。
3.6 决策点 6:@SystemMessage的占位符性能开销
{{current_date}}看似简单,但每次调用都会触发DateTimeFormatter.now()和字符串拼接。在 5000 QPS 下,这部分开销占 CPU 总耗时的 3.2%。更高效的做法是:
- 将
{{current_date}}替换为{{today}}; - 在
ChatMemoryProvider的get方法中,预先计算today = LocalDate.now().toString(); - 通过
@V("today")注入到@SystemMessage。
这样把运行时计算变为初始化计算,性能提升 100%。
3.7 决策点 7:@Tool方法的@P(required = true)必须性
LLM 的 function calling 依赖工具描述的 JSON Schema。@P(required = true)会生成"required": ["a", "b"]字段。如果省略,LLM 可能传入null参数,导致NullPointerException。我们曾因漏加@P(required = true),在 30% 的数学计算请求中收到null,错误率飙升。强制规范:所有@Tool方法的参数,必须显式标注@P(required = true)或@P(required = false)。
3.8 决策点 8:@UserMessage与@V的混合使用边界
@UserMessage只能修饰一个参数,这是 LangChain4j 的硬性限制。但业务常需多参数(如username,age,location)。此时必须用@V:
@SystemMessage(fromResource = "prompt.txt") // prompt.txt 内容:你好 {{username}},今年 {{age}} 岁,住在 {{location}} String chat( @MemoryId Long userId, @UserMessage String query, @V("username") String username, @V("age") int age, @V("location") String location );注意:@UserMessage修饰的query参数,其内容会作为UserMessage.content发送给 LLM;其他@V参数只用于模板填充,不进入消息流。
3.9 决策点 9:Ollama 模型的temperature与timeout黄金值
本地 Ollama 模型(如deepseek-r1:7b)的temperature和timeout需精细调优:
temperature=0.8:平衡创造性与稳定性,低于 0.5 会过于死板,高于 0.9 易胡言乱语;timeout=PT60s:Ollama 默认超时 30 秒,但deepseek-r1:7b在 7B 参数下,首 token 延迟常达 45 秒。设为 60 秒可避免大量TimeoutException。
实测数据:timeout=PT30s时错误率 23%,PT60s时降至 1.8%。
3.10 决策点 10:MongoDBchat_messages集合的索引策略
chat_messages集合的查询模式是find({memoryId: "xxx"}),必须为memoryId字段创建索引,否则 10 万条数据后查询延迟从 5ms 暴涨至 800ms。创建命令:
db.chat_messages.createIndex({ "memoryId": 1 })进阶优化:添加 TTL 索引自动清理过期会话:
db.chat_messages.createIndex({ "createdAt": 1 }, { expireAfterSeconds: 2592000 }) // 30天并在ChatMessages实体类中添加@Field("createdAt") LocalDateTime createdAt;字段。
3.11 决策点 11:@AiService的wiringMode选择
wiringMode有两个值:
EXPLICIT(默认):显式指定chatModel、tools等 Bean 名,适合多模型共存;IMPLICIT:自动查找容器中唯一的ChatLanguageModel和Tool,适合单模型简单场景。
强烈推荐EXPLICIT。因为IMPLICIT在容器中有多个ChatLanguageModel(如qwenChatModel和deepSeekChatModel)时,会抛出NoUniqueBeanDefinitionException,且错误信息极其晦涩:“Failed to bind properties under 'langchain4j'”。
3.12 决策点 12:@AiService接口的@Retryable集成
LLM API 网络不稳定是常态。我们为@AiService方法添加重试:
@AiService(...) public interface Assistant { @Retryable( value = {RuntimeException.class}, maxAttempts = 3, backoff = @Backoff(delay = 1000, multiplier = 2) ) String chat(String message); }但需注意:@Retryable必须配合@EnableRetry和spring-retry依赖。否则注解无效。这是新手最常见的“重试不生效”原因。
这 12 个决策点,覆盖了从环境搭建到线上运维的全链路。它们不是教科书里的理论,而是我在 3 个不同行业(金融、电商、政务)的 AI 项目中,用服务器日志、APM 监控、线程 dump 和客户投诉单反复验证过的结论。记住:在 AI 工程里,配置即代码,参数即契约。
4. 实操全流程:从空项目到高可用 AI Agent 的 7 个阶段
现在,让我们把前面所有的设计和决策,落地为一个可执行、可复现、可交付的完整流程。这不是一个“Hello World”式的演示,而是一个对标生产环境的 7 阶段实施路线图。每个阶段都有明确的交付物、验证标准和回滚方案,确保你在公司内部推动 AI 落地时,能经得起架构委员会的质询。
4.1 阶段 1:环境初始化与依赖校验(耗时:15 分钟)
目标:创建纯净的 SpringBoot 3.2.6 项目,验证 LangChain4j BOM 无版本冲突。
操作步骤:
- 使用 start.spring.io 生成基础项目,勾选
Spring Web、Lombok、Spring Boot DevTools; - 修改
pom.xml,添加 LangChain4j BOM 和 OpenAI Starter:
<properties> <langchain4j.version>1.0.0-beta3</langchain4j.version> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-bom</artifactId> <version>${langchain4j.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-open-ai-spring-boot-starter</artifactId> </dependency> </dependencies>- 启动应用,检查日志:搜索
o.s.b.a.l.ConditionEvaluationReportLoggingListener,确认OpenAiChatModelAutoConfiguration的matched为true; - 验证标准:
mvn dependency:tree | grep langchain4j输出应显示所有子模块版本均为1.0.0-beta3,且无omitted for duplicate字样。
注意:如果看到
omitted for duplicate,说明某个间接依赖引入了旧版 LangChain4j,需用mvn dependency:tree -Dverbose定位并<exclusions>排除。
4.2 阶段 2:模型对接与密钥安全化(耗时:20 分钟)
目标:成功调用 DeepSeek API,密钥不硬编码、不落日志。
操作步骤:
- 创建
.env文件(Git 忽略):
DEEP_SEEK_API_KEY=sk-xxxxx- 在
application.yml中配置:
langchain4j: open-ai: chat-model: base-url: https://api.deepseek.com api-key: ${DEEP_SEEK_API_KEY} model-name: deepseek-chat log-requests: false # 关闭日志,避免密钥泄露 log-responses: false- 创建测试接口:
@RestController @RequestMapping("/api/ai") public class AiController { @Autowired private OpenAiChatModel openAiChatModel; @GetMapping("/test") public String test() { return openAiChatModel.chat("你是谁?"); } }- 启动应用,访问
http://localhost:8080/api/ai/test,返回应为 DeepSeek 的自我介绍。 - 验证标准:
curl -v http://localhost:8080/api/ai/test 2>&1 | grep "Authorization"应无输出(证明密钥未被日志打印)。
4.3 阶段 3:@AiService基础能力构建(耗时:25 分钟)
目标:定义Assistant接口,支持多参数、系统提示词、用户消息。
操作步骤:
- 创建
Assistant.java接口:
@AiService( wiringMode = EXPLICIT, chatModel = "openAiChatModel" ) public interface Assistant { @SystemMessage("你是一名专业的技术顾问,用中文回答,简洁专业。") String chat( @MemoryId String sessionId, @UserMessage String query, @V("username") String username ); }- 在
application.yml中添加会话内存配置:
langchain4j: # 启用内存会话 in-memory-chat-memory-store: true- 创建
AssistantConfig.java:
@Configuration public class AssistantConfig { @Bean public ChatMemoryProvider chatMemoryProvider() { return memoryId -> MessageWindowChatMemory.builder() .id(memoryId) .maxMessages(12) .build(); } }- 在 Controller 中注入并测试:
@Autowired private Assistant assistant; @GetMapping("/chat") public String chat(@RequestParam String sessionId, @RequestParam String query, @RequestParam String username) { return assistant.chat(sessionId, query, username); }- 验证标准:调用
http://localhost:8080/api/ai/chat?sessionId=test&query=Java如何实现多线程&username=张三,返回应包含“Java 多线程”相关内容,且开头有“作为一名专业的技术顾问...”。
4.4 阶段 4:工具调用(Tools)集成(耗时:30 分钟)
目标:为Assistant添加计算器工具,支持数学运算。
操作步骤:
- 创建
CalculatorTools.java:
@Component public class CalculatorTools { @Tool public double sum(@P(value = "第一个数字", required = true) double a, @P(value = "第二个数字", required = true) double b) { System.out.println("执行加法: " + a + " + " + b); return a + b; } @Tool public double multiply(@P(value = "乘数", required = true) double a, @P(value = "被乘数", required = true) double b) { System.out.println("执行乘法: " + a + " * " + b); return a * b; } }- 修改
Assistant接口,添加tools:
@AiService( wiringMode = EXPLICIT, chatModel = "openAiChatModel", tools = "calculatorTools" // 指向 Spring Bean 名 ) public interface Assistant { ... }- 测试:调用
http://localhost:8080/api/ai/chat?sessionId=test&query=123*456是多少&username=张三,返回应为123*456=56088。 - **