Taro 介绍

简介

Taro 是一个开放式跨端跨框架解决方案,支持使用 React/Vue/Nerv 等框架来开发 微信 / 京东 / 百度 / 支付宝 / 字节跳动 / QQ / 飞书 小程序 / H5 / RN 等应用。

现如今市面上端的形态多种多样,Web、React Native、微信小程序等各种端大行其道。当业务要求同时在不同的端都要求有所表现的时候,针对不同的端去编写多套代码的成本显然非常高,这时候只编写一套代码就能够适配到多端的能力就显得极为需要。

特性

多端转换支持

Taro 3 可以支持转换到 H5、ReactNative 以及任意小程序平台。

目前官方支持转换的平台如下:

框架支持

在 Taro 3 中可以使用完整的 react React / vue Vue / preact Preact / svelte Svelte / Nerv 开发体验,具体请参考:

  • Vue

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<template>
<view class="index">
<text>msg</text>
</view>
</template>

<script>
export default {
data() {
return {
msg: 'Hello World!',
}
},
created() {},
onShow() {},
onHide() {},
}
</script>

Taro UI

https://taro-docs.jd.com/docs/#taro-ui

Taro 3 只能配合使用 taro-ui@next 版本

安装命令: npm i taro-ui@next

一款基于 Taro 框架开发的多端 UI 组件库。

Taro UI 特性:

  • 基于 Taro 开发 UI 组件
  • 一套组件可以在多端适配运行(ReactNative 端暂不支持)
  • 提供友好的 API,可灵活的使用组件

学习资源

https://taro-docs.jd.com/docs/#学习资源

【资讯】Taro 团队博客

【教程】5 分钟上手 Taro 开发

【视频】5 分钟快速上手 Taro 开发小程序

CoT思维链

一、CoT是什么

CoT(Chain of Thought,思维链) 是一种提示工程技术,通过引导大语言模型(LLM)逐步推理,显式地展示思考过程,从而提升模型在复杂任务上的表现。

1.1 核心思想

传统Prompt CoT Prompt
直接问答案 要求展示推理过程
模型”黑盒”思考 思考过程可见、可验证
简单问题效果好 复杂推理任务效果显著

1.2 为什么CoT有效

1
2
3
4
5
人类解决问题的方式:
问题 → 分析 → 推理步骤1 → 推理步骤2 → ... → 得出答案

CoT让LLM模拟这个过程:
输入 → 分解问题 → 逐步推理 → 中间结论 → 最终答案

关键优势

  • 可解释性:能看到模型的思考路径
  • 准确性:复杂推理任务准确率提升显著
  • 可调试性:出错时可以定位到具体步骤

二、CoT的基本形式

2.1 零样本CoT(Zero-shot CoT)

最简单的方式:在问题后添加触发词

1
2
3
问题:一个农场有鸡和兔,头共35个,脚共94只。鸡兔各多少只?

请逐步思考并解答:

常用触发词

  • “让我们逐步思考”
  • “请展示你的推理过程”
  • “一步一步来解决这个问题”
  • “Let’s think step by step”

2.2 少样本CoT(Few-shot CoT)

提供示例:在Prompt中加入带推理过程的示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
示例1:
问题:小明有5个苹果,给了小红2个,又买了3个,现在有几个?
推理:
1. 初始有5个苹果
2. 给小红2个后:5 - 2 = 3个
3. 又买3个后:3 + 3 = 6个
答案:6个

示例2:
问题:一本书100页,第一天看了1/4,第二天看了剩下的1/3,还剩多少页?
推理:
1. 第一天看了:100 × 1/4 = 25页
2. 剩余:100 - 25 = 75页
3. 第二天看了:75 × 1/3 = 25页
4. 剩余:75 - 25 = 50页
答案:50页

现在请解决:
问题:一个水池有进水管和出水管,进水管单独注满需6小时,出水管单独排空需8小时,同时打开两管,几小时注满?
推理:

2.3 自动CoT(Auto-CoT)

自动构建示例:通过算法自动选择或生成示例

适用场景

  • 示例难以人工编写
  • 需要大量多样化示例
  • 领域专业性较强

三、CoT的高级技巧

3.1 自我一致性(Self-Consistency)

核心思想:让模型多次推理,选择最一致的答案

1
2
3
4
5
6
7
8
9
10
11
12
13
步骤:
1. 使用CoT生成多个推理路径(temperature > 0)
2. 收集所有答案
3. 投票选择出现次数最多的答案

