LangChain4j Java LLM工程化实战:从本地RAG到生产避坑
1. 为什么是 LangChain4j 而不是直接上 Spring AI 或原生 LLM SDKLangChain4j 这个名字刚看到时我第一反应是“又一个套壳项目”——毕竟市面上叫“XXChain”的库不少有些只是把 OpenAI Java SDK 包了一层加几个注解就号称“支持 RAG”“内置 Agent”。但真正搭起第一个本地 LLM 向量检索 Demo 后我才意识到LangChain4j 不是“又一个封装”而是Java 生态里第一个把 LLM 工程化链路真正跑通、且拒绝魔法的框架。它不强制你用 Spring纯 Java SE 环境下也能跑不隐藏 token 计数逻辑不把 System Prompt 偷偷塞进 request body 里让你 debug 到凌晨三点。它把每个组件都做成可替换、可观测、可调试的独立单元AiModel是模型调用层EmbeddingModel是向量化层EmbeddingStore是存储层Retriever是检索层ChatMemory是状态层——没有“黑盒”只有接口契约。这背后其实是 Java 工程师最熟悉的思维面向接口编程 显式依赖注入 可插拔设计。比如你要换模型不是改application.yml里一行model: qwen2就完事而是显式 new 一个Qwen2AiModel实例传给AiServices构造器。这种“啰嗦”恰恰是稳定性的来源。提示很多新手卡在第一步——以为 LangChain4j 是个“开箱即用的 AI 应用框架”结果发现连Hello World都要自己写PromptTemplate和ResponseFormat。它本质是一个LLM 编排工具包Orchestration Toolkit不是低代码平台。接受这个定位才能少走弯路。我带过的某高校实训班里A同学一开始坚持用 Spring Boot Starter 自动装配所有 Bean结果在切换本地 Ollama 模型时因为SpringAiModel的默认重试策略和 Ollama 的 HTTP 流式响应不兼容导致整个ChatMemory乱序。后来他退一步手动构造AiServices把RetryPolicy设为NONE问题当场解决。这不是倒退是回归工程本质先理解数据流再谈自动化。所以如果你正站在 Java LLM 的入口别急着找“最简 Demo”先问自己三个问题我的 LLM 请求是否需要流式响应影响StreamingChatLanguageModel接口选型我的 Embedding 是否必须本地运行决定用OllamaEmbeddingModel还是AzureOpenAiEmbeddingModel我的业务是否需要多轮对话记忆决定是否引入InMemoryChatMemory或自定义 Redis 实现这三个问题的答案会直接决定你后续 80% 的代码结构。LangChain4j 的价值正在于它逼你直面这些决策点而不是用一层抽象帮你绕过去。2. 从零构建一个可调试的本地 RAG Demo不跳过任何中间态很多教程一上来就是AiServices.create(...)然后aiServices.chat(xxx)看起来三行代码搞定。但真实项目里你一定会遇到为什么检索出来的 chunk 总是不相关为什么 prompt 里写了“请用中文回答”模型还是输出英文为什么加了SystemMessage注解实际请求里却没看到 system role这些问题全因跳过了中间态的可观测性。下面我带你手写一个带完整日志埋点、可逐层验证的 RAG 流程全程基于 JDK 17 Maven不依赖 Spring。2.1 环境准备最小依赖集与版本锁定LangChain4j 官方推荐使用langchain4j-core 对应实现模块而非langchain4j-all后者会引入大量未使用的 transitive 依赖增加 classpath 冲突风险。我的pom.xml关键片段如下properties langchain4j.version0.33.0/langchain4j.version ollama-java.version0.9.0/ollama-java.version /properties dependencies !-- 核心接口必须 -- dependency groupIddev.langchain4j/groupId artifactIdlangchain4j-core/artifactId version${langchain4j.version}/version /dependency !-- 本地模型支持Ollama -- dependency groupIddev.langchain4j/groupId artifactIdlangchain4j-ollama/artifactId version${langchain4j.version}/version /dependency !-- 向量存储内存版用于调试生产换 Qdrant/Weaviate -- dependency groupIddev.langchain4j/groupId artifactIdlangchain4j-memory/artifactId version${langchain4j.version}/version /dependency !-- 日志增强关键用于观察 token 使用和 embedding 向量 -- dependency groupIddev.langchain4j/groupId artifactIdlangchain4j-monitoring-prometheus/artifactId version${langchain4j.version}/version /dependency /dependencies注意langchain4j-ollama依赖ollama-java0.9.0而该版本要求 Ollama 服务端 0.3.0。我实测过 0.2.x 版本会因/api/chat接口字段变更导致JsonMappingException。建议直接curl -fsSL https://ollama.com/install.sh | sh升级到最新版。2.2 第一步验证模型调用链路不带 RAG先扔掉所有高级功能只做最朴素的事发一条消息看模型能不能回。这是所有后续工作的地基。public class ModelTest { public static void main(String[] args) { // 1. 构建模型实例显式指定 URL 和模型名 AiModel model OllamaChatModel.builder() .baseUrl(http://localhost:11434) // Ollama 默认端口 .modelName(qwen2:1.5b) // 必须是已 pull 的模型 .timeout(60, TimeUnit.SECONDS) .logRequests(true) // 关键开启请求日志 .logResponses(true) // 关键开启响应日志 .build(); // 2. 构建最简 ChatLanguageModel非 Streaming ChatLanguageModel chatModel OllamaChatModel.builder() .baseUrl(http://localhost:11434) .modelName(qwen2:1.5b) .build(); // 3. 发送单次请求 String response chatModel.generate(你好请用一句话介绍你自己); System.out.println(【原始响应】 response); } }运行后控制台会打印出完整的 HTTP 请求体含messages数组和响应体。重点观察两点请求体中messages[0].role是否为user如果是system说明你误用了SystemMessage注解响应体中message.content是否有内容若为空大概率是模型未加载成功ollama list确认qwen2:1.5b状态为unchanged这一步看似简单但我在某公司内部培训中发现超过 65% 的“模型不响应”问题根源都在这里要么 Ollama 服务没起来要么模型名拼错qwen2:1.5b≠qwen2:1.5b-text要么防火墙拦截了 11434 端口。不亲眼看到 HTTP 流量你就永远在猜。2.3 第二步注入 Embedding 能力并验证向量化质量RAG 的核心不是“检索”而是“检索什么”。Embedding 模型决定了你的知识库能否被正确切分和匹配。LangChain4j 把EmbeddingModel和AiModel完全解耦这是巨大优势。我们选用nomic-embed-text轻量、开源、支持中文ollama pull nomic-embed-textJava 侧代码// 构建 EmbeddingModel EmbeddingModel embeddingModel OllamaEmbeddingModel.builder() .baseUrl(http://localhost:11434) .modelName(nomic-embed-text) .build(); // 测试向量化输入一段中文看是否生成合理向量 String text Java 中 ArrayList 和 LinkedList 的主要区别是什么; Embedding embedding embeddingModel.embed(text).content(); System.out.printf(【文本】%s%n, text); System.out.printf(【向量维度】%d%n, embedding.vector().length); System.out.printf(【向量前5维】%s%n, Arrays.toString(Arrays.stream(embedding.vector()) .limit(5) .mapToObj(d - String.format(%.3f, d)) .toArray()));输出类似【文本】Java 中 ArrayList 和 LinkedList 的主要区别是什么 【向量维度】768 【向量前5维】[-0.023, 0.156, -0.442, 0.089, 0.211]注意nomic-embed-text输出 768 维向量而bge-m3是 1024 维。如果你后续要用 Qdrant建 collection 时vector_size必须严格匹配。这是生产环境最常见的 500 错误来源之一。更关键的是语义验证拿两个语义相近的句子计算余弦相似度是否 0.8拿两个无关句子是否 0.3。我写了个小工具类public class EmbeddingValidator { public static double cosineSimilarity(float[] v1, float[] v2) { double dotProduct 0.0; double norm1 0.0; double norm2 0.0; for (int i 0; i v1.length; i) { dotProduct v1[i] * v2[i]; norm1 v1[i] * v1[i]; norm2 v2[i] * v2[i]; } return dotProduct / (Math.sqrt(norm1) * Math.sqrt(norm2)); } public static void main(String[] args) { EmbeddingModel em ... // 同上 String s1 Java 集合框架中 ArrayList 的底层实现是数组; String s2 ArrayList 在 Java 里是用动态数组实现的; String s3 Python 的列表是用链表实现的; double sim12 cosineSimilarity(em.embed(s1).content().vector(), em.embed(s2).content().vector()); double sim13 cosineSimilarity(em.embed(s1).content().vector(), em.embed(s3).content().vector()); System.out.printf(s1-s2 相似度: %.3f%n, sim12); // 应 0.85 System.out.printf(s1-s3 相似度: %.3f%n, sim13); // 应 0.25 } }实测nomic-embed-text对中文技术文本效果稳定bge-m3更强但体积大。不要迷信 SOTA 模型先用小模型验证 pipeline 是否跑通——这是我踩过最深的坑团队曾花两周调优bge-reranker-large结果发现根本问题是文档切分粒度太大整篇 API 文档当一个 chunk换RecursiveCharacterTextSplitter后nomic-embed-text效果反而更好。2.4 第三步组装 RAG 链路——从检索到生成的全链路日志现在把前面三块拼起来。注意LangChain4j 的Retriever不是“检索器”而是“检索策略执行器”。它接收Query返回ListContent这个Content可以是文本、图片、甚至自定义对象。public class RAGDemo { public static void main(String[] args) { // 1. 初始化各组件全部显式构造 ChatLanguageModel chatModel OllamaChatModel.builder() .baseUrl(http://localhost:11434) .modelName(qwen2:1.5b) .logRequests(true) .logResponses(true) .build(); EmbeddingModel embeddingModel OllamaEmbeddingModel.builder() .baseUrl(http://localhost:11434) .modelName(nomic-embed-text) .build(); // 2. 构建内存向量库仅用于调试 EmbeddingStoreContent embeddingStore InMemoryEmbeddingStore.builder() .dimension(768) // 必须与 embeddingModel 输出维度一致 .build(); // 3. 加载知识库模拟从文件读取 ListString documents Arrays.asList( ArrayList 底层是动态数组随机访问快插入删除慢, LinkedList 底层是双向链表插入删除快随机访问慢, HashMap 基于哈希表实现key 不允许重复value 可重复 ); // 4. 批量嵌入并存入向量库 for (String doc : documents) { Embedding embedding embeddingModel.embed(doc).content(); embeddingStore.add(embedding, new Content(doc)); } // 5. 构建检索器这里用最简单的相似度检索 RetrieverContent retriever EmbeddingStoreRetriever.builder() .embeddingStore(embeddingStore) .embeddingModel(embeddingModel) .maxResults(2) // 最多返回2个相关chunk .minScore(0.3) // 相似度阈值低于此值不返回 .build(); // 6. 构建 Prompt 模板显式控制格式 PromptTemplate promptTemplate PromptTemplate.from( 你是一个 Java 技术专家。请根据以下上下文回答问题 如果上下文无法回答请说我不知道。\n\n 上下文{{context}}\n\n 问题{{question}}\n\n 回答 ); // 7. 执行 RAG 查询 String question ArrayList 和 LinkedList 哪个更适合频繁插入; ListContent relevantContents retriever.retrieve(question); System.out.println(【检索到的上下文】); for (int i 0; i relevantContents.size(); i) { System.out.printf( [%d] %s%n, i 1, relevantContents.get(i).text()); } // 8. 渲染 Prompt 并调用模型 String renderedPrompt promptTemplate.apply(Parameters.of( context, relevantContents.stream() .map(Content::text) .collect(Collectors.joining(\n)), question, question )); System.out.println(\n【渲染后的 Prompt】 renderedPrompt); String answer chatModel.generate(renderedPrompt); System.out.println(\n【最终回答】 answer); } }运行这段代码你会在控制台看到清晰的四段日志检索阶段显示retriever.retrieve()返回了哪几条 chunk 及其相似度分数Prompt 渲染阶段看到{{context}}被真实内容替换后的完整 prompt模型请求阶段HTTP 请求体中messages[0].content就是渲染后的 prompt模型响应阶段原始 JSON 响应体含message.content。提示minScore参数是 RAG 稳定性的命门。设为0.0会导致无关内容强行塞入 prompt引发幻觉设为0.9又可能漏检。我的经验是先设0.5用 20 个测试问题跑一遍统计召回率relevant chunk 是否在 top2再微调。某金融客户项目中我们将minScore从0.6调到0.65准确率提升 22%但召回率下降 8%——这是业务可接受的 trade-off。3. LangChain4j 的核心组件解剖接口契约比实现更重要LangChain4j 的设计哲学是一切皆接口实现可替换行为可预期。它的源码里几乎没有if-else分支判断“当前用的是哪个模型”而是通过接口方法签名强制约定行为。理解这五个核心接口你就掌握了 90% 的扩展能力。3.1AiModel模型调用的统一门面AiModel是最顶层接口定义了一个通用的generate(String input)方法。但它真正的价值在于其子接口ChatLanguageModel处理ListChatMessage输入返回AiMessage支持system/user/assistant角色StreamingChatLanguageModel返回StreamAiMessage用于前端实时打字效果EmbeddingModel输入String输出Embedding含 float[] 向量关键点在于ChatLanguageModel和EmbeddingModel是正交的互不继承。这意味着你可以用qwen2做 chat用nomic-embed-text做 embedding完全解耦。很多新手误以为“一个模型必须同时支持 chat 和 embed”其实大模型厂商也分专业OpenAI 的gpt-4-turbo做 chattext-embedding-3-small做 embed。// ✅ 正确混合使用不同模型 ChatLanguageModel chatModel new OpenAiChatModel(gpt-4-turbo); EmbeddingModel embeddingModel new AzureOpenAiEmbeddingModel(text-embedding-ada-002); // ❌ 错误试图用 chat 模型做 embedding // embeddingModel.embed(hello) // 编译报错因为 OpenAiChatModel 不实现 EmbeddingModel这种强类型约束避免了运行时ClassCastException。我在某电商项目中曾因误将OllamaChatModel强转为EmbeddingModel导致服务启动时静默失败Spring 的PostConstruct未抛异常排查耗时两天。LangChain4j 的接口设计本质上是一种编译期防御。3.2EmbeddingStore向量存储的抽象层EmbeddingStoreT接口只定义了四个方法public interface EmbeddingStoreT { void add(Embedding embedding, T embedded); // 存储 ListEmbeddingMatchT findRelevant(Embedding query, int maxResults); // 检索 void remove(String id); // 删除 void removeAll(); // 清空 }注意它不关心底层是内存、Redis 还是 Qdrant。T是你存的任意对象String、Document、甚至User实体。这带来两个巨大好处测试友好单元测试用InMemoryEmbeddingStore集成测试换QdrantEmbeddingStore代码零修改元数据扩展Embedded可以是自定义类携带sourceUrl、updatedAt、author等字段检索时一并返回。public class DocumentWithMeta { private final String content; private final String source; private final LocalDateTime updatedAt; public DocumentWithMeta(String content, String source, LocalDateTime updatedAt) { this.content content; this.source source; this.updatedAt updatedAt; } // getter... } // 存入时 embeddingStore.add(embedding, new DocumentWithMeta(..., manual.md, now())); // 检索时 ListEmbeddingMatchDocumentWithMeta matches embeddingStore.findRelevant(query, 3); for (EmbeddingMatchDocumentWithMeta match : matches) { System.out.printf(匹配度: %.3f, 来源: %s%n, match.score(), match.embedded().source()); }注意InMemoryEmbeddingStore的findRelevant默认用余弦相似度而QdrantEmbeddingStore支持dot、euclid等多种距离算法。如果你的业务对精度敏感如法律文书比对务必在 Qdrant 创建 collection 时指定distance: Cosine否则默认Dot会导致结果偏差。3.3Retriever检索策略的组合器RetrieverT是 LangChain4j 最精妙的设计。它不绑定具体存储而是接收一个Query字符串返回ListT。标准实现EmbeddingStoreRetriever只是其中一种你完全可以写自己的public class HybridRetriever implements RetrieverContent { private final RetrieverContent embeddingRetriever; private final RetrieverContent keywordRetriever; // 如 Lucene 实现 Override public ListContent retrieve(String query) { // 混合检索embedding 结果 keyword 结果去重合并 ListContent embeddingResults embeddingRetriever.retrieve(query); ListContent keywordResults keywordRetriever.retrieve(query); return Stream.concat(embeddingResults.stream(), keywordResults.stream()) .distinct() .limit(5) .collect(Collectors.toList()); } }这种组合模式让 LangChain4j 天然支持Hybrid Search向量关键词而无需等待框架升级。某教育 SaaS 项目中我们用HybridRetriever解决了“学生问‘Java 泛型擦除’但知识库中写的是‘type erasure’”的问题——关键词检索补足了 embedding 对术语缩写的弱覆盖。3.4ChatMemory对话状态的显式管理ChatMemory接口定义了add(Message)、messages()、clear()三个方法。它不负责序列化只管内存中的ListMessage。这带来极高的可控性InMemoryChatMemory适合单机调试RedisChatMemory支持分布式会话需自行实现Message的 Redis 序列化NoOpChatMemory禁用记忆每次都是新对话关键技巧ChatMemory的messages()返回的是完整历史但AiServices默认只传最后n条。你可以通过ChatOptions控制AiServices aiServices AiServices.builder() .chatLanguageModel(chatModel) .chatMemory(chatMemory) .chatOptions(ChatOptions.builder() .maxTokens(2048) // 限制总 token 数 .maxHistoryMessages(10) // 只传最近10条 .build()) .build();提示maxHistoryMessages不是“保留最近10条”而是“从历史中取最后10条传给模型”。如果历史有 100 条maxHistoryMessages10会丢弃前 90 条。某客服机器人项目中我们发现用户常引用 5 轮前的对话内容于是将maxHistoryMessages从5提到15配合TokenCountEstimator动态截断解决了“上下文丢失”投诉。3.5PromptTemplate模板引擎的极简主义PromptTemplate只做一件事字符串替换。它不解析语法树不支持条件判断不递归嵌套。Parameters.of(a, x, b, y)替换{{a}} and {{b}}→x and y。这种“简陋”恰恰是优势可预测你永远知道渲染结果不会因模板引擎 bug 导致 prompt 错乱可测试promptTemplate.apply(params)返回String可直接 assertEquals可审计日志中打印的renderedPrompt就是最终发给模型的内容无隐藏逻辑。// ✅ 安全所有变量显式传入 PromptTemplate pt PromptTemplate.from(角色{{role}}\n任务{{task}}\n输入{{input}}); String prompt pt.apply(Parameters.of( role, Java 架构师, task, 分析代码性能瓶颈, input, ListInteger list new ArrayList(); for(int i0;i1000000;i) list.add(i); )); // ❌ 危险依赖隐式上下文LangChain4j 不支持 // PromptTemplate.from(请基于上下文回答{context}) // {context} 会被忽略必须用 {{context}}4. 生产环境避坑指南从本地 Demo 到高可用服务的 7 个生死线本地跑通 Demo 和生产上线中间隔着一堵叫“稳定性”的墙。LangChain4j 本身很轻量但 LLM 服务的不可靠性会放大所有设计缺陷。以下是我在多个项目中总结的 7 个必踩然后爬起来的坑。4.1 坑一Ollama 的并发连接数陷阱Ollama 默认最大并发连接数是 16。当你的 Web 服务 QPS 16后续请求会阻塞在HttpClient连接池直到超时。现象是前 16 个请求正常第 17 个开始 504 Gateway Timeout。解决方案启动 Ollama 时指定OLLAMA_NUM_PARALLEL32根据 CPU 核数调整Java 侧配置OllamaChatModel的maxConnectionsPerRouteOllamaChatModel model OllamaChatModel.builder() .baseUrl(http://ollama:11434) .modelName(qwen2:1.5b) .maxConnectionsPerRoute(32) // 匹配 OLLAMA_NUM_PARALLEL .maxConnections(64) .build();实测数据某在线教育平台将OLLAMA_NUM_PARALLEL从 16 提到 64QPS 从 18 提升至 52平均延迟下降 63%。但注意过高会导致 Ollama OOM需监控docker stats ollama的内存占用。4.2 坑二Embedding 向量维度不匹配的静默失败InMemoryEmbeddingStore构造时指定dimension768但如果你用bge-m31024 维存入findRelevant()会抛ArrayIndexOutOfBoundsException且错误堆栈指向Arrays.copyOf完全看不出是维度问题。根因InMemoryEmbeddingStore内部用float[][]存向量findRelevant()计算余弦相似度时v1.length和v2.length不等。解决方案在embeddingStore.add()前强制校验维度public class SafeEmbeddingStoreT implements EmbeddingStoreT { private final EmbeddingStoreT delegate; private final int expectedDimension; public SafeEmbeddingStore(EmbeddingStoreT delegate, int expectedDimension) { this.delegate delegate; this.expectedDimension expectedDimension; } Override public void add(Embedding embedding, T embedded) { if (embedding.vector().length ! expectedDimension) { throw new IllegalArgumentException( String.format(Embedding dimension mismatch: expected %d, got %d, expectedDimension, embedding.vector().length)); } delegate.add(embedding, embedded); } // 其他方法委托... }4.3 坑三Prompt 渲染时的空指针与特殊字符PromptTemplate.apply()传入null值或{{context}}中包含{、}、$等模板引擎保留字符会导致IllegalArgumentException。解决方案永远用Objects.toString(value, )包装参数对context字符串做 HTML 实体编码防 XSS也防模板解析String safeContext StringEscapeUtils.escapeHtml4(context); Parameters params Parameters.of(context, safeContext, question, question); String prompt promptTemplate.apply(params);4.4 坑四ChatMemory 的线程安全假象InMemoryChatMemory是线程不安全的如果你在 Spring MVC 的RestController中直接Autowired一个单例InMemoryChatMemory多个请求会共享同一份ListMessage导致对话串扰。解决方案每次请求新建InMemoryChatMemory轻量无状态或用ThreadLocalChatMemory封装Component public class ThreadLocalChatMemory { private final ThreadLocalChatMemory memoryHolder ThreadLocal.withInitial( () - InMemoryChatMemory.builder().build() ); public ChatMemory get() { return memoryHolder.get(); } public void clear() { memoryHolder.remove(); } }4.5 坑五Token 计数不准引发的截断灾难LangChain4j 的TokenCountEstimator默认按字符数估算String.length()但 LLM 实际按 subword token 计数。qwen2的 tokenizer 和gpt-4差异极大用字符数估算会导致 prompt 被意外截断。解决方案对关键模型集成官方 tokenizerqwen2用transformers的Qwen2Tokenizer需 Python 服务Ollama调用/api/tokenize接口实验性更务实的做法预留 20% buffermaxTokens 2048 * 0.8 1638。4.6 坑六HTTP 客户端超时配置的层级混乱OllamaChatModel有timeout()OllamaEmbeddingModel也有timeout()但底层HttpClient还有connectionTimeout、readTimeout、writeTimeout。三者不一致会导致诡异超时。黄金配置以 30 秒总超时为例层级参数建议值说明OllamaXxxModeltimeout(30, SECONDS)30s总超时覆盖所有子操作HttpClientconnectionTimeout(5, SECONDS)5s建连超时必须最短HttpClientreadTimeout(25, SECONDS)25s读响应超时 总超时 - 建连超时4.7 坑七日志爆炸与敏感信息泄露logRequests(true)会打印完整 prompt其中可能含用户 PII手机号、身份证号。生产环境必须关闭或脱敏OllamaChatModel model OllamaChatModel.builder() .baseUrl(http://prod-ollama:11434) .modelName(qwen2:1.5b) .logRequests(false) // 生产必须 false .logResponses(false) .build(); // 但保留关键指标日志 MeterRegistry registry new SimpleMeterRegistry(); new LangChain4jMetrics(registry).bindTo(registry); // 自动上报llm.request.duration、llm.response.tokens 等5. 进阶实战用 LangChain4j 实现一个“Java 代码审查助手”前面讲的都是基础能力现在我们整合所有知识点做一个真实场景的 Demo自动审查 Java 代码指出潜在 Bug 和优化点。这个需求在某金融科技公司的 Code Review 流程中落地将人工 review 时间缩短 40%。5.1 需求拆解与架构设计目标输入一段 Java 代码返回 JSON 格式的审查报告含issues数组每项有lineNumber、severityHIGH/MEDIUM/LOW、message、suggestion。难点不在模型而在如何让 LLM 理解代码上下文。我们采用三段式 PromptSystem Message定义角色和规则固定Context提供 JDK 版本、常用框架Spring Boot 3.x、公司编码规范如“禁止使用new Date()”User Message待审查的代码片段 行号标注。LangChain4j 的AiServices天然支持SystemMessage、UserMessage注解但我们要动态注入 Context所以不用注解改用手动构造ListChatMessage。5.2 核心代码实现public class JavaCodeReviewer { private final ChatLanguageModel chatModel; private final String codingStandard; // 公司编码规范文本 public JavaCodeReviewer(ChatLanguageModel chatModel, String codingStandard) { this.chatModel chatModel; this.codingStandard codingStandard; } public ReviewReport review(String javaCode, int maxIssues) { // 1. 构建 System Message固定 String systemPrompt 你是一个资深 Java 架构师专注于代码质量和安全。 请严格按以下 JSON Schema 输出审查报告不要任何额外文字 { issues: [ { lineNumber: 1

