Web标准的未来,浏览器的未来,应用的未来。

拇放惶捉基础原理

对于?个企业专属的智能客服,AI ?模型是必不可少,例如 deepseek、chatGPT 等。 可模型本身并不知道公司的各种产品信息,所以需要我们在给模型发送问题的时候,将产品?册?同发送给模型。 可如果产品?册的内容?较多,例如有上百?,上千?,会为该场景带来很多问题:

模型可能?法读取所有内容: ?语?模型只能存储?定量的信息,通常成这个量为上下?窗???。如果产品?册内容超过上下?窗???,模型就会读了后?内容,忘记前?内容。前?所回答的准确率也?法得到保障。

模型推理成本较?: 模型推理成本取决于输?与输出的 token 数量。?般来说,输?的 token数量越多,推理成本就越?。

模型推理慢: 输?的内容越多,模型需要消化的内容也就越多,上百?的?册丢给模型,会极?的拖慢模型推理的速度。

既然直接将?册扔给模型不可?,那么我们可以考虑将和问题相关的内容提取出来扔给模型。这时,RAG 技术就派上?场了。

RAG 的基本运?流程

RAG 会将?档的内容切割为多个?段。当?户提出问题后, RAG 会根据问题的内容,在所有的?段中寻找相关内容。 假设?户问题仅关联了 2 个?段,那么 RAG 仅会将这 2 个?段发送给模型,这样整个?册扔给模型的问题便迎刃?解了。

上述仅是 RAG 流程的简化链路。每个环节都包含了很多实现细节。 ?般来说,RAG 的基本流程包含两个部分:

准备阶段(?户提问前):需要将相关的?档都准备好,并完成相应的预处理。其包含分? 与索引两个环节。

回答阶段(?户提问后):需要根据?户的问题,依次触发回答问题的各个环节,包括召回 、重排 与?成。

接下来我们逐步拆解,看看这五个环节都是如何?作的。

分片

顾名思义,分?就是将?档分成多个?段。

分?的?式有很多种。可以按照字数来分,?如 1000 字为?个?段。也可以按照段落来分,每个段落是?个?段。 亦或者可以按照章节分,按照?码分,按照指定的字符分等等。?论选择何种

?式,最终?标是将?篇完整的?档切分为多份。?此,该环节即可结束。

索引

索引是通过 Embedding 将?段?本转化为向量 ,然后将?段?本和对应的向量存储在向量数据库中的过程。

这?存在?个重要概念需要理解:

向量:数学上的?个概念,既包含了??,也包含了?向。通常我们会??个数组来表示它。 RAG 中使?的向量,其维度可以包含数百个甚?上千个。维度越?,向量所包含的信息也就越丰富,使?这些向量做的?作内容可靠性也有越强。

Embedding:将?本转化为向量的?个过程。含义相近的?本在经历了 Embedding 之后,其对应的向量也会?较接近。

使??维向量来举例:假设我们有两个?本?段,分别是:

?本?段 1:"活字格是低代码平台"

?本?段 2:"活字格是低代码?具"

那么这两个?本?段在经历了 Embedding 之后,会分别转化为两个?维向量,分别是:

向量 1: 3, 6

向量 2: 4, 6

其在坐标轴上的可视化如图所示:

image

可以看到,这两个向量是?常接近。这时,?户的问题内容是:"今天天?怎么样?",其对应的向量为: 5, 3 。该向量位置如图所示:

image

这说明前两个?本的内容在语义上是相似的,?第三个问题内容和前两个?本内容毫不相关。这就是 Embedding 的?的。

TIP

Embedding 这个操作需要借助专属的**向量模型**进?处理。