示例:
推理路径1 → 答案A
推理路径2 → 答案A
推理路径3 → 答案B
推理路径4 → 答案A
推理路径5 → 答案C

最终答案:A(出现3次,最多)

适用场景:数学问题、逻辑推理题

3.2 从简到繁(Least-to-Most)

核心思想:先解决子问题,再组合解决主问题

1
2
3
4
5
6
7
8
9
复杂问题:计算 (123 + 456) × (789 - 321) ÷ 3

分解:
步骤1:计算 123 + 456 = 579
步骤2:计算 789 - 321 = 468
步骤3:计算 579 × 468 = 270,972
步骤4:计算 270,972 ÷ 3 = 90,324

答案:90,324

3.3 思维树(Tree of Thoughts, ToT)

核心思想:探索多个推理分支,评估后选择最优路径

1
2
3
4
5
6
7
8
9
10
11
12
13
14
问题:24点游戏,数字 [4, 9, 10, 13]

思维树探索:
├── 分支1: 13 + 10 = 23
│ ├── 23 + 9 = 32 (×)
│ └── 23 - 4 = 19 (×)
├── 分支2: 13 - 9 = 4
│ ├── 4 × 4 = 16 (×)
│ └── 4 + 10 = 14 (×)
├── 分支3: 10 - 4 = 6
│ └── 6 × (13 - 9) = 24 (√)
└── ...

最优路径:(13 - 9) × (10 - 4) = 24

3.4 验证链(Chain of Verification)

核心思想:生成答案后,主动验证每一步

1
2
3
4
5
6
7
8
9
10
11
问题:法国的首都是哪里?它的人口是多少?

初始回答:
法国的首都是巴黎,人口约2200万。

验证步骤:
1. 验证首都:巴黎确实是法国首都 ✓
2. 验证人口:巴黎市区人口约220万,大都会区约1200万 ✗

修正后:
法国的首都是巴黎,市区人口约220万。

四、CoT实践指南

4.1 何时使用CoT

场景 建议 原因
数学计算 ✅ 强烈推荐 需要多步计算
逻辑推理 ✅ 强烈推荐 需要演绎推理
代码生成 ✅ 推荐 需要分解步骤
文本分类 ⚠️ 可选 简单任务可能不需要
翻译任务 ❌ 不推荐 通常不需要推理
创意写作 ❌ 不推荐 可能限制创造性

4.2 CoT Prompt设计原则

原则1:明确性

1
2
3
4
5
6
7
8
9
10
❌ 差:
解决这个问题。

✅ 好:
请按以下步骤解决这个问题:
1. 理解问题要求
2. 识别已知条件
3. 制定解题计划
4. 执行计算
5. 验证结果

原则2:结构化

1
2
3
4
5
6
7
8
9
10
11
12
❌ 差:
帮我算一下这个。

✅ 好:
问题:[具体问题]

请按以下格式回答:
分析:[对问题的分析]
步骤1:[第一步推理]
步骤2:[第二步推理]
...
结论:[最终答案]

原则3:示例质量

1
2
3
4
5
6
7
8
9
10
11
12
13
❌ 差示例:
问题:2+2=?
推理:等于4
答案:4

✅ 好示例:
问题:25 × 4 = ?
推理:
1. 将25分解为20 + 5
2. 计算20 × 4 = 80
3. 计算5 × 4 = 20
4. 相加:80 + 20 = 100
答案:100

4.3 CoT模板库

模板1:数学问题解决

1
2
3
4
5
6
7
8
9
10
11
12
你是一个数学助手。请按以下步骤解决问题:

问题:{question}

解题步骤:
1. 理解题意:{分析题目要求}
2. 识别已知:{列出已知条件}
3. 确定方法:{选择解题方法}
4. 详细计算:{展示计算过程}
5. 结果验证:{检查结果合理性}

答案:{最终答案}

模板2:逻辑推理

1
2
3
4
5
6
7
8
9
10
11
12
13
请作为逻辑分析师,按以下框架分析:

前提条件:
{列出所有前提}

推理过程:
步骤1:{从前提A推导}
步骤2:{结合前提B推导}
步骤3:{得出中间结论}

最终结论:{答案}

置信度:{高/中/低},理由:{说明}

模板3:代码调试

1
2
3
4
5
请作为资深程序员,按以下步骤调试代码:

代码:
```{language}
{code}

错误信息:{error}

调试步骤:

  1. 错误定位:{哪一行/哪个函数}
  2. 原因分析:{为什么会出现这个错误}
  3. 解决方案:{如何修复}
  4. 修复后代码:{corrected code}
  5. 预防措施:{如何避免类似错误}
    1
    2
    3
    4
    5
    6
    7
    8
    9

    ---

    ## 五、CoT与其他技术的结合

    ### 5.1 CoT + RAG

    **场景**:需要结合外部知识的推理

    系统流程:
    用户问题 → 检索相关知识 → CoT推理 → 生成答案

示例:
问题:某公司2023年营收增长了多少?

步骤1(RAG):
检索到:2022年营收100亿,2023年营收120亿

步骤2(CoT):

  1. 获取2023年营收:120亿
  2. 获取2022年营收:100亿
  3. 计算增长额:120 - 100 = 20亿
  4. 计算增长率:20/100 × 100% = 20%

答案:营收增长了20%,即20亿元

1
2
3
4
5

### 5.2 CoT + Function Calling

**场景**:需要调用工具完成推理

问题:北京明天适合户外活动吗?

CoT推理:

  1. 需要获取北京明天的天气信息
  2. 需要判断天气是否适合户外活动

Function Call:

  • 调用天气API获取北京明天天气

继续推理:
3. 根据返回的天气数据(温度、降雨概率、风力)
4. 判断:温度适宜(15-25°C)、降雨概率低(<30%)、风力适中(<4级)
5. 结论:适合户外活动

答案:适合,明天北京天气晴好,温度适宜,适合户外活动。

1
2
3
4
5

### 5.3 CoT + 多智能体协作

**场景**:复杂问题需要多个专家协作

问题:设计一个高并发的电商系统

智能体协作流程:

[架构师智能体]
推理:需要考虑负载均衡、服务拆分、缓存策略…
输出:系统架构方案

[数据库专家智能体]
推理:根据架构,需要设计分库分表、读写分离…
输出:数据库设计方案

[安全专家智能体]
推理:检查架构中的安全漏洞,设计防护方案…
输出:安全加固建议

[汇总智能体]
CoT整合:

  1. 整合架构方案
  2. 整合数据库设计
  3. 整合安全建议
  4. 生成最终方案文档
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35

    ---

    ## 六、CoT效果评估

    ### 6.1 评估指标

    | 指标 | 说明 | 计算方法 |
    |------|------|----------|
    | 准确率 | 答案正确比例 | 正确数/总数 |
    | 推理完整性 | 步骤是否完整 | 人工评估 |
    | 逻辑一致性 | 推理是否自洽 | 人工评估 |
    | 答案稳定性 | 多次运行一致性 | Self-Consistency |
    | 推理长度 | 步骤数量 | 自动统计 |

    ### 6.2 常见问题与优化

    | 问题 | 表现 | 解决方案 |
    |------|------|----------|
    | 推理跳跃 | 跳过关键步骤 | 要求"详细展示每一步" |
    | 幻觉推理 | 基于错误前提推理 | 添加验证步骤 |
    | 循环推理 | 重复相同步骤 | 限制最大步骤数 |
    | 过早结论 | 推理不充分就下结论 | 要求"确保推理完整" |
    | 格式混乱 | 输出结构不清晰 | 提供明确的格式模板 |

    ---

    ## 七、实战案例

    ### 案例1:数学应用题

    **问题**:
    甲乙两人同时从A、B两地相向而行,甲每小时走5公里,乙每小时走4公里,3小时后相遇。A、B两地相距多少公里?

    **CoT解答**:
    分析:这是相遇问题,需要计算两人3小时走的总路程。

步骤1:确定已知条件

  • 甲的速度:5 km/h
  • 乙的速度:4 km/h
  • 行走时间:3 小时

步骤2:计算甲走的路程
甲的路程 = 速度 × 时间 = 5 × 3 = 15 公里

步骤3:计算乙走的路程
乙的路程 = 速度 × 时间 = 4 × 3 = 12 公里

步骤4:计算总距离
A、B距离 = 甲的路程 + 乙的路程 = 15 + 12 = 27 公里

验证:
两人相向而行,相对速度 = 5 + 4 = 9 km/h
3小时相遇,距离 = 9 × 3 = 27 公里 ✓

答案:A、B两地相距27公里。

1
2
3
4
5
6
7

### 案例2:逻辑谜题

**问题**:
有三扇门,后面分别是一辆车和两只山羊。你选择一扇门后,主持人(知道门后是什么)打开另一扇有山羊的门,然后问你要不要换门。你应该换吗?

**CoT解答**:

分析:这是蒙提霍尔问题,需要概率分析。

初始状态:

  • 选中车的概率:1/3
  • 选中山羊的概率:2/3

情况1:初始选中车(概率1/3)

  • 主持人打开一扇有山羊的门
  • 换门会得到山羊
  • 不换得到车

情况2:初始选中山羊(概率2/3)

  • 主持人只能打开另一扇有山羊的门
  • 换门会得到车
  • 不换得到山羊

概率计算:

  • 不换门的胜率:1/3
  • 换门的胜率:2/3

结论:应该换门,胜率从1/3提升到2/3。

1
2
3
4
5
6
7

### 案例3:代码生成

**问题**:
写一个函数,找出数组中第K大的元素。

**CoT解答**:

分析:有多种方法可以实现,需要考虑时间复杂度和空间复杂度。

方案对比:

  1. 排序法:O(nlogn),简单直接
  2. 最小堆法:O(nlogk),适合k较小的情况
  3. 快速选择:O(n)平均,最优但实现复杂

选择:使用最小堆法,时间复杂度较好且实现相对简单。

实现步骤:

  1. 创建一个大小为k的最小堆
  2. 遍历数组,维护堆中的k个最大元素
  3. 堆顶就是第k大的元素

代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import heapq

def find_kth_largest(nums, k):
# 创建最小堆(Python heapq默认是最小堆)
min_heap = []

for num in nums:
if len(min_heap) < k:
heapq.heappush(min_heap, num)
elif num > min_heap[0]:
# 当前元素比堆顶大,替换堆顶
heapq.heapreplace(min_heap, num)

# 堆顶就是第k大的元素
return min_heap[0]

# 验证
nums = [3, 2, 1, 5, 6, 4]
k = 2
result = find_kth_largest(nums, k)
print(f"第{k}大的元素是:{result}") # 输出:5

复杂度分析:

  • 时间复杂度:O(nlogk),每个元素最多入堆出堆一次
  • 空间复杂度:O(k),堆的大小
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26

    ---

    ## 八、总结

    ### 8.1 CoT核心要点

    | 要点 | 说明 |
    |------|------|
    | 适用场景 | 复杂推理、数学计算、逻辑分析 |
    | 关键技巧 | 零样本触发、少样本示例、自我一致性 |
    | 设计原则 | 明确性、结构化、高质量示例 |
    | 高级扩展 | ToT、验证链、多智能体协作 |

    ### 8.2 最佳实践 checklist

    - [ ] 判断任务是否需要推理(简单任务可能不需要CoT)
    - [ ] 选择合适的CoT形式(零样本/少样本)
    - [ ] 设计清晰的推理步骤和格式
    - [ ] 提供高质量的示例(如果使用少样本)
    - [ ] 考虑使用自我一致性提升准确率
    - [ ] 对关键任务添加验证步骤
    - [ ] 评估CoT效果,持续优化Prompt

    ### 8.3 学习路径

    入门 → 掌握零样本CoT → 学习少样本CoT → 了解高级技巧 → 实践优化
    ```