相关新闻

JPEG修复工具源码解析:损坏类型、诊断与批量修复实战

JPEG修复工具源码解析:损坏类型、诊断与批量修复实战

简介:JPEG修复工具介绍代码包是一份适合图像处理工作者与软件开发者参考的源码资源,重点解决 JPEG 文件损坏、RAW 照片丢失恢复等常见问题。其中 JPEG-Repair 组件用于处理损坏的 JPEG 标头、无效标记以及坏扇区引起的读取异常,JpegDigger 组…

2026/10/11 4:57:28 阅读更多 →
Seata 四种事务模式详解:从 XA、AT、TCC 到 Saga

Seata 四种事务模式详解:从 XA、AT、TCC 到 Saga

背景知识分布式事务模式没有唯一标准分类,但按一致性强度和实现机制,通常可以归为四大类:强一致协调型:2PC、3PC、XA业务补偿型:TCC、Saga、AT(本地事务改进 2PC)消息最终一致型:本地…

2026/10/11 4:57:28 阅读更多 →
如何筛选西安物业洗地机厂家现货?自邦源头工厂采购避坑建议

如何筛选西安物业洗地机厂家现货?自邦源头工厂采购避坑建议

西安物业洗地机厂家现货筛选指南:自邦等品牌采购避坑与决策参考在寻找西安物业洗地机厂家现货的过程中,许多物业管理负责人和保洁承包商常面临信息不对称、参数复杂以及售后响应不确定等挑战。本文并非基于商业排名的官方推荐,而是结合公开的…

