☰
Java开发者别再手写大模型接口了:LangChain4j+通义千问落地RAG流式问答
2026/10/12 17:52:25 网站建设 项目流程

1. 为什么手写大模型接口是在浪费生命

1.1 一个让我彻底放弃手写接口的下午

去年做企业知识库项目的时候,我干了一件现在想起来都觉得自己蠢的事。当时需求很简单:把公司内部几百份产品文档喂给大模型,做一个能回答产品问题的问答机器人。我第一反应是——这有什么难的,HTTP 请求发一发不就完了?

于是我花了整整三天时间,手写了一套调用大模型 API 的 Java 代码。封装 HTTP 客户端、处理 JSON 序列化、拼接 Prompt 模板、解析返回结果、处理各种异常……代码写完大概八百多行,跑起来确实能用。但接下来噩梦开始了:产品经理说“能不能让回答流式输出,一个字一个字蹦出来那种”,我改了两天;又说“能不能记住上下文,别每次都是金鱼记忆”,我又改了一天;再后来“能不能让它先查一下我们的文档再回答”,我直接崩溃了——这意味着我要自己实现向量检索、文档切分、相似度匹配、结果注入 Prompt 这一整套 RAG 流程。

那三天手写的八百行代码,最后全部删掉重来。因为 LangChain4j 用不到五十行就搞定了我上面说的所有需求,而且流式输出、上下文记忆、RAG 检索全是开箱即用。

这就是我想说的核心问题:90% 的 Java 开发者还在手写大模型接口,纯属浪费时间。不是说手写学不到东西,而是在业务交付场景下,你花三天手写的东西,别人用成熟框架三小时就能落地,而且比你稳定、比你功能全、比你更好维护。

1.2 这篇文章能帮你解决什么

如果你是一个 Java 开发者,想在自己的项目里接入大模型能力,但被各种 SDK、API、框架搞得头晕,那这篇内容就是写给你的。我会用LangChain4j + 通义千问这套组合,带你从零落地一个完整的 RAG 流式问答系统。

具体来说,读完你能拿到这些东西:

  • 一套可直接复用的 Maven 依赖配置,不用再纠结版本冲突
  • 通义千问在 LangChain4j 里的正确接入姿势,包括流式和非流式两种模式
  • RAG 完整链路:文档加载、切分、向量化、存储、检索、注入,每一步都有可运行的代码
  • 流式问答的 SSE 接口实现,前端能直接对接
  • 我踩过的坑和对应的解决方案,这些是官方文档里不会写的

适合的读者:有 Java 基础、用过 Spring Boot、听说过大模型但没实际接过 API 的开发者。不需要你懂机器学习,不需要你会 Python,只要你会写 Java 就能跟上。

1.3 先搞清楚 RAG 到底在干什么

在动手之前,我用一个生活化的类比把 RAG 讲清楚,不然后面代码你只能抄不能改。

想象你是一个客服新人,客户问“你们产品支持批量导出吗”。你有两种回答方式:

第一种(纯大模型):凭记忆回答。但你的记忆是训练时灌进去的,可能过时了,可能记错了,也可能根本不知道你们公司产品的细节。这就是大模型的“幻觉”问题。

第二种(RAG):你先去翻公司的产品手册,找到“批量导出”那一章,看完之后再回答客户。这样回答就有据可依,准确率高得多。

RAG 的全称是 Retrieval-Augmented Generation,翻译过来就是“检索增强生成”。核心流程就三步:

  1. 检索(Retrieval):把用户的问题拿去和知识库里的文档片段做相似度匹配,找出最相关的几段
  2. 增强(Augmented):把找到的文档片段拼接到 Prompt 里,作为“参考资料”一起发给大模型
  3. 生成(Generation):大模型基于参考资料生成回答

听起来简单,但工程落地时每一步都有坑。文档怎么切?切多大?向量怎么存?检索出来不相关怎么办?这些我后面会一个个讲。

2. 技术选型:为什么是 LangChain4j 加通义千问

2.1 LangChain4j 到底解决了什么问题

LangChain4j 是 LangChain 的 Java 版本,专门为 Java 生态设计。它的核心价值在于把大模型应用开发中的通用模式抽象成了 API。

我列一下手写接口和用 LangChain4j 的对比,你感受一下差距:

功能点手写实现LangChain4j
调用大模型自己封装 HTTP 客户端一行代码
流式输出自己处理 SSE 解析内置 StreamingChatLanguageModel
上下文记忆自己维护消息列表ChatMemory 开箱即用
文档切分自己写递归切分逻辑DocumentSplitters 内置多种策略
向量化自己调 Embedding APIEmbeddingModel 统一接口
向量存储自己写相似度计算EmbeddingStore 支持多种存储
RAG 检索自己拼 PromptRetrievalAugmentor 自动完成
多模型切换改代码换一个依赖和配置

这个对比不是夸张,是我实际项目里的真实感受。手写的时候,光是一个“上下文记忆”就要考虑消息怎么存、存多少条、超长了怎么截断、多轮对话怎么区分用户,这些细节能吃掉你大量时间。

LangChain4j 的另一个好处是模型无关。今天用通义千问,明天想换成别的模型,只需要换依赖和配置,业务代码基本不动。这对企业项目来说太重要了,谁也不想被某个模型绑死。

2.2 通义千问在 Java 生态里的优势

选通义千问有几个实际考虑:

第一,国内访问稳定。做企业项目,网络稳定性是硬指标。通义千问的 API 在国内访问延迟低,不需要额外处理网络问题。

第二,有免费的 Embedding 模型。RAG 需要把文档向量化,Embedding 调用量很大。通义千问的 text-embedding-v2 有免费额度,对中小项目来说能省不少成本。

第三,LangChain4j 官方支持。LangChain4j 有专门的langchain4j-community-dashscope模块,接入通义千问只需要加一个依赖,不用自己写适配层。

第四,流式输出体验好。通义千问的流式接口响应快,首字延迟低,做打字机效果很流畅。

2.3 整体架构设计

在动手写代码之前,先把架构理清楚。整个系统分四层:

第一层:接入层。Spring Boot 提供 REST 接口,前端通过 SSE 接收流式回答。

第二层:AI 服务层。LangChain4j 的 AiServices 接口,定义问答方法,框架自动生成实现。

第三层:RAG 层。包含文档加载器、切分器、Embedding 模型、向量存储、检索器,负责把知识库内容变成可检索的向量。

第四层:模型层。通义千问的 Chat 模型和 Embedding 模型,通过 LangChain4j 的适配器接入。

数据流向是这样的:用户提问 → 接入层接收 → AI 服务层调用 → RAG 层检索相关文档 → 拼接 Prompt → 模型层生成回答 → 流式返回给前端。

这个架构的好处是每层职责清晰,想换模型只动第四层,想换向量库只动第三层,业务代码不受影响。

3. 环境搭建与依赖配置

3.1 Maven 依赖的正确姿势

先上依赖配置,这是最容易踩坑的地方。LangChain4j 的模块很多,版本要对齐,不然会出现各种 NoSuchMethodError。

<properties> <langchain4j.version>0.35.0</langchain4j.version> <spring-boot.version>3.2.5</spring-boot.version> </properties> <dependencies> <!-- LangChain4j 核心 --> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j</artifactId> <version>${langchain4j.version}</version> </dependency> <!-- 通义千问适配 --> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-community-dashscope</artifactId> <version>${langchain4j.version}</version> </dependency> <!-- 文档解析:支持 PDF、Word、Excel --> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-document-parser-apache-tika</artifactId> <version>${langchain4j.version}</version> </dependency> <!-- 内存向量存储,生产环境可换 Redis 或 PgVector --> <dependency> <groupId>dev.langchain4j</groupId> <artifactId>langchain4j-embeddings</artifactId> <version>${langchain4j.version}</version> </dependency> <!-- Spring Boot Web,用于提供 SSE 接口 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies>

这里有几个关键点要说明:

版本对齐:LangChain4j 的所有模块必须用同一个版本号。我见过有人核心模块用 0.35.0,适配模块用 0.34.0,结果启动就报错。统一用${langchain4j.version}管理最省心。

dashscope 模块:这是通义千问的适配模块,注意它属于langchain4j-community系列,不是核心模块。社区模块的更新频率和核心模块不完全同步,选版本时要注意兼容性。

Tika 解析器:如果你的知识库有 PDF、Word 文档,这个依赖必加。它基于 Apache Tika,能解析几乎所有常见文档格式。如果只有纯文本,可以不加。