关键记住:CoT的本质是让模型像人类一样”先思考,后回答”,通过显式展示推理过程来提升复杂任务的准确性和可解释性。

OOAD操作指南

一、OOAD是什么

OOAD(Object-Oriented Analysis and Design)是面向对象分析与设计的简称,分为两个阶段:

  • OOA(分析阶段):理解业务,确定”做什么”
  • OOD(设计阶段):设计实现方案,确定”怎么做”

二、OOAD要完成的任务

步骤 阶段 核心任务 产出物 与下一步的关系
1 需求分析 理解用户需求 需求文档、用例图 为领域建模提供业务对象线索
2 领域建模 识别业务对象和关系 领域模型、类图 为类设计提供对象定义
3 类设计 设计类的属性和方法 详细类定义 为架构设计提供基础单元
4 架构设计 确定系统层次和模块 架构图、分层设计 为详细设计提供结构框架
5 详细设计 设计对象交互流程 时序图、接口定义 指导编码实现

三、OOAD执行步骤

步骤1:需求分析

做什么:收集和理解用户需求

怎么做

  1. 与用户沟通,收集功能需求
  2. 识别系统参与者和用例
  3. 编写用例描述

示例:电商系统需求

1
2
3
4
5
参与者:顾客、商家、管理员
用例:
- 顾客:浏览商品、下订单、支付
- 商家:上架商品、处理订单
- 管理员:管理用户、查看报表