2026/10/11 4:57:28 阅读更多 →

最新新闻

实测不掺水!音频快剪神器深度测评,普通人剪辑效率提升80%

实测不掺水!音频快剪神器深度测评,普通人剪辑效率提升80%

做自媒体、剪短视频、做配音和播客的小伙伴,大概率都被音频剪辑折磨过:电脑专业软件操作繁琐、学习成本高,手机免费工具功能残缺,要么剪完音质翻车,要么处理速度巨慢,稍微复杂一点的人声分离、降噪就完全ho…

2026/10/11 10:54:26 阅读更多 →
DeepSeek本地部署实战:基于Ollama的模型运行与WebUI集成指南

DeepSeek本地部署实战:基于Ollama的模型运行与WebUI集成指南

简介:这是一份围绕DeepSeek-R1本地部署的实战型技术文档,面向机器学习与AI应用开发者,适合已掌握基础命令行与容器概念的工程师、研究人员在Windows、macOS或Linux环境下快速搭建推理环境。资源包共1个文件,以docx格式呈现&#x…

2026/10/11 10:54:26 阅读更多 →
Vector 软件授权激活全攻略:在线与离线激活避坑指南

Vector 软件授权激活全攻略:在线与离线激活避坑指南

简介:这份PDF文档面向使用Vector系列工具(如CANoe、CANape、vFlash、CANalyzer、vTESTstudio、DYNA4等)的工程师与测试人员,聚焦软件安装与License激活这一常见入门难题,提供从环境准备到授权完成的完整指引。资源包内…

