在私域场景下,利用LangChain与LLM模型搭建个性化服务平台的思考

2,198 阅读7分钟

缘起

4月份裸辞开始,原计划给自己几个月的修整期,去看看祖国的大好山河,来一场说走就走的旅行,结果万万没想到AIGC爆火,我的修整计划基本GG思密达,这一段时间,录了一套基础教程,基于chatgpt做了几个项目,微信机器人,公众号,套壳网站,英语陪练,MJ,等等 最近在和一些朋友聊天,聊到一个新的变现方向:私域垂类助手

应用场景如下

  1. 电商客服系统
  2. 政务问答系统
  3. 法律顾问助手
  4. 教培咨询机器人

当然现在类似的机器人助手都是已经有的,但是大多是通过字符串匹配实现的。这种方式,问不到关键词,系统就答不上。所以我们可以把助手和chatgpt 或者其他的LLM模型进行关联,但是同样也会出现一些问题,比如在大语言模型中,存在的关键词都属于普适知识,但是如果我去询问一些私有的知识,比如我的名字就GG,

所以本文去探讨一种 私域场景下的垂类问答系统思考(chatPDF)

先看结果

  1. 我准备一个 李狗蛋的高级前端开发工程师简历如下

image-20230601003109615

image-20230601003137002

因为这种LLM 基础训练的都是普适知识,所以当我们询问 who is 李狗蛋他是没有办法回复的如下:

image-20230601003434464

接着我们测试下本地知识库

询问三个问题

  1. 请介绍下李狗蛋
  2. 李狗蛋的技术栈是什么
  3. 李狗蛋的缺点是什么
async function test(q:string) {

  /** 从 本地 文件初始化 仓库, (从本地缓存文件初始数据库会省 token 消耗) */
  const vectorStore = await initVectorStoreFromLocalTempFiles();
  /** 本地向量搜索并向 LLM 发问 */
  const answer = await searchAndAsk(vectorStore, q);
}
test("请介绍下李狗蛋")
test("李狗蛋的技术栈是什么")
test("李狗蛋的缺点是什么")

结果如下

image-20230601004517034

技术栈

Nodejs + TS

实现思路

Fine-Tuning(微调)

Fine-Tuning,也称为微调,在机器学习中指通过训练预先训练的模型来适应特定任务的过程。通常情况下,Fine-Tuning需要针对特定任务准备一个小型数据集,并使用该数据集对预训练模型进行微调。这个过程通常只需要很少的数据和计算资源,可以使得原有的预训练模型在特定任务上得到更好的表现。

Embedding(嵌入)

Embedding(嵌入)是将高维数据向量化的技术。在自然语言处理中,嵌入通常用于将单词、短语或文档转换成密集的低维向量。这些向量通常具有语义相关性,可以用于实现单词相似度比较、文本分类、翻译等任务。在LangChain中,利用Document Loaders、Text Splitters、Vector Stores等工具,可以将本地文档嵌入到向量数据库中,并在进入GPT之前通过相似度搜索找到与输入问题最相似的答案嵌入到提问中,从而避免了GPT3.5 API长度限制带来的问题。

Fhx39ebx5a7C98imVLYO5uZkOWuv

实际上,用 LangChain 来解决复杂问题并不需要训练大语言模型(LLM),我们只是将本地知识库和问题作为prompt的一部分输入到LLM中。这种做法非常轻量级, LLM 只需要专注于它最擅长的工作:翻译和总结。因此,如果您在目录中放置的是英文文档,那么您可以通过中文与应用程序进行对话,并且 LangChain 会像一个聪明的助手一样快速准确地回答您的问题。

核心代码实现

1. 本地训练内容读取

这里我们直接通过LangChainapi实现本地文件读取

/** 本地默认文件路径 */
const  LOCAL_FILE_PATH = 'files';
const LOCAL_VENCTOR_DATA_PATH = "./vectorData/";

/** 获取本地文件 */
async function getLocalFiles(){
  const directoryLoader = new DirectoryLoader(LOCAL_FILE_PATH, {
    '.pdf': (path) => new CustomPDFLoader(path),
    ".json": (path) => new JSONLoader(path, "/texts"),
    ".txt": (path) => new TextLoader(path),
    ".csv": (path) => new CSVLoader(path, "text"),
  });

  /** 文件执行加载,得到 Document */
  const fileDocs = await directoryLoader.load();

  return fileDocs;

}