步骤2:领域建模

做什么:基于需求分析结果,找出业务中的关键对象和它们的关系

怎么做

  1. 从需求文档中识别名词(候选对象):关注需求描述中的业务实体
  2. 筛选核心领域对象:去除冗余,保留与业务密切相关的对象
  3. 确定对象属性:识别对象的关键特征
  4. 确定对象之间的关系:关联、聚合、组合、继承

示例:基于电商需求分析的领域模型

1
2
3
4
5
6
7
8
9
10
11
12
需求中的名词:用户、商品、订单、订单项、购物车、支付记录...

核心领域对象:
- 用户(User)
- 商品(Product)
- 订单(Order)
- 订单项(OrderItem)

对象关系:
- 用户 --创建--> 订单(一对多)
- 订单 --包含--> 订单项(组合关系,订单项不能独立存在)
- 订单项 --关联--> 商品(多对一,订单项引用商品信息)

步骤3:类设计

做什么:将领域对象转化为程序类,为架构设计提供基础单元

怎么做

  1. 根据领域模型,定义类名、属性、方法
  2. 确定类之间的关系(继承、关联、组合)
  3. 应用设计原则(SOLID)
  4. 为架构分层做准备:识别哪些类属于领域层,哪些属于应用层

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 领域层类 - 包含核心业务逻辑
public class Order {
private Long id;
private User user;
private List<OrderItem> items;
private OrderStatus status;

// 领域方法:业务规则在此实现
public void addItem(Product product, int qty) {
if (status != OrderStatus.CREATED) {
throw new IllegalStateException("只能修改待支付订单");
}
items.add(new OrderItem(product, qty));
recalculateTotal();
}

public void pay() {
this.status = OrderStatus.PAID;
}
}

步骤4:架构设计

做什么:确定系统的整体结构,将类组织到合适的层次和模块中

4.1 常见架构模式

模式 适用场景 核心思想
分层架构 大多数企业应用 按职责分层:表现层→应用层→领域层→基础设施层
MVC Web应用 分离Model、View、Controller
微服务 大型分布式系统 按业务拆分独立服务

4.2 架构选型建议

简单决策法

  • 小型项目/团队 → 使用分层架构(推荐)
  • 复杂业务逻辑 → 在分层架构基础上增加领域层
  • 大型分布式系统 → 考虑微服务

4.3 分层架构设计(推荐)

四层结构

1
2
3
4
5
6
7
8
9
┌─────────────────────────────────────┐
│ 表现层 (Presentation) │ ← Controller,处理HTTP请求
├─────────────────────────────────────┤
│ 应用层 (Application) │ ← Service,编排业务流程
├─────────────────────────────────────┤
│ 领域层 (Domain) │ ← 实体类,核心业务逻辑
├─────────────────────────────────────┤
│ 基础设施层 (Infrastructure) │ ← Repository,数据访问
└─────────────────────────────────────┘

层间规则

  • 上层可以调用下层,下层不能调用上层
  • 只能调用相邻层,不能跨层
  • 层间通过接口交互,使用DTO传递数据

模块划分示例

1
2
3
4
5
src/
├── controller/ # 表现层:Controller类
├── service/ # 应用层:业务编排
├── domain/ # 领域层:Order、User等实体
└── repository/ # 基础设施层:数据访问

步骤5:详细设计

做什么:基于架构设计,设计对象之间的交互流程

怎么做

  1. 绘制时序图:描述请求在各层之间的流转过程
  2. 设计接口参数:定义每层接口的输入输出
  3. 确定异常处理:每层如何捕获和转换异常

示例:基于分层架构的下订单流程

1
2
3
4
5
6
7
8
9
10
11
用户请求

[表现层] OrderController.createOrder(request)

[应用层] OrderService.createOrder(command)

[领域层] Order.create() → 业务规则校验

[基础设施层] OrderRepository.save(order)

返回结果

设计要点

  • 严格遵循架构设计的层次结构
  • 每层只处理本层职责(表现层做参数校验,领域层做业务规则)
  • 层间通过DTO传递数据,避免直接暴露领域对象

四、OOAD核心原则

原则 含义 实践建议
单一职责 一个类只做一件事 类功能要聚焦
开闭原则 对扩展开放,对修改关闭 使用接口和抽象类
依赖倒置 依赖抽象而非具体 通过接口交互
高内聚 内部元素紧密相关 相关功能放一起
低耦合 减少类之间的依赖 降低相互影响