2026/10/11 10:54:26 阅读更多 →
TeX Live中文字体生成:CJK+GBK字体文件全解析与避坑指南

TeX Live中文字体生成:CJK+GBK字体文件全解析与避坑指南

简介:面向Texlive中文排版的使用者,CJKGBK中字体生成器是一套专门解决LaTeX中文字体缺失问题的实用工具包。针对CJK宏包安装后仍无法显示GBK汉字这一常见痛点,它基于GBK编码标准,打包了宋体、黑体、仿宋、楷书等多款字体的生成与配…

2026/10/11 10:54:26 阅读更多 →
从能跑到无可挑剔:代码质量提升的六个维度与自检清单

从能跑到无可挑剔:代码质量提升的六个维度与自检清单

1. 一个词引发的执念:为什么我要死磕“impeccable”第一次在代码评审里被人甩了一句“this is not impeccable”,我盯着屏幕愣了半分钟。不是生气,是突然意识到——我们天天把“能用”“跑通”“没报错”当终点,但真正拉开工程师差…

2026/10/11 10:54:26 阅读更多 →
AngelSlim推理加速双引擎:SpecExit提前退出与D-Cut剪枝原理及调优指南

AngelSlim推理加速双引擎:SpecExit提前退出与D-Cut剪枝原理及调优指南

人工智能大模型模型压缩模型量化模型蒸馏模型优化 【免费下载链接】AngelSlim Model compression toolkit engineered for enhanced usability, comprehensiveness, and efficiency. 项目地址: https://gitcode.com/gh_mirrors/an/AngelSlim 点击查看 免费下载 Ang…

2026/10/11 10:53:25 阅读更多 →

日新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/11 0:00:27 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/11 0:00:27 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/11 0:00:27 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/11 0:00:27 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/11 0:00:27 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/11 0:00:27 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 10:45:37 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 21:32:20 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 10:38:42 阅读更多 →