news 2026/10/26 5:54:09

LangChain4j实战:Java后端快速接入大模型对话与RAG应用

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
LangChain4j实战:Java后端快速接入大模型对话与RAG应用

我一直有个观点:Java后端想接大模型能力,不应该非得先去学一遍Python。LangChain4j 就是为 JVM 生态准备的 LLM 工程框架,我用它在 Spring Boot 项目里做过对话助手、工具调用、私有文档问答,踩了不少坑,也沉淀了一些能直接抄作业的套路。这篇教程不会逐行翻译官方文档,而是从零开始,带着你跑通第一个对话、理解核心抽象、做完一个带工具的 AI 助手和一个 RAG 文档问答,最后把新手最容易踩的坑一次性讲清楚。

1. 为什么Java开发者不等Python教程,直接用LangChain4j

1.1 LangChain4j到底帮你省掉了什么

先说结论:如果你只想调用一个 ChatCompletion 接口,直接用 HttpClient 写几十行代码就够了,不需要任何框架。但你一旦要做多轮对话、工具调用、文档检索、按用户隔离会话历史,自己写的代码很快就会失控。

LangChain4j 帮你做的,是把下面这些脏活封装成了几组稳定的接口:

  • 平台差异:OpenAI、Ollama、通义千问、Azure OpenAI 等不同模型服务,在 LangChain4j 里统一成ChatLanguageModel和StreamingChatLanguageModel两个入口。
  • 对话历史:ChatMemory负责保存和裁剪消息,前端不用每次把全量历史拼进 Prompt。
  • 工具调用:@Tool注解标注一个 Java 方法,模型就能在对话中自动决定调用它,参数由模型生成并绑定。
  • 文档检索:文本切分、向量化、相似度搜索,组合成一个ContentRetriever,让模型回答私有知识库内容。

我在早期用原生 API 做过一个客服机器人,最痛的不是模型响应慢,而是我得自己维护消息队列、自己处理工具调用的多轮循环、自己区分每个用户的历史。这些代码放到 LangChain4j 里,基本就是配置项。

1.2 和Java生态天生的亲和性

LangChain4j 的设计风格很对 Java 老手的胃口。它大量使用 Builder 模式、接口默认方法、类型安全参数,没有 Python 那种动态返回值带来的惊喜感。配合 Spring Boot,你可以直接在主类里注册ChatLanguageModelBean,业务代码里注入即可,不需要自己写工厂类。

它的模块化也是 Maven 思维。核心模块langchain4j只包含抽象接口,具体模型通过langchain4j-open-ai、langchain4j-ollama等扩展模块接入。这意味着你的业务代码可以只依赖抽象层,哪天换模型服务商,改动基本控制在启动配置里。

1.3 认清定位:它不是万能框架

我见过有人把 LangChain4j 当成 Agent 万能容器,什么复杂流程都想往里塞。实际上它的长处在"标准化交互流程",而不是"编排复杂业务状态机"。如果你的场景涉及多步骤人工审批、状态流转、事务回滚,老实用代码控制流程,别让模型自由发挥。框架会帮你连接模型和工具,但不会替你想清楚业务边界。

适合用它快速做的场景:智能客服、知识库问答、代码辅助解释、报表生成、翻译与润色、模拟对话测试。不适合的场景:需要严格数值计算、强事务一致性、多系统复杂依赖编排。我的经验是:LLM 相关代码只占总服务的一小部分,框架选型首要标准是"能不能让这一小部分保持简单",LangChain4j 在这点上做得不错。

2. 环境准备:最小依赖跑通第一个对话

2.1 依赖怎么加

创建一个普通的 Maven 项目,Java 版本建议 17 或更高。核心依赖如下:

<dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j</artifactId> <version>1.0.0-beta1</version> </dependency>

然后根据你选的模型服务商,再加一个模块。我这里演示两个最常用的:

<!-- 使用 OpenAI 兼容接口 --> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-open-ai</artifactId> <version>1.0.0-beta1</version> </dependency> <!-- 使用本地 Ollama --> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-ollama</artifactId> <version>1.0.0-beta1</version> </dependency>