五、简单示例:学生选课系统

需求

  • 学生可以选课、退课
  • 老师可以查看选课学生
  • 课程有人数限制

领域模型

1
2
3
学生(Student) --选修--> 课程(Course)
课程(Course) --由--> 老师(Teacher) 教授
选课(Enrollment) --记录--> 学生选课信息

类设计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Student {
private String id;
private String name;

public Enrollment enroll(Course course) {
if (course.hasCapacity()) {
return new Enrollment(this, course);
}
throw new RuntimeException("课程已满");
}
}

public class Course {
private String code;
private String name;
private int capacity;
private int enrolledCount;
private Teacher teacher;

public boolean hasCapacity() {
return enrolledCount < capacity;
}
}

调用流程

1
学生请求选课 → 选课Service → 检查课程容量 → 创建选课记录 → 更新课程人数 → 返回结果

六、总结

OOAD的核心过程:

  1. 分析:理解需求,识别对象
  2. 设计:定义类结构,确定关系
  3. 实现:按设计编码,保持结构

关键记住:先分析清楚业务,再设计实现方案

RAG 精度优化文档

问题描述

当用户询问”Vue 3 简介”时,匹配结果中出现了 SpringBoot 相关文档,说明向量检索的匹配度不够精确,存在以下问题:

  1. 没有设置相似度阈值 - 低相关度的文档也被返回
  2. 没有限制返回结果数量 - 返回结果过多,质量参差不齐
  3. 缺少相似度分数展示 - 无法判断文档与问题的相关程度
  4. 文本分块策略可能不够精细 - 分块大小和重叠度需要优化

优化方案

方案一:使用 SearchRequest 进行精确检索(推荐)

Spring AI 提供了 SearchRequest 类,可以设置相似度阈值和返回数量。

修改文件: src/main/java/com/snrt/knowledgebase/service/ChatService.java

修改内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import org.springframework.ai.vectorstore.SearchRequest;

private PromptResult buildPromptWithSources(String message, String knowledgeBaseId) {
if (knowledgeBaseId == null) {
log.debug("[Prompt构建] 未指定知识库,直接返回用户消息");
return new PromptResult(message, null);
}

log.info("[Prompt构建] 知识库ID: {}, 开始检索相关文档", knowledgeBaseId);

// 使用 SearchRequest 设置相似度阈值和返回数量
SearchRequest searchRequest = SearchRequest.builder()
.query(message)
.topK(20) // 增加初始检索数量,后续再过滤
.similarityThreshold(0.5) // 设置相似度阈值 0.5(可根据实际情况调整)
.build();

List<Document> relevantDocs = vectorStore.similaritySearch(searchRequest);
log.debug("[Prompt构建] 检索到 {} 个相关文档", relevantDocs.size());

// ... 后续逻辑保持不变
}

参数说明:

  • topK(20) - 初始检索20个文档,给后续过滤留有余地
  • similarityThreshold(0.5) - 只返回相似度大于等于0.5的文档(注意:pgvector返回的是余弦距离,需要转换为相似度,详见下文”余弦距离与相似度的关系”)

方案二:增加后置过滤和排序

在检索后增加基于相似度分数的过滤和排序,确保返回最相关的内容。

修改文件: src/main/java/com/snrt/knowledgebase/service/ChatService.java

修改内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
private PromptResult buildPromptWithSources(String message, String knowledgeBaseId) {
// ... 前面的代码 ...

List<Document> relevantDocs = vectorStore.similaritySearch(searchRequest);

// 按相似度分数排序(从高到低)
relevantDocs.sort((d1, d2) -> {
Double score1 = (Double) d1.getMetadata().get("distance");
Double score2 = (Double) d2.getMetadata().get("distance");
return Double.compare(score2 != null ? score2 : 0, score1 != null ? score1 : 0);
});

// 过滤并限制结果
List<Document> filteredDocs = relevantDocs.stream()
.filter(doc -> {
// 知识库ID过滤
Object kbId = doc.getMetadata().get(Constants.VectorStore.METADATA_KNOWLEDGE_BASE_ID);
boolean kbMatch = kbId != null && kbId.equals(knowledgeBaseId);

// 相似度过滤(如果 SearchRequest 的阈值不够,可以在这里二次过滤)
Double score = (Double) doc.getMetadata().get("distance");
boolean scoreMatch = score == null || score >= 0.7; // 相似度阈值

return kbMatch && scoreMatch;
})
.limit(Constants.Chat.MAX_CONTEXT_LENGTH)
.collect(Collectors.toList());

// ... 后续代码 ...
}

