目录7. 文本向量7.1 嵌入与嵌入模型Embedding and Embedding Models7.1.1 什么是向量7.1.2 嵌入模型应用场景7.2 Embeddings 嵌入模型类7.2.1 定义嵌入模型7.2.2 嵌入文档列表离线批量索引7.2.3 嵌入单个查询在线实时二者区别一目了然7. 文本向量7.1 嵌入与嵌入模型Embedding and Embedding Models计算机天生擅长处理数字但不理解文字、图片的含义。嵌入Embedding的核心思想就是将人类世界的符号如单词、句子、产品、用户、图片转换为计算机能够理解的数值形式即向量本质上是一个数字列表并且要求这种转换能够保留原始符号的语义和关系。我们可以把它想象成一个翻译过程把人类语言“翻译”成计算机的“数学语言”。类比苹果 ──[嵌入模型]──▶ [0.023, 0.487, -0.129, ..., 0.325]香蕉 ──[嵌入模型]──▶ [0.019, 0.502, -0.118, ..., 0.341]因为苹果和香蕉语义相近都是水果这两个向量的方向很接近我们之前一直用的大语言模型是生成式模型。它理解输入并生成新的文本回答问题、写文章。它内部实际上也使用嵌入技术来理解输入但最终目标是“创造”。而嵌入模型Embedding Models是表示型模型。它的目标不是生成文本而是为输入的文本创建一个最佳的、富含语义的数值表示向量。如 OpenAI 的 text-embedding-3-large 嵌入模型Google 的 gemini-embedding-001 嵌入模型阿里的 Qwen3-Embedding-8B 嵌入模型等。生成式模型 vs 表示型模型7.1.1 什么是向量首先我们要知道嵌入的结果是就是一个向量它本质上是一个数字列表一维数组。例如[0.023, 0.487, -0.129, ..., 0.325] 。对于向量来说有两个关键概念需要了解向量维度嵌入结果得到的列表长度是固定的称为向量的“维度”。例如OpenAI 的 text-embeddingada-002 模型会生成一个 1536 维的向量 text-embedding-3-large 模型会生成一个 3072 维的向量。维度越高通常能捕捉更细微的语义信息但也需要更多的计算和存储资源。向量空间想象一个无限延伸的、拥有无数个维度的宇宙这个宇宙就是一个向量空间。这有点抽象可以想象一下• 在三维世界里一个点可以用 (x, y, z) 坐标表示例如 (2, 5, -1) 。• 在机器学习的高维向量空间中一个点可能是 (0.1, 0.7, -0.2, 0.4, ..., 0.02) 一个有几百或几千个数字的坐标。在这个空间里每个点即每个向量都能代表一个概念。例如在嵌入模型中一个点可以代表一个单词、一句话、一张图片、一个用户、一部电影等。到这里向量空间的威力就能体现出来我们可以用数学来度量语义。可以通过计算两个向量之间的“距离”或“相似度”来实现这一点。如何度量距离• 欧氏距离Euclidean Distance就是我们高中几何学的两点之间的直线距离。距离越短相似度越高。• 余弦相似度Cosine Similarity它忽略向量的绝对长度大小只关注两个向量在方向上的差异。在文本和语义的世界里“方向”代表“含义”而“长度”往往只代表“文本的长度”或“词汇的多少”。换句话说余弦相似度关注的是“你们是否指向同一个方向” / “你们是否代表同一个含义”苹果很好吃 的向量苹果很好吃很好吃很好吃 的向量更长但方向相同欧氏距离距离很大不相似— 不对余弦相似度方向一致很相似— 对因此在捕捉语义上的相似性上余弦相似度是更常用的度量方式。我们又能反推出由于使用向量来绘制向量空间而向量是有维度的维度越高则更能捕捉极其细微和复杂的语义差别比如“高兴”和“喜悦”的区别。这能干什么这能解决一个传统数据库如MySQL不擅长的问题基于内容的相似性搜索而不是基于精确匹配的查询。传统 MySQL搜一种红色的水果 → 找不到苹果的文档没有关键词匹配语义搜索embed(一种红色的水果) 和 embed(苹果是一种常见的水果) 的向量方向相近 → 能找到7.1.2 嵌入模型应用场景对于对于嵌入模型实际上在示例选择器部分我们已经使用过。当时使用的场景就是可以根据语义相似性完成示例的筛选。根据嵌入的特性由此延伸出了许多嵌入模型在 AI 应用的使用场景• 语义搜索Semantic Search传统搜索依赖关键词匹配搜“苹果” 只能找到包含“苹果” 这个词的文档。语义搜索则能将查询如“一种红色的水果” 和文档库中的所有文档都转换为向量。然后计算查询向量与所有文档向量的相似度返回最相似的文档。这样即使文档里没有“红色” 和“水果” 这些词但只要它是关于“苹果” 的就能被找到。下图为我们展示了借助嵌入模型进行文档搜索的过程1. 为多文档生成其各自的向量2. 为搜索查询语句生成向量3. 衡量查询向量与每个文档向量之间的相似性得到相似度最高的文档。• 检索增强生成Retrieval-Augmented Generation, RAG这是当前大语言模型应用的核心模式。当用户向 LLM 提问时系统首先使用嵌入模型在知识库如公司内部文档中进行语义搜索找到最相关的内容然后将这些内容和问题一起交给 LLM 来生成答案。这极大地提高了答案的准确性和时效性。• 推荐系统Recommendation Systems将用户根据其历史行为、偏好和物品商品、电影、新闻都转换为向量。喜欢相似物品的用户其向量会接近相似的物品其向量也会接近。通过计算用户和物品向量的相似度就可以进行精准推荐。• 异常检测Anomaly Detection:正常数据的向量通常会聚集在一起。如果一个新数据的向量远离大多数向量的聚集区它就可能是一个异常点如垃圾邮件、欺诈交易。7.2 Embeddings 嵌入模型类在 LangChain 中有很多的嵌入模型提供方使用不同的模型提供方需要安装为其各自包例如• OpenAI pip install -U langchain-openai• Ollama pip install -U langchain-ollama• Google Gemini pip install -U langchain-google-genai• 更多见LangChain Python integrations - Docs by LangChain7.2.1 定义嵌入模型在这里我们选择 嵌入用 Ollama 来进行后续操作。这里介绍一下定义 OpenAI 下的嵌入模型使用class langchain_openai.embeddings.base.OpenAIEmbeddings官方接口介绍见langchain_openai | LangChain Reference定义 OpenAIEmbeddings 嵌入模型类与定义聊天模型类似如下所示from langchain_openai import OpenAIEmbeddings embeddings OpenAIEmbeddings( modeltext-embedding-3-large, # 3072 维高质量向量 )在 LangChain 框架中基础 Embeddings 类 OpenAIEmbeddings 继承了它设计了两个核心方法来处理文本嵌入,分别对应两种场景• .embed_documents() 用于处理文档Documents 。它的输入是多个文本。例如要将一个知识库里的所有段落都转换成向量后存入数据库就会使用这个方法。◦ 它返回一个【二维列表】List[List[float]] 。外层列表的每个元素对应一个输入文档内层列表则是该文档的向量表示。• .embed_query() 用于处理查询Query 。它的输入是单个文本一个字符串str。例如当用户提出一个问题时需要将这个问题转换成向量以便在数据库中搜索相似的文档段落就会使用这个方法。◦ 它返回一个【一维列表】里面是浮点数 List[float] 代表单个查询文本的向量。其实分别对应下图中文档与查询的向量生成之所以设计成两个方法是因为某些嵌入模型提供商如 OpenAI、Cohere 等会针对“被搜索的文档” 和“搜索查询本身” 采用不同的优化策略和模型。即使底层是同一个模型也可能对两者进行不同的预处理例如添加不同的指令前缀以获得更好的搜索效果。7.2.2 嵌入文档列表离线批量索引embed_documents的语义是“索引”。它的目的是预处理大量文本为它们创建向量表示以便后续被搜索。这一般是一个离线、批量处理的过程。代码如下完整的加载 → 分割 → 嵌入流程from langchain_ollama import OllamaEmbeddings from langchain_community.document_loaders import UnstructuredMarkdownLoader from langchain_text_splitters import CharacterTextSplitter # 1. 加载文档 loader UnstructuredMarkdownLoader(./file/Day 1 详解总览.md) data loader.load() # 2. 分割成小块 text_splitter CharacterTextSplitter.from_tiktoken_encoder( encoding_namecl100k_base, chunk_size200, chunk_overlap50 ) documents text_splitter.split_documents(data) # 3. 提取纯文本 texts [doc.page_content for doc in documents] # 4. 嵌入把所有文档块转成向量用本地 Ollama免费 embeddings OllamaEmbeddings(modelnomic-embed-text) documents_vector embeddings.embed_documents(texts) print(f文档数量{len(documents)}生成了 {len(documents_vector)} 个向量) print(f第一个向量维度{len(documents_vector[0])}) # nomic-embed-text 是 768 维 print(f第二个向量维度{len(documents_vector[1])})7.2.3 嵌入单个查询在线实时embed_query的语义是“搜索”。它的目的是在用户发起请求时实时地将一个问题或指令转换为向量用于在已索引的文档向量中进行检索。这是一个在线、实时、按需处理的过程。为单个查询生成向量的代码如下from langchain_ollama import OllamaEmbeddings from langchain_community.document_loaders import UnstructuredMarkdownLoader from langchain_text_splitters import CharacterTextSplitter # 1. 加载文档 loader UnstructuredMarkdownLoader(./file/Day 1 详解总览.md) data loader.load() # 2. 分割成小块 text_splitter CharacterTextSplitter.from_tiktoken_encoder( encoding_namecl100k_base, chunk_size200, chunk_overlap50 ) documents text_splitter.split_documents(data) # 3. 提取纯文本 texts [doc.page_content for doc in documents] # 4. 嵌入把所有文档块转成向量用本地 Ollama免费 # embeddings OllamaEmbeddings(modelnomic-embed-text) # documents_vector embeddings.embed_documents(texts) # # print(f文档数量{len(documents)}生成了 {len(documents_vector)} 个向量) # print(f第一个向量维度{len(documents_vector[0])}) # nomic-embed-text 是 768 维 # print(f第二个向量维度{len(documents_vector[1])}) # 5. 嵌入单个查询在线实时 embeddings OllamaEmbeddings(modelnomic-embed-text) query_vector embeddings.embed_query(### WEBSOCKET通信) print(f\n查询向量维度{len(query_vector)}) print(f查询向量前五个值{query_vector[:5]})二者区别一目了然# embed_documents多文本 → 二维列表embeddings.embed_documents([文本1, 文本2, 文本3]) 我们代码里传的一个遍历结果text其本质是一样的# → [[0.1, 0.2, ...], [0.3, 0.4, ...], [0.5, 0.6, ...]]# embed_query单文本 → 一维列表embeddings.embed_query(一个问题)# → [0.1, 0.2, 0.3, ...]之前写过的 test25/test26 中OllamaEmbeddings(modelnomic-embed-text)这就是一个嵌入模型。SemanticSimilarityExampleSelector 内部就是用它把示例转成向量再计算余弦相似度来挑选的。happy ──[nomic-embed-text]──▶ [0.12, -0.34, ...]worried ──[nomic-embed-text]──▶ [0.09, -0.38, ...]余弦相似度 → 0.95看作同一类→情绪词import sys import numpy as np from langchain_ollama import OllamaEmbeddings sys.stdout.reconfigure(encodingutf-8) # 1. 定义嵌入模型本地免费768维向量 embeddings OllamaEmbeddings(modelnomic-embed-text) # 2. 把文字分别转成向量 # embed_query单个文本 → 一维列表768个浮点数 vec_happy embeddings.embed_query(happy) vec_worried embeddings.embed_query(worried) vec_tall embeddings.embed_query(tall) print(f向量维度{len(vec_happy)} 维) print(fhappy 前5个值{[round(v, 4) for v in vec_happy[:5]]}) print(fworried 前5个值{[round(v, 4) for v in vec_worried[:5]]}) print(ftall 前5个值{[round(v, 4) for v in vec_tall[:5]]}) # 3. 计算余弦相似度 def cosine_similarity(a, b): 余弦相似度 两个向量夹角的余弦值。 1.0 方向完全相同语义最接近 0.0 方向垂直语义无关 -1.0 方向完全相反 为什么不用欧氏距离 happy → 向量短 happy happy → 向量长但方向相同 余弦只看方向不看长度 → 正确判断为语义相同 a np.array(a) b np.array(b) return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b)) sim_happy_worried cosine_similarity(vec_happy, vec_worried) sim_happy_tall cosine_similarity(vec_happy, vec_tall) sim_worried_tall cosine_similarity(vec_worried, vec_tall) print(f\n余弦相似度) print(f happy vs worried : {sim_happy_worried:.4f} (都是情绪词,方向接近)) print(f happy vs tall : {sim_happy_tall:.4f} (情绪 vs 身高,无关)) print(f worried vs tall : {sim_worried_tall:.4f} (同上))ok,终于写完了继续学习向量存储