2.将读取到的本地文件进行切片

chunkSize:切片尺寸

chunkOverlap: 为了保证上下文连续 重复量级

 /** 文件执行加载,得到 Document */
      const rawDocs = await getLocalFiles();
      /* 文字 转 切片 */
      const textSplitter = new RecursiveCharacterTextSplitter({
        chunkSize: 1000,
        chunkOverlap: 200,
      });
  
      /** 切片文档 */
      const docs = await textSplitter.splitDocuments(rawDocs);

3.通过Embedding方式 初始化向量数据库

LangChain确实为用户提供了多种向量数据库可供选择,其中包括HNSWLibAnnoyFaiss等。这些不同的向量数据库具有各自的优势和适用场景,可以根据用户需求进行选择。

在这里,我们选择了HNSWLib作为向量数据库,原因是它支持Node.js、轻巧便捷、支持离线运行,并且无需单独启动数据库程序,只需将其作为模块导入即可运行。

当然,在生产环境中,建议使用更高级的云端数据库以确保性能和可靠性,例如PineconeSupabase等。这些云端数据库提供全球分布式部署、高效查询、强大的数据安全性等特点,并且可以方便地与其他云服务集成使用。

将把切片文档传入HNSWLib类中,同时使用OpenAIEmbeddings类来生成向量数据。

 /** 创建和存储 vectorStore 嵌入, 这个步骤会生成向量数据(按 token 计费,给 openai 送钱) */
      const vectorStore = await HNSWLib.fromDocuments(docs, new OpenAIEmbeddings({
          verbose: true,
      }));
      /** 初始化完数据库后,存成本地文件 */
      await vectorStore.save(LOCAL_VENCTOR_DATA_PATH);
      console.log('向量数据库初始化成功!');

4. 用户提问优先近似向量内容检索

/** 先本地执行相似搜索,再向 LLM 发问 */
async  function  searchAndAsk(loadedVectorStore: HNSWLib, question: string) {
  /** 向量相似搜索 */
  const referenceContextDocuments = await loadedVectorStore.similaritySearch(question, 1);
  /** 向量搜索相似结果string化 */
 const contextString = (referenceContextDocuments || []).map(docItem => `[${getFileName(docItem.metadata.source)}] ${docItem.pageContent}`).join('/n')

  /** 带着本地信息, 向 LLM 发问 */  
  const llmAnswer = await askLLM({
    question, 
    referenceContext: contextString
  });

  return llmAnswer;
}

5. 向LLM 发起问题

在这一步将向量搜索到的内容,结合问题用户的问题添加到prompt 里丢给LLM,让他来帮助我们总结提炼

/** 调用 LLM 接口 */
async  function  askLLM({question, referenceContext}: IAsk) {
  //  LLM 用 openai
  const model = new OpenAI({ temperature: 0 });
  /** prompt 模板 */
  const prompt = PromptTemplate.fromTemplate(
    `作为一位专业的文档工程师,你的任务是从给定的上下文回答问题。
    你的回答应该基于我提供的上下文回答我的问题,并以对话的形式呈现。
    问题如下:
    {question}
    给定的上下文:
    {context}
    `,
  );

  // 通过 PromptTemplate + LLM 创建 chain
  const chain = new LLMChain({ 
    llm: model,
     prompt, 
    // verbose: true 
  });

  const res = await chain.call({ question, context: referenceContext });
  console.log('res log:', res);
  return res;
}

结束语

在当今信息爆炸的时代,如何从无数信息中筛选出有价值的内容成为了人们不可或缺的技能。然而,特定领域的专业知识往往受限于私密性和专业性等因素,传统问答系统难以满足用户的需求。为此,本文提出了一种基于LangChain与LLM模型的私有知识库方案,为垂类问答服务带来了新的发展思路和前景。

该方案不仅可以应用于企业内部知识库管理,还可以运用于教育、司法、医疗等领域,提供智能便捷的信息服务。通过Fine-Tuning与Embedding技术的结合运用,垂类问答系统在私域场景下实现更加高效精准的回答,大大提升了用户的使用体验。同时,本文也探索了如何利用LangChain实现本地知识库的嵌入式搜索,进一步提高了系统的效率和准确性。

最终我们不会被AI取代,而是被掌握AI的人取代,拥抱AI吧

前端开发在AIGC的探索之路,共同努力