提示:代码里的版本号只是示例。LangChain4j 迭代很快,建议到 Maven 中央仓库查看最新版本。接口若有个别调整,以官方 release notes 为准。

如果你后续要做文档问答,还需要加langchain4j-embeddings和langchain4j-easy-rag这类模块,我在第 5 节详细说。

2.2 我的建议:先用本地模型跑通链路

我第一次上手时直接配 OpenAI API Key,结果每次调试都要盯着账户余额,心疼得不行。后来换成 Ollama 跑本地模型,调试链路完全免费,也更容易理解框架的基本流程。

Ollama 的安装很简单:去官网下载对应系统安装包,装完执行下面的命令拉取模型。

ollama pull qwen2.5:7b

这个模型在普通开发机上能跑,生成速度也够用。你也可以换成llama3.1:8b或者gemma2:9b,看自己机器配置。选模型的原则:先跑通,再谈效果。

2.3 第一个同步调用

这是最简版本的代码,调用本地模型:

import dev.langchain4j.model.chat.ChatLanguageModel; import dev.langchain4j.model.ollama.OllamaChatModel; public class FirstDemo { public static void main(String[] args) { ChatLanguageModel model = OllamaChatModel.builder() .baseUrl("http://localhost:11434") .modelName("qwen2.5:7b") .temperature(0.7) .build(); String answer = model.chat("请用一句话介绍Java的垃圾回收机制"); System.out.println(answer); } }

如果你要用 OpenAI,代码结构几乎一样:

import dev.langchain4j.model.chat.ChatLanguageModel; import dev.langchain4j.model.openai.OpenAiChatModel; ChatLanguageModel model = OpenAiChatModel.builder() .apiKey(System.getenv("OPENAI_API_KEY")) .modelName("gpt-4o-mini") .build();

这里有个细节很多人忽略:model.chat()返回的是完整字符串,适合一次性请求;但聊天应用通常需要流式输出,所以同时要会StreamingChatLanguageModel。

2.4 流式输出:让页面真的"一个字一个字蹦出来"

流式输出是聊天类产品的刚需。用同步接口时,用户要等十几秒才看到完整回答,体验非常差。LangChain4j 的流式接口长这样:

import dev.langchain4j.model.StreamingChatLanguageModel; import dev.langchain4j.model.openai.OpenAiStreamingChatModel; import dev.langchain4j.model.output.Response; import reactor.core.publisher.Flux; StreamingChatLanguageModel model = OpenAiStreamingChatModel.builder() .apiKey(System.getenv("OPENAI_API_KEY")) .modelName("gpt-4o-mini") .build(); model.chat("讲一个程序员的冷笑话", new StreamingChatResponseHandler() { @Override public void onPartialResponse(String token) { System.out.print(token); System.out.flush(); } @Override public void onCompleteResponse(Response<AiMessage> response) { System.out.println(); } @Override public void onError(Throwable error) { error.printStackTrace(); } });

这里StreamingChatResponseHandler是一个回调接口,onPartialResponse会持续收到 token,onCompleteResponse表示流结束。和 Reactor 集成时,LangChain4j 也有对应的 Flux 封装,能在 WebFlux 链路里直接返回流。新手最容易犯的错是在回调里做耗时操作,把流式变成了"攒完再返回",那样就失去了流式的意义。

2.5 配置管理经验

第一周我犯过一个低级错误:把 API Key 直接写在代码里提交到了 Git 仓库,后来紧急重置密钥。建议一开始就养成这些习惯:

  • API Key 从环境变量或配置中心读取,代码里只出现System.getenv("OPENAI_API_KEY")。
  • 模型名称、温度、最大 Token 等参数放到application.yml,方便不同环境切换。
  • 生产环境务必设置超时时间,否则默认行为可能让你的服务线程长期挂起。
OpenAiChatModel.builder() .apiKey(System.getenv("OPENAI_API_KEY")) .modelName("gpt-4o-mini") .chatTimeout(Duration.ofSeconds(30)) .build();

3. 理解LangChain4j的核心抽象:Message、ChatMemory与ChatLanguageModel