关于向量模型的评估与选择,可参考 [huggingface 的向量模型评估](https://huggingface.co/spaces/mteb/leaderboard)。

向量数据库:?来存储和查询向量的专?数据库。它为存储向量做了很多优化,还提供了计算向量相似度等相关的函数,?便我们查询与使? Embedding 后的向量。

NOTE

为确保向量和?本的对应关系,我们需要在索引阶段,务必同时存??本?段!

image

向量仅仅是?个中间产物,最终我们需要通过向量相似度检索出相似的向量,并抽取原始?本,?起发送给?模型,?成最终的答案。

?论是分?,还是索引,都是发?在?户提问题之前的阶段,属于要提前准备的步骤。接下来,我们来看看?户提问题之后的环节。

召回

召回就是搜索与?户问题相关?段的过程。这个环节从?户问题开始。

?户提问:"活字格是什么?"

将?户的问题发送给 Embedding 模型。

Embedding 模型会将问题转化为向量;

将问题向量发送?向量数据库中;

向量数据库会基于问题向量检索出库中与?户问题最为相关的 N 个?段内容(N 为召回数量,

?般为 10 个,可根据实际情况进?调整)。

在召回环节,我们需要根据?户问题的向量,与向量数据库中的向量进?相似度计算,找到与?户问题最为相关的 N 个向量以及其对应的原始?本?段。

TIP

召回环节最重要的步骤就是基于 向量相似度 进?相关内容的检索。

向量相似度的计算?式有很多种,例如余弦相似度、欧?距离、点积等。 经过计算后的向量相似度是?个数字,数字越?,代表两个向量的相似度越?,也就意味着向量对应的?本语义相关性越强。

重排

重排的全称是重新排序。其本质和召回是相同。召回是从所有的?段中找到相似度最?的 N 个?段。 ?重排则是在召回结果的基础上,根据?段的相关性进?排序,再选取出最相关的 K 个?段

(K 为最终输出数量,?般为 3 个)。

TIP

之所以在召回的基础上进?重拍的操作,是因为召回环节使?的?本相似度计算逻辑,在保证性能和效率的前提下,获取到的结果可能并不是最优的。只适合做海量数据的初步筛选。 ?重排会采?准确率更好的相似度算法(会占?更多的计算资源)来进?排序,从?获取到更相关的内容。因此重排更适合对于少量数据做精细化筛选。 该过程类似公司筛选?才。召回环节可类?为简历筛选,?重排环节可类?为?试筛选。

生成

?成环节的唯??作就是?成最终答案。

这?步我们已经获得了?户问题以及相关性最?的 K 个资料?段。我们可将这两部分?起发送给

?模型,让它根据?段内容来回答?户问题。 ?此,整个 RAG 流程就结束了。

image

RAG 实战 - 代码模式

本章节将通过代码实战的?式,帮助您快速搭建?个 RAG 系统。教程会使?三种不同的技术栈,分别是:

Python(3.12 及以上), 使? Node.js(18.x 及以上), 使? Java(JDK 1.8 及以上), 使?

作为包管理器。作为包管理器。

作为包管理器。

可以按需选择其中?种技术栈进?实战。

环境准备

向量数据库

为保证多技术栈的依赖?致性,我们选择Qdrant 作为向量数据库。

TIP

Qdrant 暂不?持内存存储,因此建议学习时可选择 docker 的?式进? Qdrant 服务的部署。

Qdrant 服务部署完成后,我们需要使? Qdrant 客户端与其进?交互。

io.qdrant

client

1.15.0

模型服务

教程会使?向量模型和标准?语?模型。为?便?户体验,我们会使?阿?云百炼平台的模型。

向量模型:text-embedding-v4 ?于将?本转换为向量。

?语?模型:qwen-plus?于?成最终回复的内容。

使?模型服务,需要提前配置环境变量的 api key。配置?法可参考 阿?云百炼?档。

TIP

您也可以在对应的?程下创建配置?件,从配置?件中读取环境变量。

文本准备

可??准备?于 RAG 的内容?档,?户提问后,RAG 系统会从该?档中进?答案搜索,为?便学习,建议准备?档格式为 Markdown。

RAG 实现

分片

将?档?件按段落分割成?本块列表。

该函数读取指定的?档?件,并根据双换?符\n\n将?档内容分割成多个?本块。

1

/**

* 将?档?件按段落分割成?本块列表

*

* @param docFile 要读取的?档?件路径

* @return 包含所有?空?本块的列表,每个元素代表?档中的?个段落或?本块

* @throws IOException 当?件读取失败时抛出

2*/

9 public static List splitIntoChunksByParagraph(String docFile) throw

10 String doc = new String(Files.readAllBytes(Paths.get(docFile)));

11 String[] chunks = doc.split("\n\n");

12 List result = new ArrayList<>(); 13

14 for (String chunk : chunks) {

15 if (!chunk.trim().isEmpty()) {

16 result.add(chunk); 17 }

18 }

19 return result; 20 }

21

22 // ======== 分? ========

