普通视图

发现新文章,点击刷新页面。
昨天 — 2025年1月17日首页

Langchian.js |Embedding & Vector Store👈| 数据向量化后这样储存😱

作者 浪遏
2025年1月17日 23:01

前言

书接上文 , 学习了分割多个文档对象 , 这一次要学习

  • 如何将数据向量化 ? 😍
  • 向量化的数据持久化储存 ? 😍

也就是说 ,下面这张图 ,要 over 了 , 🤡👈

Embedding

langchain.js 在文本处理领域 ,不仅提供我前面所学的文本分割与转换 , 也为文本的向量化提供了支持 , 这不禁让应用开发者尖叫 ~ , 所谓文本的嵌入 , 其机制就是 : 将复杂文本数据转换为具有固定维度的向量 , 在机器学习和检索任务中十分 nice ~

Embedding 就是嵌入 , 他是 Langchain.js 的一个核心组件

主要作用是 , 为各种文本嵌入模型交互而设计 , 为许多的模型提供统一的 、标准化的接口 ; 说到这里 , 我们可以思考 : 其实 langchain 框架本身就是为了提供“统一化 、标准化的接口”而生 , 它是 LLM 的上层应用框架 , 成为开发层面的老大 , 底层调用各类模型 , 我们开发者只需要熟悉固定的语法 , 痛苦都交给了 langchain 🤡

langchain 支持的嵌入式模型如下 :