3.1 一次对话在框架里是怎么走的

LangChain4j 把大模型交互抽象成了几条消息组成的列表:

  • SystemMessage:系统提示词,告诉模型它的角色和约束。
  • UserMessage:用户输入。
  • AiMessage:模型生成的回复。
  • ToolMessage:工具执行后的结果。

多轮对话的底层逻辑,就是把这几类消息按时间顺序追加进列表,整体发给模型。LangChain4j 的ChatLanguageModel.chat()会接受一个List<ChatMessage>,自动转换成对应模型服务的请求体。

List<ChatMessage> messages = new ArrayList<>(); messages.add(new SystemMessage("你是一个只回答技术问题的助手,回答要简洁。")); messages.add(new UserMessage("什么是JVM内存模型?")); Response<AiMessage> response = model.chat(messages); System.out.println(response.content().text());

这个例子展示了底层结构,但实际业务里你不应该手动拼这个列表。原因很简单:历史消息怎么保存?怎么裁剪?按什么维度隔离?这些问题应该交给ChatMemory。

3.2 ChatMemory:别把多轮对话做成纯拼接

没有记忆的对话,就是每次把历史全部塞回 Prompt。看似简单,但有两个致命的后果:

  1. Token 爆炸。聊到几十轮后,历史越长,单次请求越贵,响应越慢。
  2. 早期噪音干扰。用户中途说的一句"不对,刚才那个方案改成 X",如果没有结构化保存,模型分不清现在到底该执行哪个。

LangChain4j 提供了开箱即用的MessageWindowChatMemory,按消息条数保留历史:

import dev.langchain4j.memory.ChatMemory; import dev.langchain4j.memory.chat.MessageWindowChatMemory; ChatMemory chatMemory = MessageWindowChatMemory.withMaxMessages(20);

这行代码的意思:最多保留 20 条消息,超出后自动丢弃最旧的。20 条大约对应 10 轮一问一答,适合轻量场景。如果使用大模型且单条回复很长,建议同时关注 Token 数而不是只看条数。

3.3 多用户场景必须做会话隔离

一个线上服务不可能只有一个用户。如果所有用户共享同一个ChatMemory,A 用户的问题会被 B 用户的上下文污染,输出结果会非常诡异。LangChain4j 的解决方案是ChatMemoryProvider,配合@MemoryId注解实现按会话隔离:

interface Assistant { String chat(@MemoryId String memoryId, @UserMessage String message); } ChatMemoryProvider memoryProvider = memoryId -> MessageWindowChatMemory.builder() .id(memoryId) .maxMessages(20) .build(); Assistant assistant = AiServices.builder(Assistant.class) .chatLanguageModel(model) .chatMemoryProvider(memoryProvider) .build(); // 两个用户互不干扰 String user1Answer = assistant.chat("user-001", "你好,我是A"); String user2Answer = assistant.chat("user-002", "你好,我是B");

@MemoryId可以接业务主键,比如用户 ID、会话 ID。生产环境更推荐使用持久化的ChatMemoryStore,把历史存到 Redis 或数据库,不然服务重启后用户的上下文就全丢了。

3.4 为什么建议用AiServices而不是手写循环

直接用model.chat(messages)做单轮请求没问题,但多做几轮后你会发现要自己维护循环、异常重试、消息转换。AiServices是 LangChain4j 对完整交互链路的封装,它的核心价值是用一个接口方法描述业务能力,框架负责把对话记忆、工具调用、检索增强组合起来。

interface Assistant { String chat(@MemoryId String memoryId, @UserMessage String message); } Assistant assistant = AiServices.builder(Assistant.class) .chatLanguageModel(model) .chatMemoryProvider(memoryProvider) .build();

之后的章节里,工具调用和 RAG 也都是往AiServices.builder()上加配置。理解这个消息流转模型,再看后面就顺了。

4. 实战:给AI助手加一个查询天气的工具

4.1 模型不会查天气,所以需要工具调用

大模型的知识截止到训练数据,它查不了实时天气、查不了订单状态、调不了内部接口。解决办法是"工具调用":模型生成一个结构化的函数调用请求,你的程序执行真实逻辑,再把结果喂回给模型生成最终回答。

