赞
踩
大家好,我是feng,欢迎关注公众号和我一起探索。如果文章对你有所启发,请为我点赞、转发以及在下方留言自己的理解!
每次发文章都看到不少人收藏。我自己也有这个习惯,但是基本上收藏起来的东西都没再打开过。哪怕再遇到类似的问题,首先想到的却是搜索,置于收藏的东西根本想不到去查看。所以当然可以收藏,但是我更建议大家输出(写出文字、敲出代码)自己的理解,比如在评论区写上自己对本文的理解和疑问。不要小看自己写的这几十个字,帮你加深记忆的效果绝对比收藏要好得多!
所以,请在评论区输出你的收获吧!
这个历史问题,指的是之前代码对于对话历史记录的处理逻辑,其实是存在问题的。在之前的逻辑中,我们将每次的对话的历史信息加入到历史信息数组里,并被转为字符串植入提示语模板中。这样在程序运行时,确实可以执行的很好。但是当关闭程序,重新执行的情况下,我们再提出问题时,大模型是不知道答案的。那是因为历史对话记录是存储在内存中的。一旦我们终止应用程序,所有的历史记录都会丢失。所以如果我们希望应用程序能够记住用户和大模型之间的对话时长几周、几个月或几年时,我们就需要为大模型增加记忆 - Memory。使得每次我们运行应用程序时,大模型都能够记得我们之间的对话历史。
在之前的文章中,我曾经提过历史记录可以通过接口存入外部数据库、通过接口从外部数据库导入,这样确实能够实现,但这需要依赖于后端提供相关的api。借助于LangChain提供的相关api,本文将介绍前端程序可以直接将数据读取或存储到外部存储系统中。
Memory这个词,通常被翻译为内存。在AI应用场景,我认为将memory理解为大模型与用户之间的会话过程更适合一些。所以,记忆可以被定义为获取、储存、保留以及后来检索信息的过程。
人脑中有几种类型的记忆:
记忆类型 | 映射 | 例子 |
---|---|---|
感觉记忆 | 学习原始输入的嵌入表示,包括文本、图像或其他形式,短暂保留感觉印象。 | 看一张图片,然后在图片消失后能够在脑海中回想起它的视觉印象。 |
短期记忆 | 上下文学习(比如直接写入prompt中的信息),处理复杂任务的临时存储空间,受Transformer有限的上下文窗口长度限制。 | 在进行心算时记住几个数字,但短期记忆是有限的,只能暂时保持几个项目。 |
长期记忆 | 在查询时智能Agent可以关注的外部向量存储,具有快速检索和基本无限的存储容量。 | 学会骑自行车后,多年后再次骑起来时仍能掌握这项技能,这要归功于长期记忆的持久存储。 |
可以大致考虑以下对应关系:
mport { ChatOllama } from '@langchain/community/chat_models/ollama'; import { ChatPromptTemplate } from '@langchain/core/prompts'; import { BufferMemory } from 'langchain/memory'; import { ConversationChain } from 'langchain/chains'; // 创建模型 const model = new ChatOllama({ baseUrl: 'http://localhost:11434', // Default value model: 'qwen:4b', temperature: 0.7, }); // 创建提示语 const prompt = ChatPromptTemplate.fromTemplate(` 你是一个助手。 History: {history} {input} `); // 创建记忆,memoryKey代表了每次对话的唯一标识 const memory = new BufferMemory({ memoryKey: 'history', }); // 使用 Chain 类的方式 const chain = new ConversationChain({ llm: model, prompt, memory, }); // 使用LCEL //const chain = prompt.pipe(model); // 打印记忆中的变量 console.log('记忆中的变量:', await memory.loadMemoryVariables()); // 第一次调用 const input1 = { input: '密码是:abc', }; // 获取响应 const resp1 = await chain.invoke(input1); console.log(resp1); console.log('更新后的记忆:', await memory.loadMemoryVariables()); // 第二次调用 const input2 = { input: '密码是什么?', }; // 获取响应 const resp2 = await chain.invoke(input2); console.log(resp2);
如代码注释,使用了Chain类的方式来创建一个链,ConversationChain类接收一个memory参数。代码中共调用了2次大模型。每次调用都会自动更新memory历史记录。看一下调用情况:
可以看到,在第一次调用前,通过memory.loadMemoryVariables()方法,来获取当前记忆中的变量列表是空字符串。在第一次调用完以后,记忆被更新。通过将记忆传入到提示语中,大模型才能够回答问题。
这个例子实际上也是运行在内存之中,当我们移动重新运行时,记忆中的变量仍然会被清空,这并不是本文的目的。因此我们要了解如果将记忆存到外部系统,以及如何从外部系统读取记忆。
LangChain支持多种类型的外部存储,如Postgresql、Redis、MongoDB等,详细的支持列表可以访问网址:https://js.langchain.com/docs/integrations/chat_memory
我将使用本机安装的Postgresql数据库来作为外部存储目的地。
首先,创建一个新的数据库:ai_start,在pgAdmin4中查看如图:
这是新创建的数据库,所以在默认的Schema:public下,并没有任何表存在。
mport { ChatOllama } from '@langchain/community/chat_models/ollama'; import { PostgresChatMessageHistory } from '@langchain/community/stores/message/postgres'; import { RunnableWithMessageHistory } from '@langchain/core/runnables'; import { ChatPromptTemplate, MessagesPlaceholder, } from '@langchain/core/prompts'; import { StringOutputParser } from '@langchain/core/output_parsers'; import pg from 'pg'; const model = new ChatOllama({ baseUrl: 'http://localhost:11434', // Default value model: 'qwen:4b', temperature: 0.7, }); const poolConfig = { host: 'localhost', port: 5431, user: 'bx_dev', password: 'bx_dev', database: 'ai_start', }; const pool = new pg.Pool(poolConfig); const prompt = ChatPromptTemplate.fromMessages([ ('system', '你是一个很有能力的助手,尽你所有的能力来回答用户提出的问题。'), new MessagesPlaceholder('chat_history'), ('human', '{input}'), ]); const chain = prompt.pipe(model).pipe(new StringOutputParser()); const chainWithHistory = new RunnableWithMessageHistory({ runnable: chain, inputMessagesKey: 'input', historyMessagesKey: 'chat_history', getMessageHistory: async (sessionId) => { const chatHistory = new PostgresChatMessageHistory({ sessionId, pool, }); return chatHistory; }, }); const res1 = await chainWithHistory.invoke( { input: '你好,我是tony。', }, { configurable: { sessionId: 'langchain-test-session' }, }, ); console.log('res1:', res1); const res2 = await chainWithHistory.invoke( { input: '我的名字叫什么?', }, { configurable: { sessionId: 'langchain-test-session' }, }, ); console.log('res2:', res2); await pool.end();
我们来看看控制台执行结果:
可以看到,正确的回答了问题。再来看看数据库:
我们会发现,LangChain创建了一个名为langchain_chat_histories的表,并且在表中已经存储了程序在运行时的历史对话记录。
至此,我们实现了通过前端,将历史记录存储到外部系统,并且从外部系统获取历史数据以支持大模型的回答。
要实现为大模型增加记忆功能,在本文中主要是RunnableWithMessageHistory对象的使用。通过配置数据连接信息,和定义获取历史记录方法,我们可以非常简单的就实现这个目的。
在Langchain官网上还有很多对话存储的api,读者可以根据自己的实际情况选择不同的实现方式。
从数据安全性角度来说,连接信息存储在前端(比如本文的例子)并不是一个好的选择。Langchain也支持通过token的形式连接云端系统,这种方式是一种选择。不过,我还是建议通过后端的api来实现存储。这样我们可以方便的使用axios库直接调用进行读取和存储记忆数据,无论从安全性、灵活性以及前端对相关技术的熟练度来说,都可能是更合适的选择。
1、https://js.langchain.com/docs/integrations/chat_memory/postgres
2、https://zhuanlan.zhihu.com/p/648376562
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。