Name Description
Alibaba Tongyi The AlibabaTongyiEmbeddings class uses the Alibaba Tongyi API to gene...
Azure OpenAI [Azure
Baidu Qianfan The BaiduQianfanEmbeddings class uses the Baidu Qianfan API to genera...
Amazon Bedrock Amazon Bedrock is a fully managed
ByteDance Doubao This will help you get started with ByteDanceDoubao [embedding
Cloudflare Workers AI This will help you get started with Cloudflare Workers AI [embedding
Cohere This will help you get started with CohereEmbeddings [embedding
DeepInfra The DeepInfraEmbeddings class utilizes the DeepInfra API to generate ...
Fireworks This will help you get started with FireworksEmbeddings [embedding
Google Generative AI This will help you get started with Google Generative AI [embedding
Google Vertex AI Google Vertex is a service that
Gradient AI The GradientEmbeddings class uses the Gradient AI API to generate emb...
HuggingFace Inference This Embeddings integration uses the HuggingFace Inference API to gen...
IBM watsonx.ai This will help you get started with IBM watsonx.ai [embedding
Jina The JinaEmbeddings class utilizes the Jina API to generate embeddings...
Llama CPP Only available on Node.js.
Minimax The MinimaxEmbeddings class uses the Minimax API to generate embeddin...
MistralAI This will help you get started with MistralAIEmbeddings [embedding
Mixedbread AI The MixedbreadAIEmbeddings class uses the Mixedbread AI API to genera...
Nomic The NomicEmbeddings class uses the Nomic AI API to generate embedding...
Ollama This will help you get started with Ollama [embedding
OpenAI This will help you get started with OpenAIEmbeddings [embedding
Pinecone This will help you get started with PineconeEmbeddings [embedding
Prem AI The PremEmbeddings class uses the Prem AI API to generate embeddings ...
Tencent Hunyuan The TencentHunyuanEmbeddings class uses the Tencent Hunyuan API to ge...
TensorFlow This Embeddings integration runs the embeddings entirely in your brow...
TogetherAI This will help you get started with TogetherAIEmbeddings [embedding
HuggingFace Transformers The TransformerEmbeddings class uses the Transformers.js package to g...
Voyage AI The VoyageEmbeddings class uses the Voyage AI REST API to generate em...
ZhipuAI The ZhipuAIEmbeddings class uses the ZhipuAI API to generate embeddin...

参考自官网 :js.langchain.com/docs/integr…

这些模型支持嵌入式 , 即支持将文本向量化 ~

我将使用 openai 来演示 ,

  1. 首先加载 data 文件夹下的"少年中国说.txt"文件为 Document 对象
  2. 然后使用工具分割对象
  3. 使用嵌入式模型向量化第二步分割后的 chunk
import { load } from "dotenv";
import { OpenAIEmbeddings } from "@langchain/openai";
import { TextLoader } from "langchain/document_loaders/fs/text";
import { RecursiveCharacterTextSplitter } from "langchain/text_splitter";
const env = await load();
const process = {
    env
}
// 1.
const loader = new TextLoader("data/少年中国说.txt");
const docs = await loader.load();
// 2.
const splitter = new RecursiveCharacterTextSplitter({
    chunkSize: 100, // 切片大小
    chunkOverlap: 20,// 重叠部分
  });
const splitDocs = await splitter.splitDocuments(docs);
// 3.
const embeddings = new OpenAIEmbeddings()
const splitDoc = splitDocs[0].pageContent
const res = await embeddings.embedQuery(splitDoc)
console.log(res)

在向量化之前 , 打印splitDocs , 输出如下:

使用OpenAIEmbeddings 嵌入式模型 , 对上述输出向量化之后 , 变成如下 :

上述代码使用嵌入式模型将文本变成了向量

会经历一下过程 :

  1. 预处理
    分词:首先,文本需要被分割成更小的单元,如单词或子词(subword)。例如,中文文本通常会被切分成单个汉字或词语。
    标准化:去除标点符号、转换为小写等操作,以确保一致性。
  2. 词汇表构建
    模型会根据训练数据构建一个词汇表(vocabulary),其中每个词都对应一个唯一的索引。对于未出现在词汇表中的词,通常会有一个特殊的标记(如)来表示未知词。
  3. 词向量生成
    静态词向量:早期的方法如Word2Vec、GloVe等会为每个词生成一个固定长度的向量。这些向量是通过无监督学习从大量文本中训练得到的,能够捕捉到词与词之间的语义关系。
    动态词向量:现代模型如BERT、OpenAI的模型使用的是上下文敏感的词向量。这意味着同一个词在不同的句子中可能会有不同的向量表示,从而更好地捕捉其在特定上下文中的含义。
  4. 句子编码
    平均池化:一种简单的方法是将句子中所有词向量的平均值作为句子的向量表示。
    加权求和:可以对词向量进行加权求和,权重可以根据词的重要性(如TF-IDF)来确定。
    Transformer架构:现代模型如BERT、OpenAI的模型使用了自注意力机制(self-attention),能够更好地捕捉句子中的长距离依赖关系,并生成整个句子的向量表示。
  5. 嵌入层
    在神经网络中,嵌入层(Embedding Layer)负责将输入的词索引转换为对应的词向量。这个层通常是可训练的,可以在下游任务中进一步优化。
  6. 输出向量
    最终,模型会输出一个固定长度的向量,这个向量代表了输入文本片段的语义信息。这个向量可以用于各种自然语言处理任务,如相似度计算、分类等。

以上过程参考自网络

Vector Store

向量数据库主要由 LangChain 社区维护的第三方集成 , 即在@langchain/community 包下面

关于选取那个数据 ,请查阅:js.langchain.ac.cn/docs/integr…

下面介绍两种向量数据库

  • Chroma
  • FaissStore

Chroma

一个专门为嵌入式向量设计的基于 SQLite 的开源数据库 , 有如下特点

  • 容易用
  • 轻量
  • 智能

通过向量切分多个段落 , 并对每个段落独立进行 k-means 聚类 , Chroma 可以有效压缩数据 , 减少储存空间 , 提高查询效率

k-means 聚类是一种无监督学习算法。它将数据分为 k 个聚类,使得每个数据点都属于离它最近的聚类中心所属的聚类。 通过不断迭代更新聚类中心,直到达到某种收敛条件。 例如,在图像识别中,可以用 k-means 聚类对图像的颜色进行分类;在客户细分中, 可以根据客户的特征将客户分为不同的群体。

langchain.js 官网 : js.langchain.ac.cn/docs/integr…

Chroma 官网 : docs.trychroma.com/docs/overvi…

好家伙 , 只支持 python 和 ts 🤡

安装、使用 ,依照上面官网

FaissStore

Faiss 是一个用于高效相似性搜索和密集向量聚类的库。

LangChain.js 支持使用 Faiss 作为本地运行的向量存储,可以保存到文件。

它还提供从 LangChain Python 实现 读取保存的文件的能力。

我在官网上看到这段 , 从那一眼起 , 我就选择她了 , 可是让我无语的是 , 我熬夜到天亮改了一个很臭的 bug —— 使用 npm , yarn , pnpm ... , 从淘宝源到腾讯源 , 这个包总是下不下来 , 我就不断搜索 , 可惜我用的是 Edge , 全是 csdn , 直到我在 github 上搜到以下解决方案 ,非常 nice !

一言蔽之即 : 手动下载 realse 版本 , 将无法下载的文件 ,手动添加到 node_modules

愿以我之发 , 保倔友之发🤡

总结 : 不要使用诸如 Edge 之类的浏览器搜报错🤡👈

实战

package.json
{
  "name": "test-app-node",
  "private": true,
  "version": "0.0.0",
  "scripts": {
    "prepare-kong-faiss": "ts-node prepare-kong-faiss.ts",
    "load-kong-faiss": "ts-node load-kong-faiss.ts",
    "multiQueryRetriever": "ts-node multiQueryRetriever.ts",
    "LLMChainExtractor": "ts-node LLMChainExtractor.ts",
    "ScoreThresholdRetriever": "ts-node ScoreThresholdRetriever.ts",
    "prepare-qiu": "ts-node ./rag/prepare-qiu.ts",
    "rag-2": "ts-node ./rag/index.ts",
    "rag-server": "ts-node ./rag/server.ts",
    "rag-client": "ts-node ./rag/client.ts"
  },
  "type": "module",
  "dependencies": {
    "@langchain/community": "^0.0.27",
    "dotenv": "^16.4.7",
    "express": "^4.19.2",
    "faiss-node": "^0.5.1",
    "langchain": "^0.1.37",
    "typescript": "^5.7.3"
  },
  "main": "index.js",
  "keywords": [],
  "author": "",
  "license": "ISC",
  "description": "",
  "devDependencies": {
    "ts-node": "^10.9.2"
  }
}
embedding

安装好上述包后 , 使用嵌入式模型 将向量化后的数据储存在 data/vector/ 下

import { TextLoader } from "langchain/document_loaders/fs/text";
import { RecursiveCharacterTextSplitter } from "langchain/text_splitter";
import { FaissStore } from "langchain/vectorstores/faiss";
import { OpenAIEmbeddings } from "@langchain/openai";
import "dotenv/config";


const run = async () => {
    const loader = new TextLoader("../data/少年中国说.txt");
    const docs = await loader.load();
  
    const splitter = new RecursiveCharacterTextSplitter({
      chunkSize: 100,
      chunkOverlap: 20,
    });
    const splitDocs = await splitter.splitDocuments(docs);
  
    const embeddings = new OpenAIEmbeddings();
    const vectorStore = await FaissStore.fromDocuments(splitDocs, embeddings);
  
    const directory = "../db/vector";
    await vectorStore.save(directory);
  };
  
  run();
  

运行上述文件后 , 生成 docstore.json 和二进制文件 faiss.index

docstore.json 中 , 即向量化后的数据 :

retriever

从向量数据库中检索 , 我提问 : 日本怎么称呼我们中国? , 将从向量数据中检索 ,

import { FaissStore } from "@langchain/community/vectorstores/faiss";
import { OpenAIEmbeddings } from "@langchain/openai";
import "faiss-node";
import dotenv from "dotenv";
dotenv.config();
async function f() {
  const directory = "../db/vector";
 
  const embeddings = new OpenAIEmbeddings(
    {  
      modelName: "text-embedding-ada-002", //指定模型的名称
      maxConcurrency: 10, //设置最大的并发数 , 意味着同负一时间最多可以并行处理10个请求 , 避免过多并发请求 , 导致系统过载和api限流
      maxRetries: 3, //设置最大的重试次数 , 当api调用失败的时候 , 程序会自动重试最多三次 , 这增加请求成功的概率 , 提高了系统的可靠性
    },
    {
      batchSize: 100, //设置批量处理的大小 , 每次调用api 最多处理100个文本片段 , 但同时也要注意api的限制和内存的使用
    }
  );
  //加载向量储存
  const vectorstore = await FaissStore.load(directory, embeddings);
  //从向量数据库中创建一个检索器
  const retriever = vectorstore.asRetriever(2);
  //使用Runnable API进行进行检索
  const res = await retriever.invoke("日本怎么称呼我们中国?");
  console.log(res);
}

f();

结果如下 :

总结

学到这里 , 我已经知道知识库从自然语言到向量的过程 , 从数据角度的话 , 经历了一下过程 :

  • 加载数据源
  • 分割数据
  • 向量化数据
  • 持久化数据

逐步走向 RAG ~

!!??所以是真的哦.gif

Langchain.js | Document Transformer👈| 文本这样转变😱

作者 浪遏
2025年1月17日 20:10

前言

书接上文 , 参考官方文档学习了 Ducument loader , 他的作用是加载不同的数据源 , 并且探讨了一些设计思想 , 这一次 ,还是这张图 , 但是学习的主题变了 , 我今天想学习 : 在加载数据之后 , 向量化之前 , 进行的文本转化处理, 这其实是文本转化器(Document Transformer)在作祟 , 对应图中红色方框的阶段 ~

同样 , 还是从宏观入手,文档转化主要有以下工作 :

  • 文本分割

    • 完整文档内容 分割为 chunk(文本块)
  • 文本元数据提取

    • 提取核心数据
  • 文本翻译

    • 全球化下 , 处理多语言文档已经成为常态
  • 生成文本问答

    • 向量储存的知识库中 , 通常以 QA 对出现 , 我们主要是在向量化之前转化为问答的形式 , 利于向量化过程中 , 增强相关性 , 聚焦核心要点

上面的四个步骤中 , 文本分割 是向量化之前的处理, 后面的三个步骤主要是在向量化前起优化作用


在 python 中提供 DocTran 库 , 用于上述文本元数据提取、文本翻译、生成文本为问答 ,因为 DocTran

参考自官网 : python.langchain.ac.cn/docs/integr…

但在 langchain.js 中 , 我简单搜索了下官网 , 没有找到集成的 DocTran库 ,

有时间再去 langchain 的 github 上看看 , 地址如下 : github.com/langchain-a…

如果还没有 , 或许我们可以自己拓展

我就不看了 , 学校放假🤡 , 我要去愉快的玩耍一下 , 之后有时间再来研究研究 , 这篇文章主要总结我分割文本的经验 ~

文本分割

思考 :

  • 为什么要文本分割 ?

  • 怎么分割 ?

    • 随便分吗 ? 肯定不是 🤡👈

为什么要文本分割 ?

主要有以下原因 :

  • 处理非统一文档长度
    现实世界的文档集合通常包含不同大小的文本。分割确保对所有文档进行一致的处理。
  • 克服模型限制
    许多嵌入模型和语言模型都有最大输入大小限制。分割使我们能够处理否则会超出这些限制的文档。
  • 提升表示质量
    对于较长的文档,嵌入或其他表示的质量可能会随着尝试捕捉过多信息而下降。拆分可以导致每个部分的表示更加专注和准确。
  • 增强检索精度
    在信息检索系统中,拆分可以提高搜索结果的粒度,允许更精确地匹配查询到相关文档部分。
  • 优化计算资源
    使用更小的文本块可以提高内存效率,并允许更好地并行化处理任务。

怎么分割 ?

关键概念

  • Chunk Size

    • 块大小:末端块长度(以字符为单位)
  • Chunk Overlap

    • 块重叠:连续块共享的重叠或交叉量

如何理解 ?

比如我们对《少年中国说》这篇文章进行分割

我们使用官网自带的ChunkViz v0.1 (块可视化工具)

上传文章后 , 选择下面的分割标准

文本分割如下 :

上面发生了什么?

上面其实就是使用 langchain.js 提供的RecursiveCharacterTextSplitter(递归字符文本分割) , 并且设置

  • Chunk size
  • Chunk overlap

这两个的参数大小

比如我上面设置 Chunk size 大小为 5 , 那么基本每个颜色块都是 5 个字符

由于可视化工具 Chunk overlap(块重叠) 无法展示 , 所以块与块之间没有重复的字段

我举个例子 : 一段分块是"日本人之称我中国也,一则曰老大帝国,再则曰老大帝国。" ,如果有重叠块可能会出现"再则曰老大帝国。是语也,盖袭译欧西人之言也。" , 如加粗的文字就是重叠部分 .

需要重叠块的原因是 , 两个块之间 , 需要有语义的联系 , 使得块向量化的时候 , 知道块与块之间的联系

以上便是分割的原理 , 以下使用代码实现

RecursiveCharacterTextSplitter

默认的分隔符列表是 ["\n\n", "\n", " ", ""] , 即分割的顺序是 : 段落("\n\n") => 句子("\n") =>单词(" ")

使用加载器加载 data 目录下的《少年中国说为 Document 对象 , 使用RecursiveCharacterTextSplitter 切割这个对象 , 代码如下

import { RecursiveCharacterTextSplitter } from "langchain/text_splitter";
import { TextLoader } from "langchain/document_loaders/fs/text";

const loader = new TextLoader("data/少年中国说.txt");
const docs = await loader.load();

const splitter = new RecursiveCharacterTextSplitter({
    chunkSize: 64,
    chunkOverlap: 0,
  });

const splitDocs = await splitter.splitDocuments(docs);
console.log(splitDocs);

部分输出如下图 :

How to split code

上面说到 , RecursiveCharacterTextSplitter 默认按照["\n\n", "\n", " ", ""] 分割 , 其实还包含预构建的分隔符列表,这些分隔符对于在特定编程语言中分割文本非常有用。

支持的语言有

"html" | "cpp" | "go" | "java" | "js" | "php" | "proto" | "python" | "rst" | "ruby" | "rust" | "scala" | "swift" | "markdown" | "latex" | "sol"

举两个例子就知道了~

Markdown

这里是一个使用 Markdown 分隔符进行分割的示例:

const markdownText = `
# 🦜️🔗 LangChain

⚡ Building applications with LLMs through composability ⚡

## Quick Install


# Hopefully this code block isn't split
pip install langchain

`;
const mdSplitter = RecursiveCharacterTextSplitter.fromLanguage("markdown", {
  chunkSize: 60,
  chunkOverlap: 0,
});
const mdDocs = await mdSplitter.createDocuments([markdownText]);

mdDocs;

效果如下 :


[
  Document {
    pageContent: "# 🦜️🔗 LangChain",
    metadata: { loc: { lines: { from: 2, to: 2 } } }
  },
  Document {
    pageContent: "⚡ Building applications with LLMs through composability ⚡",
    metadata: { loc: { lines: { from: 4, to: 4 } } }
  },
  Document {
    pageContent: "## Quick Install",
    metadata: { loc: { lines: { from: 6, to: 6 } } }
  },
  Document {
    pageContent: "```bash\n# Hopefully this code block isn't split",
    metadata: { loc: { lines: { from: 8, to: 9 } } }
  },
  Document {
    pageContent: "pip install langchain",
    metadata: { loc: { lines: { from: 10, to: 10 } } }
  },
  Document {
    pageContent: "```",
    metadata: { loc: { lines: { from: 11, to: 11 } } }
  },
  Document {
    pageContent: "As an open-source project in a rapidly developing field, we",
    metadata: { loc: { lines: { from: 13, to: 13 } } }
  },
  Document {
    pageContent: "are extremely open to contributions.",
    metadata: { loc: { lines: { from: 13, to: 13 } } }
  }
]

JavaScript
const JS_CODE = `
function helloWorld() {
  console.log("Hello, World!");
}

// Call the function
helloWorld();
`;

const jsSplitter = RecursiveCharacterTextSplitter.fromLanguage("js", {
  chunkSize: 60,
  chunkOverlap: 0,
});
const jsDocs = await jsSplitter.createDocuments([JS_CODE]);

jsDocs;
[
  Document {
    pageContent: 'function helloWorld() {\n  console.log("Hello, World!");\n}',
    metadata: { loc: { lines: { from: 2, to: 4 } } }
  },
  Document {
    pageContent: "// Call the function\nhelloWorld();",
    metadata: { loc: { lines: { from: 6, to: 7 } } }
  }
]

参考 : js.langchain.com/docs/how_to…

总结

学习文本分割的处理思想和方法 , 为后续向量化做准备 ~

可以在本次学习上加餐: token

昨天以前首页
❌
❌