LangChain4j 把底层这套协议封装成了@Tool,你只需要写一个普通 Java 方法。

4.2 用@Tool定义一个查询天气的工具

import dev.langchain4j.agent.tool.Tool; public class WeatherTools { @Tool("根据城市名称查询当前天气") public String getCurrentWeather(String city) { // 这里替换为真实天气接口,比如和风天气、高德天气 // 返回一个简短的、结构化的字符串即可 return "城市=" + city + ", 天气=晴, 温度=28℃, 风级=3级"; } }

这里有几个值得注意的经验点:

  • 工具描述@Tool("根据城市名称查询当前天气")一定要写清楚用途和参数含义。模型是靠这段描述决定何时调用、怎么填参数的。描述写得模糊,模型就会乱调用。
  • 方法参数最好用 String,如果要用多个参数,尽量使用基本类型。复杂对象不是不能传,但新手容易在参数绑定上浪费大量时间。
  • 返回值不要写长篇文章,也不要用 Java 对象的默认toString()。返回简短、键值对风格的文本,模型更容易把答案组织得准确。

4.3 把工具接入AiServices并验证

interface Assistant { String chat(String message); } Assistant assistant = AiServices.builder(Assistant.class) .chatLanguageModel(model) .tools(new WeatherTools()) .build(); String answer = assistant.chat("帮我查一下杭州现在天气怎么样?"); System.out.println(answer);

执行后,模型的调用链路大概是:

  1. 用户提问。
  2. 模型判断需要查天气,在响应里附带一个getCurrentWeather(city="杭州")的调用请求。
  3. LangChain4j 自动执行该方法,拿到返回字符串。
  4. 结果作为ToolMessage回传给模型。
  5. 模型基于工具结果生成最终回答:"杭州目前晴,温度 28℃..."

你不需要自己写循环,AiServices已经处理了多轮工具调用。如果你的工具流程需要多个步骤,比如先查城市 ID 再查天气,LangChain4j 会自动继续让模型决定是否调用第二个工具,只要它判断还需要调用。

4.4 避坑:工具返回格式与循环次数

工具调用最常见的失败场景是什么?不是代码报错,而是模型"读不懂"工具结果。我踩过的坑包括:

  • 返回了 JSON 数组结构,模型无法正确解析数组内容,于是回答"我暂时无法获取天气数据"。
  • 返回了Optional空值,模型以为工具执行失败,开始编造数据。
  • 工具方法抛了异常,AiServices默认直接把异常信息回传给模型,模型可能一本正经地分析异常信息而不是继续执行。

我的建议是:工具方法内部做好异常兜底,失败时也返回一个明确的文本,比如"查询失败,原因=超时",不要让异常穿透到模型层。同时,如果模型连续多次工具调用得不到结果,要在业务层设置调用次数上限,避免产生失控的 API 费用。

5. 文档问答落地:给私有知识库做RAG

5.1 为什么RAG现在是性价比最高的接入方式

让模型回答私有文档内容,有两条路:微调和 RAG。微调需要准备标注数据集、训练显卡、评估迭代,对大多数项目来说成本太高。RAG 的做法是:把文档切分成小段,向量化后存进向量库;提问时先检索相关片段,把片段作为上下文拼进 Prompt,让模型基于上下文回答。

LangChain4j 对 RAG 的支持很完整,下面我用一个最小可运行的例子说明全流程。

5.2 从文本文件到向量库的流水线

先把一段文档加载进来,切分、向量化、存入内存向量库:

import dev.langchain4j.data.document.Document; import dev.langchain4j.data.document.loader.FileSystemDocumentLoader; import dev.langchain4j.data.document.splitter.DocumentSplitters; import dev.langchain4j.data.segment.TextSegment; import dev.langchain4j.data.embedding.Embedding; import dev.langchain4j.model.embedding.EmbeddingModel; import dev.langchain4j.model.openai.OpenAiEmbeddingModel; import dev.langchain4j.store.embedding.inmemory.InMemoryEmbeddingStore; Document document = FileSystemDocumentLoader.loadDocument(Paths.get("/path/to/faq.txt")); List<TextSegment> segments = DocumentSplitters.recursive(500, 80).split(document); EmbeddingModel embeddingModel = OpenAiEmbeddingModel.builder() .apiKey(System.getenv("OPENAI_API_KEY")) .modelName("text-embedding-3-small") .build(); InMemoryEmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>(); for (TextSegment segment : segments) { Embedding embedding = embeddingModel.embed(segment.text()).content(); embeddingStore.add(embedding, segment); }

这里有两个参数值得琢磨:

  • recursive(500, 80)表示每个片段大约 500 个字符,前后重叠 80 个字符。重叠是为了避免一句话被拦腰切断,导致检索漏掉关键信息。具体大小要看文档类型,中文文档往往 300-500 字符更合适,章节性强的文档可以用 800-1000。
  • 向量化模型的选择直接影响检索效果。text-embedding-3-small便宜且通用,但如果你完全不想调用外部 API,也可以本地跑一个 embedding 模型。LangChain4j 有 Ollama 的 embedding 模块,模型可以用nomic-embed-text。

5.3 检索参数怎么调最省心

检索不是"查出来就完事",参数没调好会出现两种情况:相关片段没被召回,模型编答案兜底;或者召回了一堆无关片段,模型被噪音干扰。我常用的几个参数和初始值如下:

参数我的初始值说明
minScore0.6相似度低于该值的片段直接丢弃。太高则召回太少,太低则噪音太多
maxResults3最终拼进上下文的最大片段数。片段 500 字时,3 段约 1500 字,足够日常问答
maxTokens2000检索上下文的总规模上限,防止长问答把模型输入撑爆
EmbeddingSearchContentRetriever retriever = EmbeddingSearchContentRetriever.builder() .embeddingModel(embeddingModel) .embeddingStore(embeddingStore) .minScore(0.6) .maxResults(3) .build(); RetrievalAugmentor augmentor = RetrievalAugmentor.builder() .contentRetriever(retriever) .build();

5.4 把检索器挂到AiServices

有了RetrievalAugmentor,就可以直接挂到之前的AiServices上:

interface Assistant { String chat(String message); } Assistant assistant = AiServices.builder(Assistant.class) .chatLanguageModel(model) .retrievalAugmentor(augmentor) .build(); String answer = assistant.chat("文档里有没有提到退款流程?");

这条链路的大致行为:用户提问 -> 框架用用户问题去向量库检索相关片段 -> 片段自动拼接到 Prompt -> 模型参考上下文作答。你不需要自己拼 Prompt,也不需要在前端传文档片段。

如果你不想手工写加载和切分的代码,LangChain4j 的 easy-rag 模块能帮你在几分钟内建起一个默认流程,适合快速验证。但对生产项目,我建议还是自己控制切分参数、向量库选型和召回阈值,默认值未必适配你的文档结构。

5.5 向量库选型:从内存到生产

上面示例用的InMemoryEmbeddingStore只适合原型验证,重启数据就没了。生产环境我尝试过的选择:

  • Redis:如果公司已有 Redis,用RedisEmbeddingStore最省事,缺点是需要自己预处理向量索引参数。
  • PostgreSQL + pgvector:适合已经有 PG 的场景,LangChain4j 也有对应模块,文档查询和业务数据能放在同一个库。
  • Milvus / Qdrant:专门的向量数据库,数据量大时更合适,但要引入新的基础设施。

选型原则就一条:别为了 RAG 单独引入重组件,除非你的文档规模和并发查询量已经撑爆了现有方案。

6. 新手最容易踩的坑清单

6.1 Token爆炸:会话越聊越笨还越来越贵

这是我在生产环境遇到的第一个隐形杀手。一开始我用了很大的maxMessages,心想历史越多对话质量越高。结果用户聊了二十几轮后,单次请求的输入 Token 变得非常大,响应延迟翻倍,账单数字也让人肉疼。

Token 爆炸的真正危害不只是钱,还有模型注意力稀释。几十轮闲聊之后,真正重要的业务信息被埋在了大量历史中,模型反而更容易漏掉关键约束。

对策:先用MessageWindowChatMemory.withMaxMessages(20)限制消息条数;如果单条消息内容很长,还要用 Tokenizer 统计真实 Token 数。LangChain4j 提供OpenAiTokenizer可以估算文本 Token:

Tokenizer tokenizer = new OpenAiTokenizer("gpt-4o-mini"); int count = tokenizer.estimateTokenCountInText("你好,世界");

复杂的业务场景,可以考虑对早期对话做摘要压缩,而不是全量保留。先把基础的限制策略做好,比一味追求"无限记忆"靠谱得多。

6.2 会话串台:ChatMemory被所有人共享

这个坑我是在联调时发现的:A 用户提到"我是 VIP",B 用户的回答里居然出现了"尊敬的 VIP 客户"。查了半天,发现整个 Spring 容器里只有一个ChatMemory实例,所有用户共用一份上下文。

解决方式是前面提过的ChatMemoryProvider+@MemoryId。我再强调一次:生产环境千万不要在AiServices上挂一个全局ChatMemory实例,必须按用户或会话维度隔离。如果服务是多实例部署,还要使用 Redis 等共享存储,不能让会话上下文散落在单个实例内存里。

6.3 网络超时、重试与熔断

外部模型服务的稳定性不是你能控制的。我遇到过两种情况:

  1. 网络抖动导致请求超时,默认行为是抛异常,用户看到一个 500。
  2. 模型服务限流,返回 429,重试不及时就白等了。

LangChain4j 的模型 Builder 里通常有超时和重试参数:

OpenAiChatModel.builder() .apiKey(System.getenv("OPENAI_API_KEY")) .chatTimeout(Duration.ofSeconds(30)) .maxRetries(3) .logRequests(true) .logResponses(true) .build();

maxRetries会对瞬时错误自动重试,但这不代表你的服务就不需要兜底。我最推荐的方式:把模型调用封装成独立的 Service 类,外面加一层熔断逻辑,重试两三次仍失败时返回降级文案,比如"服务繁忙,请稍后再试",而不是直接把异常抛给用户。

6.4 输出幻觉和格式不稳定

不要相信模型每次都严格遵守你的输出格式要求。即使 Prompt 里写了"只返回 JSON 数组",它仍可能在某些上下文里多输出一段解释文字。我处理这个问题的几个手段优先级从高到低:

  • 在 SystemMessage 里给一个正好对应的格式示例。
  • 设置temperature到 0.2 以下,降低创造性输出。
  • 用 LangChain4j 的Response返回结构自行校验关键字段。
  • 绝对不要把模型的输出直接当作代码、SQL、Shell 命令去执行。模型输出只是数据,必须经过业务校验。

还有一个容易被忽略的点:做工具调用时,所有入参都要在 Java 侧重新校验。模型可能生成一个不合法的 city 参数,比如"hangzhou!!!",你的工具方法要对参数做清洗,不然真实接口调用就炸了。

7. 从Demo到生产:几条真正有用的经验

7.1 加日志与链路追踪

模型调用是外部依赖,出问题的时候你必须有据可查。我现在的标准做法是记录三类信息:

  • 入参:用户输入、会话 ID、模型名称。
  • 出参:完整回复文本、Token 消耗、响应耗时。
  • 异常:错误类型、重试次数、降级结果。

LangChain4j 有logRequests(logResponses)可以在模型服务层打印,但那是基础日志,建议自己再做一层业务日志。我习惯在调用前后打两个点,写清楚用户 ID 和会话 ID。排查问题时,这比看代码快得多。

7.2 成本估算与Token统计

线上项目必须算钱。单个请求的成本公式很简单:

请求费用 = 输入Token数 / 1000000 * 每百万输入价格 + 输出Token数 / 1000000 * 每百万输出价格

每次响应结束后,通过Response的tokenUsage()可以拿到精确的 Token 数据。我建议把这些数据上报到监控系统,按用户、按接口维度汇总。当某个用户日均调用成本异常升高时,你能够第一时间发现,而不是月底收到账单才反应过来。

另一个省钱技巧:把高频提问的固定答案做缓存,LLM 只处理真正需要生成的请求。我做过一个售后客服,合同解答类问题命中缓存后,整体模型调用成本直接降了一半。

7.3 我的下一步建议

如果你想继续深入,我建议按这个顺序做三件事:

  1. 把第 4 节和第 5 节的内容合并:做一个既能查文档、又能查天气、还能查订单的复合助手,熟悉AiServices同时挂tools和retrievalAugmentor时的表现。
  2. 把InMemoryEmbeddingStore换成 Redis 或 pgvector,让知识库数据在服务重启后还在。
  3. 尝试StreamingChatLanguageModel和工具调用同时开启,看流式输出在多轮工具调用下怎么组织 UI 展示。

我个人的体会是:LangChain4j 最大的价值不是"魔法般帮你调用模型",而是把 LLM 接入 Java 工程变成一件有条理的事。只要理解消息、记忆、工具、检索这几个核心概念,后面大部分问题都能通过组合配置解决。踩过的坑多了,你就会发现,AI 应用开发到最后,拼的还是工程基本功:日志要清楚、数据要隔离、成本要可控。框架只是让这条路好走一些,真正让你走远的永远是这些朴素的习惯。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/26 5:53:36

智石开PLM系统AI技术突破与应用实践

1. 项目背景与行业现状PLM&#xff08;产品生命周期管理&#xff09;系统作为制造业数字化转型的核心平台&#xff0c;正在经历从传统流程管理向智能决策支持的跃迁。根据Gartner最新调研&#xff0c;2023年全球PLM市场中AI技术渗透率已达47%&#xff0c;较前一年增长210%&…

作者头像 李华
网站建设 2026/10/26 5:53:29

Heimdall仪表盘部署与使用全指南

1. 项目概述&#xff1a;Heimdall 仪表盘的核心价值Heimdall 是一款开源的 Web 应用导航仪表盘&#xff0c;它能将你常用的服务、工具和网站集中在一个简洁的界面中。想象一下每天早上打开浏览器&#xff0c;所有需要访问的 Jenkins、Grafana、内部 Wiki、云服务控制台等常用链…

作者头像 李华
网站建设 2026/10/26 5:52:41

基于SpringBoot的中药知识平台开发实践

1. 项目背景与核心需求作为一名计算机专业的学生&#xff0c;毕业设计是检验四年学习成果的重要环节。我选择开发一个"基于SpringBoot的中药科普知识平台"&#xff0c;主要基于以下几点考虑&#xff1a;首先&#xff0c;中医药作为我国传统文化的重要组成部分&#x…

作者头像 李华
网站建设 2026/10/26 5:52:22

Final2x:开源深度学习图片无损放大工具解析

1. 项目概述&#xff1a;Final2x如何重新定义图片无损放大Final2x是一款基于深度学习的开源图片无损放大工具&#xff0c;它彻底改变了传统图像放大技术依赖在线服务的模式。我在处理一批老照片数字化项目时首次接触这个工具&#xff0c;当时需要将数百张300dpi的扫描件放大到印…

作者头像 李华
网站建设 2026/10/26 5:50:48

Vivado HLS实现可动态调相的DDS IP核

简介&#xff1a;本资源是一套基于Vivado HLS开发DDS&#xff08;直接数字频率合成&#xff09;IP核的完整工程实践包&#xff0c;面向FPGA初学者、数字信号处理学习者及嵌入式硬件开发者&#xff0c;解决传统Verilog手写DDS逻辑复杂、调试周期长的问题。压缩包共254个文件&…

作者头像 李华
网站建设 2026/10/26 5:50:10

RH2288VH2 IBMC/BIOS固件升级与风扇满速排查指南

简介&#xff1a;这份刷机升级包面向华为RH2288VH V2双路机架服务器的运维人员与机房管理员&#xff0c;聚焦IBMC管理模块配合下的BIOS固件更新&#xff0c;可用于修复硬件兼容性问题、优化运行表现并提升系统稳定性。压缩包共2个文件&#xff0c;主要由hpm固件镜像与xml版本信…

作者头像 李华