List chunks = TextUtil.splitIntoChunksByParagraph("src/main/resourc

索引

使? embedding 模型将第?步切割好的?本块依次转换为对应的?本向量。

/**

* 从配置?件中加载API Key

*/

private void loadApiKey() {

try (InputStream input = getClass().getClassLoader().getResourceAsStrea Properties prop = new Properties();

prop.load(input);

apiKey = prop.getProperty("dashscope.api-key");

} catch (IOException e) {

throw new RuntimeException("Failed to load API key from configurati

}

}

/**

* ?成?本嵌?向量

* @param textList ?本列表

* @return 向量结果

*/

20 public List textEmbedding(List textList) t

21 TextEmbeddingParam param = null;

22 TextEmbedding textEmbedding = null;

23 try {

24 param = TextEmbeddingParam

25 .builder()

26 .apiKey(apiKey)

27 .model("text-embedding-v4")

28 .texts(textList)

29 .parameter("dimension", 1024)

30 .outputType(TextEmbeddingParam.OutputType.DENSE_AND_SPARSE)

31 .build();

32 textEmbedding = new TextEmbedding();

33 TextEmbeddingResult result = textEmbedding.call(param);

34 return result.getOutput().getEmbeddings();

35 } catch (ApiException | NoApiKeyException e) {

36 System.out.println("调?失败:" + e.getMessage());

37 }

38 return List.of(); 39 }

40

41 // ======== ?本向量化 ========

42 DashScopeClient client = new DashScopeClient();

43 List> denseEmbeddings = client.textEmbedding(chunks).stream()

44 .map(TextEmbeddingResultItem::getEmbedding)

45 .map(innerList -> innerList.stream().map(Double::floatValue).to

.toList();

2.将?成的向量存?向量数据库中

/**

* 确保集合存在

*/

public void ensureCollectionExists() throws ExecutionException, Interrupted try {

qdrantClient.getCollectionInfoAsync(COLLECTION_NAME).get();

} catch (Exception e) {

if (e.getCause() instanceof io.grpc.StatusRuntimeException statusEx

9 if (statusException.getStatus().getCode() == io.grpc.Status.Cod

10 qdrantClient.createCollectionAsync(COLLECTION_NAME,

11 Collections.VectorParams.newBuilder()

12 .setDistance(Collections.Distance.C

13 .setSize(1024)

14 .build())

15 .get();

16 }

17 }

18 }

19 }

20

21 /*** 保存向量

*

* @param chunks ?本块列表

* @param embeddings 嵌?向量列表

*/

public void saveEmbeddings(List chunks, List> embedding

28 // 构建 Points 列表

29 List points = IntStream.range(0, chunks.size())

30 .mapToObj(i -> {

31 List vector = embeddings.get(i);

32 Map payload = new HashMap<>();

33 payload.put("text", ValueFactory.value(chunks.get(i)));

34

35 return Points.PointStruct.newBuilder()

36 .setId(id(i))

37 .setVectors(VectorsFactory.vectors(vector))

38 .putAllPayload(payload)

39 .build();

40 })

41 .collect(Collectors.toList());

42 // 确保集合存在

43 ensureCollectionExists();

44 // 批量插? points

45 qdrantClient.upsertAsync(

46 COLLECTION_NAME,

47 points

48 ).get();

49 System.out.println("[INFO] 成功上传 " + points.size() + " 个向量到集合 '"

50 }

?此,RAG 实现的索引部分就完成了。

召回与重排

/**

* 查询向量

*

* @param queryEmbedding 查询向量

* @param topK

* @return 查询结果

*/

public List query(List queryEmbedding, int topK) throws Exec List queryResult = qdrantClient.searchAsync(

Points.SearchPoints.newBuilder()

.setCollectionName(COLLECTION_NAME)

.addAllVector(queryEmbedding)

.setLimit(topK)

.setWithPayload(Points.WithPayloadSelector.newBuilder()

.build()

).get();

return queryResult.stream().map(point -> point.getPayloadMap().get("tex

}

// ======== 召回 ========

String query = "请替换为?户的真实问题";

List> queryEmbeddings = client.textEmbedding(List.of(query)).st

.map(TextEmbeddingResultItem::getEmbedding)

.map(innerList -> innerList.stream().map(Double::floatValue).toList

.toList();

List relatedChunks = qdrantAgent.query(queryEmbeddings.getFirst(),5

TIP

由于我们使?的是阿?云百炼的专业向量模型,其处理逻辑对于相关性提供了较好的?持。因此,我们可以直接使?向量模型的输出结果进?召回,??需进?额外的重排。

如果您发现召回的?档与?户问题的相关性较低,您可以尝试调整召回的?档数量 top_k ,或者使?更专业的向量模型增加?次重排的操作。

生成

?成阶段需要?个标准的?本?成?模型,将检索出的?档内容进?整理,并输出最终答案。这?选择的是阿?百炼平台提供的?本?成模型 。

/**

* 调??本?成模型

*

* @param query ?户问题

* @param prompt 系统提示

* @return ?成结果

*/

public GenerationResult callWithMessage(String query, String prompt) throws Generation gen = new Generation();

Message systemMsg = Message.builder()

.role(Role.SYSTEM.getValue())

.content(prompt)

.build();

Message userMsg = Message.builder()

.role(Role.USER.getValue())

.content(query)

.build();

GenerationParam param = GenerationParam.builder()

.apiKey(apiKey)

.model("qwen-plus")

.messages(Arrays.asList(systemMsg, userMsg))

.resultFormat(GenerationParam.ResultFormat.MESSAGE)

.build(); return gen.call(param);

}

// ======== ?成 ========

String prompt = String.format( """

你是?位知识助?,请根据?户的问题和下列?段?成准确的回答。

?户问题:%s

相关?段:

%s

请基于上述内容作答,不要编造信息。如果相关?段中没有相关信息,回答"没有相关

query, relatedChunks

);

GenerationResult result = client.callWithMessage(query, prompt); System.out.println(result.getOutput().getChoices().getFirst().getMessage().

总结

?此,我们使?代码开发的?式,完成了?个基于 RAG 的问答系统的完整流程,包括?档索引、问题召回和答案?成。在实际应?中,你可能需要根据具体的业务场景和数据特征进?优化,例如调整召回策略、优化?成模型的提示词等。

接下来,我们可以了解低代码?式的 RAG 系统构建。

RAG 实战 - 低代码模式

本章节将通过低代码的?式,帮助您快速构建?个 RAG 系统。教程采?的低代码平台为

环境准备

设计器

我们的构建过程旨在了解 RAG 的落地实现,因此,仅需安装设计器,即可在本地测试实践。

向量数据库

为?便低代码模式下的实践,我们选择使??个基于内存的向量数据库进?实践。您?需关?该数据的安装,葡萄城市场已经提供了 插件,您可以直接在设计器中进?安装。

模型服务

模型服务仍选择阿?云百炼平台。考虑到实现的通?性,低代码平台的模型服务选择了通?的

REST 接口,您需要在设计器中安装 的插件。 在必要情况下,您可能需要安装插件以及 。

RAG 实现

RAG 所有的实现逻辑均维护在活字格的「逻辑 服务端命令」中。

内容概要:本文介绍了一个基于冠豪猪优化算法(CPO)的无人机三维路径规划项目,利用Python实现了在复杂三维环境中为无人机规划安全、高效、低能耗飞行路径的完整解决方案。项目涵盖空间环境建模、无人机动力学约束、路径编码、多目标代价函数设计以及CPO算法的核心实现。通过体素网格建模、动态障碍物处理、路径平滑技术和多约束融合机制,系统能够在高维、密集障碍环境下快速搜索出满足飞行可行性、安全性与能效最优的路径,并支持在线重规划以适应动态环境变化。文中还提供了关键模块的代码示例,包括环境建模、路径评估和CPO优化流程。; 适合人群:具备一定Python编程基础和优化算法基础知识,从事无人机、智能机器人、路径规划或智能优化算法研究的相关科研人员与工程技术人员,尤其适合研究生及有一定工作经验的研发工程师。; 使用场景及目标:①应用于复杂三维环境下的无人机自主导航与避障;②研究智能优化算法(如CPO)在路径规划中的实际部署与性能优化;③实现多目标(路径最短、能耗最低、安全性最高)耦合条件下的工程化路径求解;④构建可扩展的智能无人系统决策框架。; 阅读建议:建议结合文中模型架构与代码示例进行实践运行,重点关注目标函数设计、CPO算法改进策略与约束处理机制,宜在仿真环境中测试不同场景以深入理解算法行为与系统鲁棒性。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值