向量存储:开发阶段用内存存储就够了,重启数据丢失但调试方便。生产环境建议换 Redis 或 PostgreSQL 的 pgvector 扩展,这个后面会讲怎么切换。

3.2 通义千问 API Key 配置

去阿里云百炼平台开通通义千问服务,拿到 API Key。然后在application.yml里配置:

langchain4j: dashscope: api-key: ${DASHSCOPE_API_KEY} chat-model: qwen-plus embedding-model: text-embedding-v2

API Key 千万别硬编码在代码里,用环境变量注入。我见过有人把 Key 提交到 Git 仓库,结果被人刷了几千块钱的调用量。

模型选择上,qwen-plus是性价比最高的,日常问答够用。如果对回答质量要求极高,可以换qwen-max,但价格贵不少。qwen-turbo最便宜,适合对延迟敏感、问题简单的场景。

Embedding 模型用text-embedding-v2,它支持中文,维度是 1536。这个维度很重要,后面配置向量存储时要用到。

3.3 项目结构规划

我习惯按职责分包,这样后期维护清晰:

src/main/java/com/example/rag/ ├── config/ 配置类 │ ├── LangChainConfig.java │ └── RagConfig.java ├── service/ 业务服务 │ ├── AiService.java │ └── DocumentService.java ├── controller/ 接口层 │ └── ChatController.java ├── model/ 数据模型 │ ├── ChatRequest.java │ └── ChatResponse.java └── RagApplication.java

这个结构不是必须的,但建议至少把配置、服务、接口分开。我见过把所有代码塞一个类里的,后期改起来想死。

4. 核心实现:从模型接入到 RAG 链路

4.1 通义千问模型接入

先写配置类,把 Chat 模型和 Embedding 模型注册成 Spring Bean:

@Configuration public class LangChainConfig { @Value("${langchain4j.dashscope.api-key}") private String apiKey; @Bean public ChatLanguageModel chatLanguageModel() { return DashScopeChatModel.builder() .apiKey(apiKey) .modelName("qwen-plus") .temperature(0.7) .build(); } @Bean public StreamingChatLanguageModel streamingChatModel() { return DashScopeStreamingChatModel.builder() .apiKey(apiKey) .modelName("qwen-plus") .temperature(0.7) .build(); } @Bean public EmbeddingModel embeddingModel() { return DashScopeEmbeddingModel.builder() .apiKey(apiKey) .modelName("text-embedding-v2") .build(); } }

这里解释几个参数:

temperature:控制回答的随机性。0 最确定,1 最随机。做知识问答建议 0.3 到 0.7,太低回答死板,太高容易胡说。我一般设 0.7,兼顾准确性和自然度。

两个 Chat 模型:ChatLanguageModel用于普通调用,StreamingChatLanguageModel用于流式输出。两个都要注册,因为 RAG 检索时需要普通调用,最终回答时需要流式。

Embedding 模型:负责把文本转成向量。注意它的输入是文本,输出是 float 数组,维度 1536。

4.2 文档加载与切分

RAG 的第一步是把知识库文档加载进来并切分成小块。切分策略直接影响检索效果,这是最容易被忽视的环节。