方案三:优化文档分块策略

调整分块大小和重叠度,提高检索精度。

修改文件: src/main/java/com/snrt/knowledgebase/constants/Constants.java

修改内容:

1
2
3
4
5
public static final class VectorStore {
public static final int CHUNK_SIZE = 800; // 从 1000 调整为 800
public static final int CHUNK_OVERLAP = 100; // 从 200 调整为 100
// ... 其他常量
}

调整说明:

  • 减小 CHUNK_SIZE 可以提高检索的精确度,但可能丢失上下文
  • 减小 CHUNK_OVERLAP 可以减少冗余,但需要权衡信息连续性

方案四:在返回结果中显示相似度分数

让用户了解每个来源的匹配程度,便于判断答案可信度。

修改文件: src/main/java/com/snrt/knowledgebase/service/ChatService.java

修改内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
List<DocumentSourceDTO> documentSources = docsByDocumentId.entrySet().stream()
.map(entry -> {
String docId = entry.getKey();
List<Document> docChunks = entry.getValue();
Document firstDoc = docChunks.get(0);

Object docName = firstDoc.getMetadata().get(Constants.VectorStore.METADATA_DOCUMENT_NAME);
Object kbName = firstDoc.getMetadata().get(Constants.VectorStore.METADATA_KNOWLEDGE_BASE_NAME);

// 获取相似度分数
Double score = (Double) firstDoc.getMetadata().get("distance");
if (score == null) {
score = 0.0;
}

// 收集所有分块的片段内容
List<String> snippets = docChunks.stream()
.map(doc -> {
String text = doc.getText();
return text.length() > 200 ? text.substring(0, 200) + "..." : text;
})
.collect(Collectors.toList());

return DocumentSourceDTO.builder()
.documentId(docId.equals("unknown") ? null : docId)
.documentName(docName != null ? docName.toString() : "未知文档")
.knowledgeBaseName(kbName != null ? kbName.toString() : "未知知识库")
.score(score) // 设置相似度分数
.snippet(snippets.isEmpty() ? "" : snippets.get(0))
.snippets(snippets)
.build();
})
// 按相似度分数排序
.sorted((s1, s2) -> Double.compare(s2.getScore() != null ? s2.getScore() : 0,
s1.getScore() != null ? s1.getScore() : 0))
.collect(Collectors.toList());

方案五:前端显示相似度分数

在前端界面展示每个来源的匹配度,帮助用户判断信息可靠性。

修改文件: knowledge-base-ui/src/components/chat/SourcesPanel.vue(或相关组件)

修改内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<template>
<div class="source-list">
<div class="source-item" v-for="source in sources" :key="source.documentId">
<div class="source-header">
<span class="source-name">{{ source.documentName }}</span>
<span class="source-score" v-if="source.score !== null">
匹配度: {{ (source.score * 100).toFixed(1) }}%
</span>
</div>
<div class="source-kb">{{ source.knowledgeBaseName }}</div>
<div class="source-snippet">{{ source.snippet }}</div>
</div>
</div>
</template>

<style scoped>
.source-header {
display: flex;
justify-content: space-between;
align-items: center;
}

.source-score {
font-size: 12px;
color: #409eff;
background-color: #ecf5ff;
padding: 2px 8px;
border-radius: 4px;
}
</style>

重要概念:余弦距离与相似度的关系

核心区别

在使用 pgvector 向量数据库时,需要特别注意 余弦距离(Cosine Distance)余弦相似度(Cosine Similarity) 的区别:

概念 计算公式 取值范围 含义
余弦距离 distance = 1 - similarity 0.0 ~ 2.0 越小越相似
余弦相似度 similarity = 1 - distance -1.0 ~ 1.0 越大越相似

实际应用中的转换

pgvector 默认返回的是 余弦距离,但我们在业务逻辑中通常使用 相似度 更直观:

1
2
3
4
5
6
// 从 metadata 中获取的是余弦距离
Object distanceObj = doc.getMetadata().get("distance");
double distance = ((Number) distanceObj).doubleValue();

// 转换为相似度(范围 0~1)
double similarity = 1.0 - distance;

分数对照表

