
如何用 Cohere 嵌入与 Annoy 从头构建稠密检索语义搜索系统【免费下载链接】llm-cookbook面向开发者的 LLM 入门教程吴恩达大模型系列课程中文版项目地址: https://gitcode.com/GitHub_Trending/ll/llm-cookbook在 llm-cookbook 仓库的选修课程《Large Language Models with Semantic Search》中第四章 稠密搜索 Dense_Retrieval.ipynb 除了演示托管向量数据库的用法外还完整演示了另一条路径不依赖任何向量数据库服务用 Cohere 生成文本 Embeddings再用 Annoy 库为这些向量建立搜索索引从头搭出一个可用的语义搜索系统。本文按该 Notebook 中“三、从头构建语义向量库”一节的实际操作展开完成一条连续链路切块 → 生成 Embeddings → 构建 Annoy 索引 → 编写搜索函数并用示例 query 验证。前置条件只有一个 Python 环境Notebook 内核为 Python 3.11.7和一个 Cohere API Key。准备环境与 API KeyNotebook 中给出的安装命令如下weaviate-client用于第二章 Weaviate 托管数据库的对比演示从头构建这条路径不需要它pip install cohere pip install python-dotenv pip install Annoy后续代码还会import numpy as np和import pandas as pd如果环境里没有需要一并安装 numpy 和 pandas。API Key 的获取COHERE_API_KEY需要自行在 Cohere 官网注册获取。拿到之后写入项目下的.env文件Key 本身是读者必须提供的值COHERE_API_KEY你的Cohere密钥然后在代码中读取环境变量并创建 Cohere 客户端import os from dotenv import load_dotenv, find_dotenv _ load_dotenv(find_dotenv()) # 读取本地的 .env 文件 import cohere co cohere.Client(os.environ[COHERE_API_KEY]) # 获取 cohere 服务准备语料并按句子切块构建语义向量库前先对数据做预处理。Notebook 给出了切块的两点核心原因GPT 类模型一次只能处理固定最大序列长度内的 token超长文档必须切块才能被处理同时切块能为训练和检索提供更多独立样本。切块方式文档对比了两种按句子切块向量集中在句子级含义上基本不会超出上下文长度但会丢失段落和文档层面的上下文信息检索时可能错过更广泛的上下文。按段落切块向量覆盖整个上下文表达更全面但较大输入可能引入噪声、淡化单句的重要性且长段落仍可能超出上下文限制。本节主路径按句子切块。Notebook 使用两段示例语料英文是《Interstellar》电影的词条介绍变量text中文是“新民主主义革命4个发展阶段”的四个时期介绍变量texts_zh注意示例中文文本用半角句点.分隔句子。切块代码# 对句子进行拆分 texts text.split(.) # 清除回车和分行 texts np.array([t.strip( \n) for t in texts])切完的句子彼此孤立语义不完整。Notebook 的下一步是给每个分句前加上整段文字的主题提升模型可读性title Interstellar (film) texts np.array([f{title} {t} for t in texts])处理后每个块形如Interstellar (film) Interstellar is a 2014 epic science fiction film ...。换成自己的语料时title替换为该文档的标题即可。用 Cohere 生成 Embeddings把处理后的文本传入 Cohere 的embed接口生成向量response co.embed( textstexts.tolist() ).embeddings # 用 text 生成 Embeddings embeds np.array(response) print(embeds.shape)Notebook 的实际输出为(15, 4096)15 个句子块每个都被映射成 4096 维向量。中文语料在 Notebook 中显式指定了多语言模型结果不同response_zh co.embed( textstexts_zh.tolist(), modelembed-multilingual-v2.0 ).embeddings embeds_zh np.array(response_zh) print(embeds_zh.shape)对应输出(24, 768)。注意两点都来自文档使用的模型不同Embedding 维度会不同英文这段调用运行时控制台出现了default model on embed will be deprecated in the future, please specify a model in the request.的警告即embed的默认模型未来会下线请求中建议显式指定model。创建 Annoy 搜索索引Notebook 对 Annoy 的定位是建立一个数据结构在精度允许的条件下牺牲一点准确率换取比暴力搜索快得多的搜索速度它通过二叉树使单点查找时间复杂度为 O(log n)。用它构建索引from annoy import AnnoyIndex search_index AnnoyIndex(embeds.shape[1], angular) # 使用 Annoy 算法构建与 embeds 相同个数的索引 # 将所有的向量添加到搜索索引中 for i in range(len(embeds)): search_index.add_item(i, embeds[i]) search_index.build(10) # 10 棵树 search_index.save(test.ann)AnnoyIndex的第一个参数是向量维度取自embeds.shape[1]angular是度量方式build(10)建 10 棵树最后把索引存为test.ann文件。中文语料同理Notebook 生成了search_index_zh并保存为test_zh.ann。需要说明Notebook 后续的搜索函数直接使用内存中已 build 的search_indextest.ann文件是索引的落盘保存Notebook 中未展示从文件加载索引的代码。编写搜索函数并验证结果搜索函数包含三步把 query 映射到 Embeddings 空间用最近邻算法在索引中找距离最近的向量再生成结果。import pandas as pd def search(query): # 先把 query 映射到 Embeddings 空间 query_embed co.embed(texts[query]).embeddings # 找到映射空间内与 query_embed 最邻近的 text similar_item_ids search_index.get_nns_by_vector(query_embed[0], 3, include_distancesTrue) # 生成结果 results pd.DataFrame(data{texts: texts[similar_item_ids[0]], distance: similar_item_ids[1]}) print(texts[similar_item_ids[0]]) return results query How much did the film make? search(query)get_nns_by_vector的第二个参数 3 表示取最近的 3 个块include_distancesTrue使返回值同时包含向量 id 和距离。文档示例中该 query 返回的 3 个块依次为票房句The film had a worldwide gross over $677 million ...、首映句Interstellar premiered on October 26, 2014, in Los Angeles和发行格式句对应 distance 为 1.018955、1.145063、1.167268以上均为 Notebook 的文档示例输出不是固定预期值。排序第一的块恰好是与“影片票房”语义最相关的句子。中文侧的搜索函数与英文侧的唯一区别是embed时显式指定modelembed-multilingual-v2.0且结果取自texts_zh与search_index_zh。Notebook 用query 大革命时期是哪几年验证文档示例输出前三名为“大革命时期(1919年—1927年)”“土地革命战争时期(1927年—1937年)”“解放战争时期(1945年—1949年)”distance 为 0.370540、0.425366、0.426011。验证方式就是运行这两个 query检查返回的文本块是否与提问语义对应、distance 是否随行号给出结果以 pandas DataFrame 呈现方便进一步分析。边界与后续切块粒度直接影响检索效果按句子切块更稳地落在上下文限制内但可能错过段落级信息按段落切块语义更全面却可能引入噪声。Notebook 同时给出了按\n\n拆段落的替代代码属于可选分支需要时自行替换切块那一步。Annoy 是近似最近邻文档明确其以牺牲部分准确率换取速度召回的“最近”块不保证与精确暴力搜索完全一致。embed不传model时使用默认模型该默认模型按文档提示未来会下线建议像中文侧那样显式指定模型。这条“从头构建”的路径完成后该课程后续章节在此基础上做 重排 Rerank.ipynb 和 生成回答 Generating_Answers.ipynb即对检索结果重排序、再交给 LLM 基于检索内容生成答案。【免费下载链接】llm-cookbook面向开发者的 LLM 入门教程吴恩达大模型系列课程中文版项目地址: https://gitcode.com/GitHub_Trending/ll/llm-cookbook创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考