LangChain4j与Prompt工程在Java中的实战应用
1. LangChain4j与Prompt工程实战概述
在2026年的技术生态中,LangChain4j已成为Java开发者对接大语言模型(LLM)的首选框架。不同于传统的直接API调用方式,LangChain4j通过模块化设计将提示词工程、记忆管理、工具调用等复杂功能封装为可复用的组件。本次实战将聚焦于如何在后端服务中构建高效的Prompt工程体系,特别针对Spring Boot 3.5+环境进行适配。
当前业界常见的痛点包括:
- 提示词模板难以维护
- 对话上下文管理复杂
- 流式响应处理效率低下
- Token消耗不可控
我们将通过三个核心维度解决这些问题:
- 分层API设计(底层/高层)
- 动态记忆管理
- 可观测性增强
2. 环境搭建与基础配置
2.1 依赖管理配置
使用LangChain4j 1.8.0+版本需要JDK17及以上环境,在pom.xml中需明确定义BOM管理:
<dependencyManagement> <dependencies> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-bom</artifactId> <version>1.8.0</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>针对OpenAI兼容API(如DeepSeek)的starter配置:
<dependencies> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-open-ai-spring-boot-starter</artifactId> </dependency> <!-- Reactor支持 --> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-reactor</artifactId> </dependency> </dependencies>2.2 模型连接配置
application.yml中的基础配置示例:
langchain4j: open-ai: chat-model: base-url: https://api.deepseek.com api-key: ${OPEN_API_KEY} model-name: deepseek-reasoner max-tokens: 2000 temperature: 0.7 log-requests: true关键参数说明:
- temperature:控制生成随机性(0-2)
- max-tokens:单次响应最大token数
- top-p:核采样阈值(建议0.9)
3. 分层API设计与实现
3.1 底层API实现
3.1.1 阻塞式ChatModel
基础配置类示例:
@Configuration public class LangChainConfig { @Bean public ChatModel chatModel() { return OpenAiChatModel.builder() .baseUrl("https://api.deepseek.com") .apiKey(System.getenv("OPEN_API_KEY")) .modelName("deepseek-reasoner") .maxRetries(3) .timeout(Duration.ofSeconds(30)) .build(); } }控制器实现要点:
@RestController @RequestMapping("/api/chat") public class ChatController { private final ChatModel chatModel; @PostMapping public CompletionResult chat(@RequestBody ChatRequest request) { List<ChatMessage> messages = Arrays.asList( SystemMessage.from("你是一个专业的数学辅导老师"), UserMessage.from(request.getQuestion()) ); ChatResponse response = chatModel.generate(messages); return new CompletionResult( response.content(), response.tokenUsage() ); } }3.1.2 流式StreamingChatModel
流式接口的特殊处理:
@GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE) public Flux<String> streamChat(String question) { return Flux.create(sink -> { streamingChatModel.generate( Arrays.asList(UserMessage.from(question)), new StreamingResponseHandler() { @Override public void onNext(String token) { sink.next(token); } @Override public void onComplete() { sink.complete(); } } ); }); }流式响应注意事项:
- 必须设置produces = MediaType.TEXT_EVENT_STREAM_VALUE
- 客户端需要支持Server-Sent Events(SSE)
- 超时时间建议设置为0(不超时)
3.2 高层API实现
3.2.1 声明式AI服务
定义服务接口:
@AiService public interface MathTutor { @SystemMessage("你是一个数学专家,用简单易懂的方式解释概念") String explainConcept(@UserMessage String concept); @SystemMessage("你是一个数学解题助手") Flux<String> solveProblem(@UserMessage String problem); }自动配置支持:
@Configuration public class AiServiceConfig { @Bean public MathTutor mathTutor(ChatModel chatModel) { return AiServices.create(MathTutor.class, chatModel); } }3.2.2 模板管理技巧
推荐将提示模板外部化:
- 创建resources/prompts目录
- 按功能分类存储模板文件:
- math_concept_explainer.txt
- problem_solver.txt
- 通过注解引用:
@SystemMessage(fromResource = "/prompts/math_concept_explainer.txt") String explainConcept(@UserMessage String concept);模板变量语法:
你是一个{{role}},请用{{style}}的方式回答关于{{topic}}的问题。 当前用户等级:{{userLevel}}4. 记忆管理系统实现
4.1 记忆与历史的区别
| 维度 | 记忆(Memory) | 历史(History) |
|---|---|---|
| 存储内容 | 提炼后的关键信息 | 原始对话记录 |
| 使用方式 | 作为Prompt上下文 | 用于展示/审计 |
| 存储形式 | 结构化数据 | 原始文本 |
| 典型实现 | TokenWindowChatMemory | 数据库存储 |
4.2 记忆管理实战
4.2.1 基础配置
@Configuration public class MemoryConfig { @Bean public ChatMemoryStore memoryStore() { return new RedisChatMemoryStore(redisTemplate); } @Bean public ChatMemoryProvider memoryProvider() { return id -> TokenWindowChatMemory.builder() .id(id) .maxTokens(2000) .chatMemoryStore(memoryStore()) .build(); } }4.2.2 对话会话管理
@RestController @RequestMapping("/api/session") public class SessionController { @PostMapping public SessionResponse startSession() { String sessionId = UUID.randomUUID().toString(); memoryProvider.get(sessionId); // 初始化记忆 return new SessionResponse(sessionId); } @DeleteMapping("/{id}") public void clearSession(@PathVariable String id) { memoryStore.deleteMessages(id); } }4.2.3 记忆优化策略
- 关键信息提取:
memory.add( UserMessage.from("我的名字是张三"), AiMessage.from("好的,已记住您的名字") ); // 提取关键信息 memory.add( SystemMessage.from("用户姓名:张三") );- Token压缩算法:
TokenWindowChatMemory.builder() .tokenCompressor(new KeyInfoTokenCompressor()) .maxTokens(1500) .build();5. 可观测性增强
5.1 监听器实现
@Component public class ChatObserver implements ChatModelListener { @Override public void onRequest(ChatModelRequestContext context) { MDC.put("traceId", UUID.randomUUID().toString()); log.info("Request to {}: {}", context.model().modelName(), context.messages()); } @Override public void onResponse(ChatModelResponseContext context) { log.info("Response from {} ({} tokens)", context.model().modelName(), context.tokenUsage().totalTokens()); } }5.2 监控指标暴露
@Bean public MeterRegistryCustomizer<MeterRegistry> metrics() { return registry -> { Counter.builder("llm.requests") .tag("model", "deepseek") .register(registry); Timer.builder("llm.latency") .publishPercentiles(0.5, 0.95) .register(registry); }; }6. 性能优化策略
6.1 提示词压缩技术
- 去除冗余空格和换行
- 使用缩写形式:
- "请" → "pls"
- "问题" → "q"
- 语义压缩:
PromptCompressor.compress("解释勾股定理", CompressLevel.AGGRESSIVE);
6.2 缓存策略实现
@Bean public CacheManager cacheManager() { return new CaffeineCacheManager("promptCache") { @Override protected Cache<Object, Object> createCache(String name) { return Caffeine.newBuilder() .maximumSize(1000) .expireAfterWrite(1, TimeUnit.HOURS) .build(); } }; } @Cacheable(value = "promptCache", key = "#prompt.hashCode()") public String getCachedResponse(String prompt) { return chatModel.generate(prompt); }7. 安全防护方案
7.1 输入过滤
public String safeGenerate(String prompt) { if (PromptValidator.containsSensitive(prompt)) { throw new InvalidPromptException(); } return chatModel.generate( PromptSanitizer.sanitize(prompt) ); }7.2 输出校验
@Bean public OutputFilter outputFilter() { return content -> { if (ContentChecker.hasHarmfulContent(content)) { return "[内容已过滤]"; } return content; }; }8. 实战问题排查
8.1 常见错误代码
| 错误码 | 含义 | 解决方案 |
|---|---|---|
| 400 | 无效的Prompt结构 | 检查system message位置 |
| 429 | 速率限制 | 实现漏桶算法控制请求频率 |
| 503 | 模型过载 | 启用自动重试机制 |
| 504 | 响应超时 | 调整timeout参数 |
8.2 调试技巧
- 启用详细日志:
logging: level: dev.langchain4j: DEBUG- 请求追踪:
chatModel = OpenAiChatModel.builder() .listeners(new RequestTracer()) .build();- Token分析工具:
TokenCounter.estimateTokens(messages);9. 架构设计建议
9.1 分层设计
┌───────────────────────┐ │ Controller │ └──────────┬────────────┘ │ ┌──────────▼────────────┐ │ Service Layer │ │ ┌──────────────────┐ │ │ │ Prompt Engine │ │ │ └──────────────────┘ │ │ ┌──────────────────┐ │ │ │ Memory Management │ │ │ └──────────────────┘ │ └──────────┬────────────┘ │ ┌──────────▼────────────┐ │ LangChain4j SDK │ └──────────┬────────────┘ │ ┌──────────▼────────────┐ │ LLM API │ └───────────────────────┘9.2 集群部署方案
- 模型代理层:
- 负载均衡
- 故障转移
- 本地缓存:
- Caffeine集群同步
- 会话亲和性:
- 基于sessionId的路由
10. 演进路线
短期优化:
- 实现Prompt版本管理
- 增加AB测试支持
中期规划:
- 构建可视化Prompt工作室
- 开发领域特定语言(DSL)
长期愿景:
- 自适应Prompt生成
- 全自动记忆优化
在实际项目落地过程中,我们发现这些关键决策点对最终效果影响显著:
- 记忆窗口大小的选择(建议500-2000 tokens)
- 流式响应分块策略(按句子分割优于固定长度)
- 异常恢复机制(特别是长对话场景)
特别提醒:当集成第三方模型时,务必进行全面的兼容性测试。我们曾在DeepSeek模型上发现,某些参数组合会导致非预期的截断行为,这需要通过设置maxTokens=null来解决。