余弦距离 余弦相似度 匹配程度 业务含义
0.0 1.0 (100%) 完全相同 理想状态,很少达到
0.2 0.8 (80%) 高度相似 非常相关,推荐阈值上限
0.3 0.7 (70%) 比较相似 相关,可作为严格阈值
0.45 0.55 (55%) 一般相似 Vue 3 文档实际分数
0.5 0.5 (50%) 基本相关 建议的最低阈值
0.7 0.3 (30%) 弱相关 通常应该过滤掉
1.0 0.0 (0%) 不相关 完全不同

阈值设置建议

根据实际测试数据(Vue 3 文档相似度约 0.51-0.55):

1
2
3
4
// 推荐阈值范围
double similarityThreshold = 0.5; // 平衡精度和召回率(当前推荐)
double similarityThreshold = 0.55; // 更严格,可能过滤部分内容
double similarityThreshold = 0.45; // 更宽松,可能包含更多内容

日志解读示例

1
2
[Prompt构建] 文档: test-rag-vue3-guide.md, 余弦距离: 0.452, 相似度: 0.55, 知识库匹配: true, 相似度匹配: true
[Prompt构建] 文档: test-rag-springboot.md, 余弦距离: 0.650, 相似度: 0.35, 知识库匹配: true, 相似度匹配: false ← 被过滤

解读:

  • Vue 3 文档:相似度 0.55 > 阈值 0.5,保留
  • SpringBoot 文档:相似度 0.35 < 阈值 0.5,过滤

实施建议

推荐实施顺序

  1. 首先实施方案一 - 使用 SearchRequest 设置相似度阈值(0.5)和返回数量
  2. 然后实施方案四 - 在返回结果中包含相似度分数,便于调试和展示
  3. 根据效果考虑方案三 - 调整分块策略

相似度阈值调整建议

注意: 以下阈值指的是余弦相似度(范围 0~1),不是余弦距离。

相似度阈值 对应余弦距离 适用场景 预期效果
0.45-0.5 0.5-0.55 宽松匹配 召回率高,可能包含弱相关内容
0.5-0.55 0.45-0.5 平衡推荐 Vue 3 文档实际分数范围,推荐默认值
0.6-0.7 0.3-0.4 严格匹配 精度高,可能漏掉部分内容
0.7+ <0.3 非常严格 只保留高度相关内容

验证方法

  1. 使用测试问题验证检索效果:

    • “Vue 3 简介” - 应该只返回 Vue 相关文档
    • “SpringBoot 自动配置原理” - 应该只返回 SpringBoot 相关文档
  2. 观察相似度分数分布,调整阈值到合适范围

  3. 监控检索结果数量和质量,确保用户体验


相关文件清单

  • src/main/java/com/snrt/knowledgebase/service/ChatService.java - 核心检索逻辑
  • src/main/java/com/snrt/knowledgebase/constants/Constants.java - 分块参数配置
  • src/main/java/com/snrt/knowledgebase/dto/DocumentSourceDTO.java - 文档来源DTO
  • knowledge-base-ui/src/components/chat/SourcesPanel.vue - 前端展示组件(路径可能不同)

注意事项

  1. 理解距离与相似度的区别:pgvector 返回的是余弦距离(越小越相似),业务逻辑通常使用相似度(越大越相似),转换公式为 similarity = 1 - distance
  2. 相似度阈值需要根据实际数据调整,不同 embedding 模型的分数分布可能不同
  3. 修改分块策略后需要重新索引文档,否则不会生效
  4. 建议先在测试环境验证效果,再应用到生产环境
  5. 可以添加配置项,让相似度阈值可动态调整,无需重启服务
  6. 观察日志中的实际分数,根据真实数据调整阈值,不要仅凭理论值设置

从基础到进阶,全面讲解 Vue 3 的组合式 API、响应式原理、组件开发、状态管理以及项目实战,打造现代化前端应用。

阅读全文 »

前言

本教程将介绍如何使用自定义的PowerShell脚本来一键安装OpenClaw。相比官方安装脚本,自定义脚本具有以下优势:

  • 📝 全程中文提示,更易理解
  • 🎨 彩色输出,状态一目了然
  • ⏳ 进度显示,知道安装进行到哪一步
  • 🔧 自动检测环境,提前发现潜在问题
  • 📦 支持离线安装包(可选)
  • 🇨🇳 支持国内镜像加速下载
  • ⚙️ 高度可定制化安装

目录

阅读全文 »
0%