从零实战Milvus向量数据库:用RAG打造你的AI日记助手
本文将带你从零开始,基于 Milvus 向量数据库 + OpenAI Embedding + LangChain ,通过 RAG(检索增强生成) 技术,构建一个能理解你心情、回忆你过往的 AI 日记助手。全文包含完整可运行的代码,建议收藏后跟着敲一遍。
一、前言:为什么传统数据库"不懂"你的日记?
想象一下这个场景:你写了一年的日记,某天突然想问:"我最近做了什么让我感到快乐的事情?"
如果用传统的关系型数据库(如 MySQL),你只能依赖关键词匹配:
sql
SELECT * FROM diaries WHERE content LIKE '%快乐%' OR content LIKE '%开心%';
但问题是------你写日记时可能根本没用过"快乐"这个词,而是写了"今天天气很好,去公园散步了,心情愉快"。关键词匹配找不到这条记录,但语义上它完全相关。
这就是向量数据库 要解决的问题:让数据库理解语义,而不是只匹配字符。
二、核心概念:向量数据库与 Embedding
2.1 什么是向量数据库?
向量数据库(Vector Database)是一种专门用于存储和检索高维向量 的数据库。与传统数据库通过 ID 或关键词查询不同,向量数据库通过语义相似度来检索数据。
| 特性 | 传统数据库(MySQL) | 向量数据库(Milvus) |
|---|---|---|
| 存储内容 | 结构化数据(文本、数字) | 高维向量(Embedding) |
| 查询方式 | 精确匹配(=、LIKE) | 相似度搜索(ANN) |
| 查询语义 | "包含哪些字" | "意思有多接近" |
| 典型应用 | 订单查询、用户管理 | 语义搜索、推荐系统、RAG |
2.2 Embedding:把文字变成"坐标"
Embedding 是将文本、图片、音频等非结构化数据转换为高维数值向量的过程。语义相近的内容,在向量空间中的距离也更近。
scss
"今天天气很好" → [0.12, -0.34, 0.56, ..., 0.89] (1024维)
"今天阳光明媚" → [0.15, -0.31, 0.59, ..., 0.85] (1024维)
"向量数据库教程" → [-0.78, 0.45, -0.12, ..., 0.33] (1024维)
在 1024 维的空间里,前两个句子的向量距离很近,第三个则很远------这就是语义理解的基础。
2.3 RAG:检索增强生成
RAG(Retrieval-Augmented Generation)是一种将信息检索 与大语言模型生成结合的技术框架:
用户提问 → 向量化查询 → 向量库检索相关内容 → 将检索结果注入 Prompt → LLM 生成回答
RAG 的优势:
- 让大模型基于你的私有数据回答,而非仅依赖训练知识
- 减少"幻觉",回答可追溯、可验证
- 数据更新无需重新训练模型
三、Milvus:专为 AI 而生的向量数据库
3.1 为什么选择 Milvus?
Milvus 是一款开源的向量数据库,专为处理海量高维向量数据而设计。AI Agent、RAG 系统几乎都在用类似 Milvus 的 Vector Store。
| 核心能力 | 说明 |
|---|---|
| 海量向量存储 | 支持十亿级向量数据的存储与检索 |
| 近似最近邻搜索(ANN) | 毫秒级响应,平衡精度与速度 |
| 多种索引类型 | IVF_FLAT、HNSW、AUTOINDEX 等 |
| 多种相似度度量 | COSINE(余弦相似度)、L2(欧氏距离)、IP(内积) |
| 云原生架构 | 支持分布式部署,弹性扩缩容 |
3.2 Zilliz Cloud:零运维的托管服务
对于学习和中小项目,Zilliz Cloud 提供了基于 Milvus 的全托管服务:
- 无需自己部署和维护 Milvus 集群
- 提供免费的 Starter 套餐(适合学习)
- 通过
@zilliz/milvus2-sdk-node即可连接
四、实战:从零搭建 AI 日记助手
4.1 项目结构
bash
ai-diary-rag/
├── .env # 环境变量配置
├── package.json
├── main.mjs # Milvus 基础操作
├── index.mjs # 创建集合 + 插入日记数据
├── query.mjs # 语义搜索查询
└── rag.mjs # 完整 RAG 实现
4.2 环境准备
安装依赖:
bash
npm init -y
npm install @zilliz/milvus2-sdk-node @langchain/openai dotenv
.env 配置文件:
env
# Zilliz Cloud 连接信息
MILVUS_ADDRESS=https://your-cluster-url.zillizcloud.com
MILVUS_TOKEN=your_api_key
# OpenAI 配置
OPENAI_API_KEY=your_openai_api_key
OPENAI_BASE_URL=https://api.openai.com/v1
EMBEDDINGS_MODEL_NAME=text-embedding-3-large
MODEL_NAME=gpt-4o-mini
4.3 第一步:连接 Milvus 与基础操作
main.mjs 演示了 Milvus 的核心操作流程:连接 → 创建集合 → 建索引 → 插入数据 → 向量搜索。
javascript
import {
MilvusClient,
IndexType, // 索引类型
MetricType // 相似度度量方式
} from '@zilliz/milvus2-sdk-node';
import 'dotenv/config';
const ADDRESS = process.env.MILVUS_ADDRESS;
const TOKEN = process.env.MILVUS_TOKEN;
async function main() {
const client = new MilvusClient({
address: ADDRESS,
token: TOKEN
});
console.log('正在连接 Zilliz Cloud...');
const checkHealth = await client.checkHealth();
if (!checkHealth.isHealthy) {
console.error('连接失败', checkHealth.reasons);
return;
}
console.log('连接成功,集群状态正常。');
const COLLECTION_NAME = 'test';
const DIMENSION = 4;
try {
// 1. 创建集合(类似 MySQL 的表)
// await client.createCollection({
// collection_name: COLLECTION_NAME,
// dimension: DIMENSION,
// auto_id: true
// });
// console.log('集合创建成功');
// 2. 创建索引------向量搜索的"加速器"
// await client.createIndex({
// collection_name: COLLECTION_NAME,
// field_name: 'vector',
// index_type: IndexType.AUTOINDEX,
// metric_type: MetricType.COSINE
// });
// console.log('索引创建成功');
// 3. 插入数据
// const data = [
// { vector: [0.1, 0.2, 0.3, 0.4], content: '这是第一条数据' },
// { vector: [0.5, 0.6, 0.7, 0.8], content: '这是第二条数据' }
// ];
// const insertRes = await client.insert({
// collection_name: COLLECTION_NAME,
// data
// });
// console.log('插入成功', insertRes.IDs);
// 4. 向量搜索------找语义最相近的数据
const searchRes = await client.search({
collection_name: COLLECTION_NAME,
data: [[0.5, 0.5, 0.6, 0.8]], // 查询向量
limit: 2, // 返回前2个最相似的结果
output_fields: ['content'] // 返回的字段
});
console.log(JSON.stringify(searchRes.results, null, 2));
} catch (err) {
console.log('操作出错', err.message);
}
}
main().catch(console.error);
关键概念解析:
| 概念 | 作用 | 类比 |
|---|---|---|
| Collection(集合) | 存储向量和字段的容器 | MySQL 的 Table |
| Vector(向量) | 文本/图片的数值化表示 | 数据的"指纹" |
| Index(索引) | 加速向量检索的数据结构 | 书籍的目录 |
| MetricType | 计算向量间相似度的方法 | 衡量"有多像"的标尺 |
为什么必须建索引?
没有索引时,每次查询都要把库里所有向量和查询向量逐一计算相似度(O(n)),数据量大了慢得没法用。索引就像图书馆的分类系统,能迅速缩小查询范围,将搜索复杂度降到 O(log n) 甚至更低。
4.4 第二步:设计 Schema 与插入日记数据
在实际项目中,我们不能只存向量,还需要存储原始内容、日期、心情、标签等元数据。index.mjs 展示了如何定义完整的 Schema:
javascript
import 'dotenv/config';
import {
MilvusClient,
MetricType,
IndexType,
DataType // 字段数据类型约束
} from '@zilliz/milvus2-sdk-node';
import { OpenAIEmbeddings } from '@langchain/openai';
const ADDRESS = process.env.MILVUS_ADDRESS;
const TOKEN = process.env.MILVUS_TOKEN;
const COLLECTION_NAME = 'ai_diary'; // 集合名称
const VECTOR_DIM = 1024; // 向量维度(text-embedding-3-large 支持)
// 初始化 Embedding 模型
const embeddings = new OpenAIEmbeddings({
apiKey: process.env.OPENAI_API_KEY,
model: process.env.EMBEDDINGS_MODEL_NAME,
configuration: {
baseURL: process.env.OPENAI_BASE_URL
},
dimensions: VECTOR_DIM
});
const client = new MilvusClient({
address: ADDRESS,
token: TOKEN
});
// 将文本转换为向量
const getEmbedding = async (text) => {
const result = await embeddings.embedQuery(text);
return result;
};
async function main() {
console.log('正在连接 Zilliz Cloud...');
const checkHealth = await client.checkHealth();
if (!checkHealth.isHealthy) {
console.error('连接失败', checkHealth.reasons);
return;
}
console.log('连接成功,集群状态正常。');
// ===== 1. 创建集合(带完整 Schema)=====
// await client.createCollection({
// collection_name: COLLECTION_NAME,
// fields: [
// {
// name: 'id',
// data_type: DataType.VarChar,
// max_length: 50,
// is_primary_key: true
// },
// {
// name: 'vector',
// data_type: DataType.FloatVector,
// dim: VECTOR_DIM
// },
// {
// name: 'content',
// data_type: DataType.VarChar,
// max_length: 5000
// },
// {
// name: 'date',
// data_type: DataType.VarChar,
// max_length: 50
// },
// {
// name: 'mood',
// data_type: DataType.VarChar,
// max_length: 50
// },
// {
// name: 'tags',
// data_type: DataType.Array,
// element_type: DataType.VarChar,
// max_capacity: 10,
// max_length: 50
// }
// ]
// });
// ===== 2. 创建索引 =====
// await client.createIndex({
// collection_name: COLLECTION_NAME,
// field_name: 'vector',
// index_type: IndexType.IVF_FLAT, // 聚簇索引,毫秒级响应
// metric_type: MetricType.COSINE // 余弦相似度
// });
// ===== 3. 加载集合到内存 =====
console.log('Loading collection...');
await client.loadCollection({ collection_name: COLLECTION_NAME });
console.log('Collection loaded');
// ===== 4. 准备日记数据 =====
const diaryContents = [
{
id: 'diary_001',
content: '今天天气很好,去公园散步了,心情愉快。看到了很多花开了,春天真美好。',
date: '2026-01-10',
mood: 'happy',
tags: ['生活', '散步']
},
{
id: 'diary_002',
content: '今天工作很忙,完成了一个重要的项目里程碑。团队合作很愉快,感觉很有成就感。',
date: '2026-01-11',
mood: 'excited',
tags: ['工作', '成就']
},
{
id: 'diary_003',
content: '周末和朋友去爬山,天气很好,心情也很放松。享受大自然的感觉真好。',
date: '2026-01-12',
mood: 'relaxed',
tags: ['户外', '朋友']
},
{
id: 'diary_004',
content: '今天学习了 Milvus 向量数据库,感觉很有意思。向量搜索技术真的很强大。',
date: '2026-01-12',
mood: 'curious',
tags: ['学习', '技术']
},
{
id: 'diary_005',
content: '晚上做了一顿丰盛的晚餐,尝试了新菜谱。家人都说很好吃,很有成就感。',
date: '2026-01-13',
mood: 'proud',
tags: ['美食', '家庭']
}
];
// ===== 5. 生成 Embedding 并插入 =====
console.log('Generating embeddings...');
const diaryData = await Promise.all(
diaryContents.map(async (diary) => ({
...diary,
vector: await getEmbedding(diary.content) // 将日记内容转为向量
}))
);
const insertResult = await client.insert({
collection_name: COLLECTION_NAME,
data: diaryData
});
console.log(`${insertResult.insert_cnt} 条记录成功插入。`);
}
main().catch(console.error);
Schema 设计要点:
| 字段 | 类型 | 作用 |
|---|---|---|
id |
VarChar (主键) | 唯一标识每条日记 |
vector |
FloatVector | 内容的 Embedding 向量,用于语义搜索 |
content |
VarChar | 日记原始文本 |
date |
VarChar | 日期 |
mood |
VarChar | 心情标签 |
tags |
Array | 分类标签数组 |
4.5 第三步:语义搜索------用自然语言找日记
有了向量化数据,我们就可以用自然语言查询,让数据库"理解"我们的意图。query.mjs 演示了语义搜索:
javascript
import 'dotenv/config';
import {
MilvusClient,
MetricType
} from '@zilliz/milvus2-sdk-node';
import { OpenAIEmbeddings } from '@langchain/openai';
const ADDRESS = process.env.MILVUS_ADDRESS;
const TOKEN = process.env.MILVUS_TOKEN;
const COLLECTION_NAME = 'ai_diary';
const VECTOR_DIM = 1024;
const embeddings = new OpenAIEmbeddings({
apiKey: process.env.OPENAI_API_KEY,
model: process.env.EMBEDDINGS_MODEL_NAME,
configuration: { baseURL: process.env.OPENAI_BASE_URL },
dimensions: VECTOR_DIM
});
const client = new MilvusClient({ address: ADDRESS, token: TOKEN });
const getEmbedding = async (text) => {
const result = await embeddings.embedQuery(text);
return result;
};
async function main() {
try {
console.log('Connecting to Milvus...');
await client.connectPromise;
console.log('Connected');
const query = '我想看看关于户外活动的日记';
console.log(`QUERY: ${query}`);
// 1. 将查询语句转为向量
const queryVector = await getEmbedding(query);
// 2. 在向量空间中搜索最相近的日记
const searchResult = await client.search({
collection_name: COLLECTION_NAME,
vector: queryVector, // 查询向量
limit: 2, // 返回最相似的2条
metric_type: MetricType.COSINE, // 使用余弦相似度
output_fields: ['id', 'content', 'date', 'mood', 'tags']
});
console.log(`Found ${searchResult.results.length} results`);
searchResult.results.forEach((item, index) => {
console.log(`${index + 1}. [Score: ${item.score.toFixed(4)}]`);
console.log(`
ID: ${item.id}
Date: ${item.date}
Mood: ${item.mood}
Tags: ${item.tags?.join(', ')}
Content: ${item.content}
`);
});
} catch (err) {
console.error('Search error:', err.message);
}
}
main().catch(console.error);
运行结果示例:
yaml
QUERY: 我想看看关于户外活动的日记
Found 2 results
1. [Score: 0.8234]
ID: diary_003
Date: 2026-01-12
Mood: relaxed
Tags: 户外, 朋友
Content: 周末和朋友去爬山,天气很好,心情也很放松。享受大自然的感觉真好。
2. [Score: 0.6123]
ID: diary_001
Date: 2026-01-10
Mood: happy
Tags: 生活, 散步
Content: 今天天气很好,去公园散步了,心情愉快。看到了很多花开了,春天真美好。
注意: 查询语句"户外活动"并没有直接出现在日记内容中,但 Milvus 通过向量相似度找到了"爬山"和"公园散步"这两条语义相关的日记------这就是语义搜索的威力。
4.6 第四步:RAG 完整实现------让 AI 读懂你的日记
最后,我们将检索到的日记内容注入 Prompt,让大语言模型生成温暖、个性化的回答。rag.mjs 是完整的 RAG 流水线:
javascript
import 'dotenv/config';
import { MilvusClient, MetricType } from '@zilliz/milvus2-sdk-node';
import { ChatOpenAI, OpenAIEmbeddings } from '@langchain/openai';
const ADDRESS = process.env.MILVUS_ADDRESS;
const TOKEN = process.env.MILVUS_TOKEN;
const COLLECTION_NAME = 'ai_diary';
const VECTOR_DIM = 1024;
// 初始化 Embedding 模型
const embeddings = new OpenAIEmbeddings({
apiKey: process.env.OPENAI_API_KEY,
model: process.env.EMBEDDINGS_MODEL_NAME,
configuration: { baseURL: process.env.OPENAI_BASE_URL },
dimensions: VECTOR_DIM
});
// 初始化 LLM
const model = new ChatOpenAI({
temperature: 0.1, // 低温度,回答更稳定
model: process.env.MODEL_NAME,
apiKey: process.env.OPENAI_API_KEY,
configuration: { baseURL: process.env.OPENAI_BASE_URL }
});
const client = new MilvusClient({ address: ADDRESS, token: TOKEN });
const getEmbedding = async (text) => {
const result = await embeddings.embedQuery(text);
return result;
};
// ===== Step 1: 检索相关日记(Retrieval)=====
async function retrieveRelevantDiaries(question, k = 2) {
try {
const queryVector = await getEmbedding(question);
const searchResult = await client.search({
collection_name: COLLECTION_NAME,
vector: queryVector,
limit: k,
metric_type: MetricType.COSINE,
output_fields: ['id', 'content', 'date', 'mood', 'tags']
});
return searchResult.results;
} catch (err) {
console.log('检索日记时出错', err.message);
return [];
}
}
// ===== Step 2: 生成回答(Generation)=====
async function answerDiaryQuestion(question, k = 2) {
try {
console.log('='.repeat(80));
console.log(`问题: ${question}`);
console.log('='.repeat(80));
// R - 检索
console.log('检索相关日记...');
const retrievedDiaries = await retrieveRelevantDiaries(question, k);
if (retrievedDiaries.length === 0) {
console.log('未找到相关日记');
return;
}
retrievedDiaries.forEach((diary, i) => {
console.log(`日记${i + 1} 相似度: ${diary.score.toFixed(4)}\n内容:${diary.content}`);
});
// 构建上下文
const context = retrievedDiaries
.map((diary, i) => `
[日记 ${i + 1}]
日期:${diary.date}
心情: ${diary.mood}
标签: ${diary.tags?.join(', ')}
内容:${diary.content}
`).join('\n\n----\n\n');
// A - 增强 Prompt
const prompt = `你是一个温暖贴心的AI日记助手。基于用户的日记内容回答问题,用亲切自然的语言。
请根据以下日记内容回答问题:
${context}
用户问题: ${question}
回答要求:
1. 如果日记中有相关信息,请结合日记内容给出详细、温暖的回答。
2. 可以总结多篇日记的内容,找出共同点或趋势。
3. 如果日记中没有相关信息,请温和告知用户。
4. 用第一人称"你"来称呼日记的作者。
5. 回答要有同理心,让用户感到被理解和关心。
AI助手的回答:`;
// G - 生成
console.log('\n[AI 回答]');
const response = await model.invoke(prompt);
console.log(response.content);
} catch (err) {
console.log('回答生成出错:', err.message);
}
}
async function main() {
try {
console.log('连接到 Milvus...');
await client.connectPromise;
console.log('已连接');
// 测试提问
await answerDiaryQuestion('我最近做了什么让我感到快乐的事情?', 2);
} catch (err) {
console.error('Main error:', err.message);
}
}
main().catch(console.error);
RAG 流程图:
arduino
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ 用户提问 │────▶│ Embedding 编码 │────▶│ Milvus 向量检索 │
│ "最近快乐的事?" │ │ 转为查询向量 │ │ 找最相似的日记 │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│
▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ LLM 生成回答 │◀────│ 构建 RAG Prompt │◀────│ 检索结果上下文 │
│ 温暖贴心回复 │ │ 日记内容 + 问题 │ │ top-k 相关日记 │
└─────────────────┘ └─────────────────┘ └─────────────────┘
运行结果示例:
markdown
================================================================================
问题: 我最近做了什么让我感到快乐的事情?
================================================================================
检索相关日记...
日记1 相似度: 0.8532
内容:今天工作很忙,完成了一个重要的项目里程碑。团队合作很愉快,感觉很有成就感。
日记2 相似度: 0.8211
内容:今天天气很好,去公园散步了,心情愉快。看到了很多花开了,春天真美好。
[AI 回答]
看到你最近有不少开心的时刻呢!
1月10日那天,你去公园散步,看到花开,感受春天的美好,那种与自然亲近的愉悦真的很珍贵。
1月11日,你完成了重要的项目里程碑,团队合作的成就感一定让你倍感充实。工作中有这样的高光时刻,说明你真的很棒!
你似乎在工作和生活中都能找到让自己快乐的方式,继续保持这份感知幸福的能力吧~
五、原理深入:为什么 RAG 能"理解"你的日记?
5.1 余弦相似度:度量语义相近程度
Milvus 默认使用 余弦相似度(COSINE) 来衡量两个向量的相似程度:
css
cos(θ) = (A · B) / (||A|| × ||B||)
- 值域:
[-1, 1] 1表示完全同向(语义完全相同)0表示正交(语义无关)-1表示反向(语义相反)
在日记场景中,"爬山"和"户外活动"的向量夹角很小,余弦相似度接近 1;而"爬山"和"数据库"的夹角很大,相似度接近 0。
5.2 IVF_FLAT 索引:聚簇加速
IVF_FLAT(Inverted File with Flat)是一种经典的 ANN 索引:
- 聚簇:将所有向量用 K-Means 分成 nlist 个簇
- 查询:先找到查询向量最近的几个簇中心,再在这些簇内精确计算相似度
- 优势:将全量搜索变为"找簇 + 簇内搜索",大幅降低计算量
5.3 Embedding 模型的选择
| 模型 | 维度 | 特点 | 适用场景 |
|---|---|---|---|
| text-embedding-3-small | 1536 | 速度快、成本低 | 一般语义搜索 |
| text-embedding-3-large | 3072(可降维) | 精度高、支持降维 | 高精度 RAG |
| text-embedding-ada-002 | 1536 | 上一代模型 | 兼容旧系统 |
降维技巧 :text-embedding-3-large 原生输出 3072 维,但可以通过 dimensions 参数降维到 1024 或 256,在精度损失很小的情况下节省存储和计算成本。
六、最佳实践与优化建议
6.1 数据建模建议
javascript
// ✅ 好的做法:将结构化字段和向量分离存储
// MySQL 存结构化数据(用户、日期、标签)
// Milvus 存向量 + 关联 ID
// ❌ 避免:所有数据都塞进 Milvus
// Milvus 擅长向量检索,不适合复杂的事务查询
混合架构:
| 数据类型 | 存储 | 查询方式 |
|---|---|---|
| 用户、日期、标签等结构化数据 | MySQL / PostgreSQL | SQL 精确查询 |
| 内容向量 | Milvus | 语义相似度搜索 |
| 关联关系 | 两者通过 ID 关联 | 联合查询 |
6.2 性能优化
javascript
// 1. 批量插入,减少网络往返
const insertResult = await client.insert({
collection_name: COLLECTION_NAME,
data: largeDataArray // 一次插入多条
});
// 2. 合理设置 limit,避免返回过多数据
const searchResult = await client.search({
// ...
limit: 5, // 只取最相关的 top-5
});
// 3. 加载集合到内存(搜索前必须)
await client.loadCollection({ collection_name: COLLECTION_NAME });
6.3 成本优化
- 使用 Zilliz Cloud 免费套餐 进行学习和原型开发
- 降维存储:将 3072 维降至 1024 维,存储和计算成本降低 66%
- 合理设置索引参数 :
nlist越大查询越快,但构建索引越慢
6.4 安全注意事项
javascript
// ✅ 使用环境变量管理密钥
const TOKEN = process.env.MILVUS_TOKEN;
// ❌ 永远不要硬编码密钥
const TOKEN = 'your-actual-token-here'; // 危险!
七、总结与展望
7.1 本文核心知识点回顾
markdown
传统数据库 向量数据库(Milvus)
│ │
▼ ▼
存储文本/数字 ──▶ Embedding ──▶ 存储高维向量
关键词查询 语义相似度搜索
精确匹配 理解上下文含义
│ │
└────────── RAG 结合 ──────────┘
│
▼
检索相关内容 + LLM 生成 = 智能问答
7.2 本文实现的功能
- 向量数据库基础操作:连接 Milvus、创建集合、建索引、插入数据
- Schema 设计:多字段集合,支持结构化数据 + 向量
- Embedding 生成:使用 LangChain OpenAIEmbeddings 将文本向量化
- 语义搜索:自然语言查询,无需关键词匹配
- 完整 RAG 流水线:检索 + 增强 Prompt + LLM 生成
7.3 可扩展的方向
| 方向 | 思路 |
|---|---|
| 多模态日记 | 将照片也通过 CLIP 模型向量化,支持"找那张海边的照片" |
| 时间线总结 | 结合日期字段,实现"帮我总结一下2026年1月的心情变化" |
| 情绪分析 | 接入情感分析模型,自动标注心情,无需手动输入 |
| 多用户隔离 | 在 Schema 中增加 user_id 字段,实现多用户数据隔离 |
| 混合搜索 | 结合标签过滤 + 向量相似度,实现"找户外相关且心情愉快的日记" |
八、参考资源
如果这篇文章对你有帮助,欢迎点赞 和收藏!