VectorStoreRetrieverMemory

VectorStoreRetrieverMemory 就是基于 向量数据库(Vector Database) 实现的一种记忆管理机制,结合了向量存储和检索技术,可以完成高效的上下文管理。简单来说就是把用户和AI的对话数据转成向量存储到数据库里,根据用户的输入来查询相关的上下文,从而帮助AI实现有感知的回答

我们重新回到之前做的RAG bot的demo:

第一步:更改Prompt Template

import { ChatPromptTemplate } from "@langchain/core/prompts";

export const createTemplate = () => {
  const chatPrompt = `你是一个熟读了新海诚小说《天气之子》的读者,精通根据作品原文详细解释和回答问题,在回答问题的时候你会引用作品原文。
  并且在回答的时候仅根据原文来回答,尽可能详细地回答用户的问题。如果原文中没有相关内容,你可以根据情况自由回答,如果实在是无法判断用户的问题,你可以回答“这个问题我不知道”。
  
  以下是原文中跟用户回答相关的内容
  {context}
  
  这是聊天记录,其中input后跟的是用户的问题,output是AI的回答
  {chat_history}
  
  现在,你要基于原文和聊天记录,回答以下问题:
  {question}`;

  const prompt = ChatPromptTemplate.fromTemplate(chatPrompt);

  return prompt;
};

我们新添加了聊天记录的Prompt,向AI提醒接下来的对话要加入聊天记录进行分析

第二步:新建基于Qdrant的Memory

// createMemoryFromQdrant.ts
import { VectorStoreRetrieverMemory } from "langchain/memory";
import { OpenAIEmbeddings } from "@langchain/openai";
import { QdrantVectorStore } from "@langchain/qdrant";
import { QdrantClient } from "@qdrant/js-client-rest";
import "dotenv/config";

export const createMemoryFromQdrant = async () => {
  const client = new QdrantClient({ url: process.env.QDRANT_API_URL });

  const embeddings = new OpenAIEmbeddings({
    configuration: {
      baseURL: process.env.OPENAI_API_URL,
    },
    model: process.env.OPENAI_EMBEDDING_MODEL,
  });

  const vectorStore = await QdrantVectorStore.fromExistingCollection(
    embeddings,
    {
      client: client,
      collectionName: "ten_ki_no_ko_history",
    }
  );

  const memory = new VectorStoreRetrieverMemory({
    vectorStoreRetriever: vectorStore.asRetriever(1),
    memoryKey: "ten_ki_no_ko_history",
  });

  return memory;
};

创建一个基于指定的collectionName的Memory,每次找出一条类似的聊天记录

第三步:新建load和save方法

// saveMemoryToQdrant.ts
import { createMemoryFromQdrant } from "./createMemoryFromQdrant";

export interface Memory {
  humanMessage: string;
  aiMessage: string;
}

export const saveMemoryToQdrant = async (messages: Memory) => {
  const memory = await createMemoryFromQdrant();
  await memory.saveContext(
    { input: messages.humanMessage },
    { output: messages.aiMessage }
  );
};
// loadMemoryFromQdrant.ts
import { createMemoryFromQdrant } from "./createMemoryFromQdrant";

export const loadMemoryFromQdrant = async (prompt: string) => {
  const memory = await createMemoryFromQdrant();
  const res = await memory.loadMemoryVariables({ prompt });
  return res["ten_ki_no_ko_history"];
};

这两个方法分别负责的是保存聊天记录,以及通过Prompt(提问)来找出数据库里最类似的那段聊天记录。

第四步:新建一个关于Memory的Retriever

// getMemoryRetrieverChain.ts
import { RunnableSequence } from "@langchain/core/runnables";
import { loadMemoryFromQdrant } from "./loadMemoryFromQdrant";

export const getMemoryRetrieverChain = async () => {
  const memoryRetrieverChain = RunnableSequence.from([
    (input) => input.question,
    loadMemoryFromQdrant,
  ]);

  return memoryRetrieverChain;
};

第五步:添加Memory的Retriever,把历史记录的值加入Prompt Template

import { ChatOpenAI } from "@langchain/openai";
import { StringOutputParser } from "@langchain/core/output_parsers";
import { RunnableSequence } from "@langchain/core/runnables";
import { getContextRetrieverChain } from "./getContextRetrieverChain";
import { createTemplate } from "./creatTemplate";
import { getMemoryRetrieverChain } from "./getMemoryRetrieverChain";

export const getLastRagChain = async () => {
  const model = new ChatOpenAI({
    configuration: {
      baseURL: process.env.OPENAI_API_URL,
    },
    model: process.env.OPENAI_MODEL,
  });

  const contextRetrieverChain = await getContextRetrieverChain();

  const memoryRetrieverChain = await getMemoryRetrieverChain();

  const prompt = createTemplate();

  const ragChain = RunnableSequence.from([
    {
      context: contextRetrieverChain,
      chat_history: memoryRetrieverChain,
      question: (input) => input.question,
    },
    prompt,
    model,
    new StringOutputParser(),
  ]);

  return ragChain;
};

第六步:在入口文件,运行结束之后,存入一下聊天记录到数据库

import { getLastRagChain } from "./getLastRagChain";
import { saveMemoryToQdrant } from "./saveMemoryToQdrant";
import { saveQdrant } from "./saveQdrant";

const run = async () => {
  await saveQdrant();
  const ragChain = await getLastRagChain();

  const question = "你好,我是读者,我的名字叫做八六";

  const res = await ragChain.invoke({
    question,
  });
  console.log(res);
  await saveMemoryToQdrant({
    humanMessage: question,
    aiMessage: res,
  });
};
run();

这样一来,我们运行了一次,就可以在Qdrant的web UI上看到history的collection:

然后我们再询问一下我是谁:

  const question = "我的名字叫什么?你会怎么给我推荐这本书?";


这样就实现记忆功能啦。

这是代码仓库

这是临时起意想起的向量数据库查询记忆的方法,有点紧急更新的味道,所以下一篇要紧跟这里,并尝试重构bot的demo,加上复数个Memory管理来实现真正的比较完善的记忆体系,请期待。

最后修改:2024 年 12 月 19 日
收款不要了,给孩子补充点点赞数吧