@Service public class DocumentService { @Autowired private EmbeddingModel embeddingModel; @Autowired private EmbeddingStore<TextSegment> embeddingStore; public void loadDocuments(String path) { // 1. 加载文档 DocumentParser parser = new ApacheTikaDocumentParser(); List<Document> documents = new ArrayList<>(); File dir = new File(path); for (File file : dir.listFiles()) { Document document = parser.parse(file); documents.add(document); } // 2. 切分文档 DocumentSplitter splitter = DocumentSplitters.recursive( 500, // 每块最大字符数 50 // 块之间重叠字符数 ); List<TextSegment> segments = splitter.splitAll(documents); // 3. 向量化并存储 for (TextSegment segment : segments) { Embedding embedding = embeddingModel.embed(segment).content(); embeddingStore.add(embedding, segment); } } }

这段代码有三个关键决策:

切分大小 500 字符:这是经验值。太小了语义不完整,检索出来断章取义;太大了包含太多无关信息,影响检索精度。500 字符大约是一到两段话,能保证语义相对完整。如果你的文档句子特别长,可以调到 800;如果是 FAQ 这种短问答,可以调到 300。

重叠 50 字符:块之间保留重叠是为了避免关键信息被切断。比如一句话正好跨在两个块的分界线上,没有重叠的话两个块都读不到完整意思。50 字符大约是 10% 的重叠率,是个平衡点。

递归切分:DocumentSplitters.recursive会优先按段落切,段落太长再按句子切,句子还长再按字符切。这样能最大程度保持语义完整性。别用固定长度切分,会把句子拦腰截断。

注意:文档切分是 RAG 效果的地基。我见过有人检索效果差,排查半天发现是切分策略有问题。如果你的知识库文档结构清晰(有明确的章节标题),建议按标题切分,效果比递归切分更好。

4.3 向量存储与检索

开发阶段用内存存储,配置很简单:

@Bean public EmbeddingStore<TextSegment> embeddingStore() { return new InMemoryEmbeddingStore<>(); }

但内存存储有两个问题:重启数据丢失,数据量大时内存吃紧。生产环境建议换 Redis:

@Bean public EmbeddingStore<TextSegment> embeddingStore(RedisClient redisClient) { return RedisEmbeddingStore.builder() .host("localhost") .port(6379) .dimension(1536) // 必须和 Embedding 模型维度一致 .build(); }

维度 1536 是text-embedding-v2的输出维度,必须匹配,否则存进去检索不出来。换 Embedding 模型时这个值要跟着改。

检索器的配置:

@Bean public ContentRetriever contentRetriever( EmbeddingStore<TextSegment> embeddingStore, EmbeddingModel embeddingModel) { return EmbeddingStoreContentRetriever.builder() .embeddingStore(embeddingStore) .embeddingModel(embeddingModel) .maxResults(5) // 返回最相关的 5 条 .minScore(0.7) // 相似度低于 0.7 的不要 .build(); }

maxResults 设 5:返回太多会稀释 Prompt,让模型抓不住重点;太少可能漏掉关键信息。5 条是个平衡点,每条 500 字符,总共 2500 字符,加上问题和系统提示,控制在模型上下文窗口内绰绰有余。

minScore 设 0.7:这是相似度阈值。低于这个值说明文档和问题不相关,硬塞给模型反而干扰回答。0.7 是余弦相似度的经验值,实际项目可以根据效果调整。如果发现经常检索不到内容,可以降到 0.6;如果检索结果经常跑偏,可以升到 0.8。

4.4 AI Service 接口定义

LangChain4j 最优雅的地方就是 AiServices,用接口声明式地定义 AI 能力:

public interface Assistant { @SystemMessage(""" 你是一个专业的产品知识助手。请根据提供的参考资料回答用户问题。 要求: 1. 只基于参考资料回答,不要编造信息 2. 如果参考资料中没有相关内容,直接说"抱歉,我没有找到相关信息" 3. 回答要简洁明了,分点说明 4. 引用参考资料时注明来源 """) String chat(@UserMessage String question); @SystemMessage("你是一个专业的产品知识助手,请根据参考资料回答问题。") TokenStream streamChat(@UserMessage String question); }

然后配置 RAG 增强:

@Bean public Assistant assistant( StreamingChatLanguageModel streamingModel, ChatLanguageModel chatModel, ContentRetriever contentRetriever) { return AiServices.builder(Assistant.class) .chatLanguageModel(chatModel) .streamingChatLanguageModel(streamingModel) .contentRetriever(contentRetriever) .chatMemory(MessageWindowChatMemory.withMaxMessages(10)) .build(); }

SystemMessage 的设计:这是整个 RAG 效果的关键。我加了四条约束,每条都有用意。“只基于参考资料回答”是防止幻觉,“没有相关内容直接说不知道”是避免硬编,“分点说明”是提升可读性,“注明来源”是方便用户核实。你可以根据自己的业务调整,但“不编造”这条建议保留。

ChatMemory:MessageWindowChatMemory.withMaxMessages(10)表示保留最近 10 条消息作为上下文。这样多轮对话时模型能记住前面聊了什么。10 条是个经验值,太多会占用上下文窗口,太少记不住。如果你的对话轮次多,可以调到 20。

contentRetriever:把检索器注入后,每次调用chat或streamChat时,框架会自动执行“检索 → 拼接 Prompt → 调用模型”这一整套流程。你不需要手动拼 Prompt,这就是框架的价值。

4.5 流式问答接口实现

最后是 Controller 层,提供 SSE 接口给前端:

@RestController @RequestMapping("/api/chat") public class ChatController { @Autowired private Assistant assistant; @GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE) public SseEmitter streamChat(@RequestParam String question) { SseEmitter emitter = new SseEmitter(180_000L); // 3 分钟超时 TokenStream tokenStream = assistant.streamChat(question); tokenStream.onNext(token -> { try { emitter.send(SseEmitter.event().data(token)); } catch (IOException e) { emitter.completeWithError(e); } }).onComplete(response -> { emitter.complete(); }).onError(error -> { emitter.completeWithError(error); }).start(); return emitter; } }

SseEmitter 超时设 3 分钟:大模型生成长回答可能需要几十秒,默认超时太短会中断。3 分钟是个安全值,一般回答不会超过这个时间。

onNext 逐 token 发送:每生成一个 token 就推送给前端,前端收到后追加到页面上,就实现了打字机效果。

onComplete 和 onError:必须处理,否则连接不会关闭,会泄漏资源。

前端对接很简单:

const eventSource = new EventSource('/api/chat/stream?question=' + encodeURIComponent(question)); eventSource.onmessage = (event) => { document.getElementById('answer').innerHTML += event.data; }; eventSource.onerror = () => { eventSource.close(); };

5. 踩坑实录与性能优化

5.1 我踩过的五个坑

坑一:Embedding 维度不匹配。第一次换 Redis 存储时,忘了改 dimension 配置,存进去的数据检索出来全是乱码。排查了半天才发现是维度对不上。记住:Embedding 模型换了,dimension 必须跟着换。

坑二:文档切分把表格切碎了。产品文档里有大量参数表格,递归切分按字符切,把表格切得七零八落,检索出来根本没法看。解决方案是自定义切分器,遇到表格标记时整块保留。如果你的文档表格多,建议用DocumentSplitters.recursive之前先做预处理,把表格转成文本描述。

坑三:流式输出中文乱码。SSE 默认编码可能不是 UTF-8,中文会乱码。解决方案是在produces里指定MediaType.TEXT_EVENT_STREAM_VALUE,Spring Boot 会自动用 UTF-8。如果还乱码,检查一下server.servlet.encoding.charset配置。

坑四:并发调用时 ChatMemory 串了。ChatMemory 默认是全局的,多个用户同时对话时,A 用户的问题会出现在 B 用户的上下文里。解决方案是用ChatMemoryProvider,按用户 ID 隔离:

.chatMemoryProvider(memoryId -> MessageWindowChatMemory.withMaxMessages(10))

调用时传入 memoryId:

String chat(@MemoryId String userId, @UserMessage String question);

坑五:API 调用超时没重试。通义千问偶尔会超时,没有重试机制的话用户直接看到报错。LangChain4j 支持配置重试:

DashScopeChatModel.builder() .apiKey(apiKey) .modelName("qwen-plus") .maxRetries(3) .timeout(Duration.ofSeconds(60)) .build();

5.2 性能优化实战

优化一:文档向量化批处理。一篇文档切分成几百个块,逐个调用 Embedding API 很慢。LangChain4j 支持批量:

List<Embedding> embeddings = embeddingModel.embedAll(segments).content(); embeddingStore.addAll(embeddings, segments);

实测批量比逐个快 5 到 10 倍。

优化二:检索结果缓存。相同的问题反复问,每次都走一遍检索很浪费。可以用 Caffeine 做本地缓存:

Cache<String, List<Content>> cache = Caffeine.newBuilder() .maximumSize(1000) .expireAfterWrite(Duration.ofMinutes(30)) .build();

优化三:Prompt 精简。检索出来的 5 条内容不一定都用得上,可以在拼接前做一次过滤,只保留相似度最高的 3 条。这样 Prompt 更短,模型响应更快。

优化四:流式输出首字优化。用户最在意的是首字延迟。可以把 SystemMessage 精简,减少模型的前置处理时间。实测精简 SystemMessage 能让首字延迟降低 20% 左右。

5.3 常见问题速查表

问题现象可能原因解决方案
启动报 NoSuchMethodError依赖版本不一致统一 langchain4j 版本号
检索不到相关内容切分太大或 minScore 太高调小切分粒度,降低 minScore
回答胡编乱造SystemMessage 约束不够强化“只基于参考资料”约束
流式输出中断SseEmitter 超时太短调大超时时间到 3 分钟
中文乱码编码未指定produces 指定 UTF-8
多用户上下文串了ChatMemory 全局共享用 ChatMemoryProvider 隔离
API 调用超时网络波动配置 maxRetries 和 timeout
内存溢出向量数据全在内存换 Redis 或 PgVector 存储

6. 从 Demo 到生产的最后一公里

6.1 向量存储的选型建议

内存存储只适合开发和演示,生产环境必须换持久化方案。我对比过三种:

Redis:部署简单,性能好,支持向量检索。适合中小规模知识库(百万级向量以内)。缺点是 Redis 的向量检索功能需要 Redis Stack,普通 Redis 不支持。

PostgreSQL + pgvector:如果你已经在用 PostgreSQL,加个扩展就行,不用额外部署。适合数据量不大、想减少组件的场景。缺点是性能不如专用向量库。

Milvus:专业向量数据库,支持亿级向量,检索性能最强。适合大规模知识库。缺点是部署复杂,运维成本高。

我的建议是:中小项目用 Redis,已有 PostgreSQL 的用 pgvector,大规模项目上 Milvus。别一上来就上 Milvus,运维成本会让你怀疑人生。

6.2 知识库更新的处理

生产环境的知识库是动态的,文档会增删改。全量重建向量库太慢,需要增量更新:

public void updateDocument(String docId, Document newDoc) { // 1. 删除旧向量 embeddingStore.removeAll(docId); // 2. 切分新文档 List<TextSegment> segments = splitter.split(newDoc); // 3. 重新向量化存储 List<Embedding> embeddings = embeddingModel.embedAll(segments).content(); embeddingStore.addAll(embeddings, segments); }

关键是给每个文档打上 docId 标记,更新时先删后加。LangChain4j 的TextSegment支持 metadata,可以把 docId 存在 metadata 里。

6.3 效果评估与调优

RAG 效果好不好,不能靠感觉,要有量化指标。我一般看三个:

检索命中率:准备一批测试问题,人工标注每个问题的正确答案在哪个文档块里,然后看检索器能不能把那个块排在前 5。命中率低于 80% 就要调切分策略或检索参数。

回答准确率:让模型回答测试问题,人工判断回答是否正确。准确率低于 90% 就要调 SystemMessage 或增加检索结果数量。

首字延迟:从用户提问到第一个字出现的时间。超过 2 秒用户就会觉得卡。优化方向是精简 Prompt、换更快的模型、加缓存。

这三个指标我建议做成监控,每次调整参数后跑一遍,用数据说话,别凭感觉调。

6.4 成本控制

大模型调用是花钱的,尤其是 Embedding 调用。几个省钱技巧:

Embedding 结果缓存:相同文档不要重复向量化。用文档内容的 MD5 做 key,缓存 Embedding 结果。

检索结果缓存:相同问题不要重复检索。用问题文本做 key,缓存检索结果。

模型分级:简单问题用 qwen-turbo,复杂问题用 qwen-plus。可以在 SystemMessage 里让模型先判断问题复杂度,但这样会增加一次调用。更简单的做法是按业务场景分流,比如 FAQ 用 turbo,深度咨询用 plus。

控制检索数量:maxResults 从 5 降到 3,Prompt 长度减少 40%,成本也跟着降。前提是检索精度够高,3 条就能覆盖答案。

我个人在实际项目里的体会是,RAG 落地最难的不是技术,是效果调优。技术方案网上到处都是,但每个业务场景的文档特点、问题类型、用户预期都不一样,需要反复测试和调整。我建议先用小批量文档跑通链路,再逐步扩大知识库规模,边用边调,别想着一次做到完美。

最后分享一个小技巧:如果你的知识库文档质量参差不齐,可以在加载时做一次预处理,把格式混乱、内容重复的文档过滤掉。垃圾进垃圾出,文档质量决定了 RAG 效果的上限,这个道理在 RAG 里体现得特别明显。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询