AI探索实践16 - Typescript开发AI应用8:为大模型增加记忆(Memory)

news2024/9/23 3:17:52

大家好,我是feng,欢迎关注公众号和我一起探索。如果文章对你有所启发,请为我点赞、转发以及在下方留言自己的理解!全栈技术探索
每次发文章都看到不少人收藏。我自己也有这个习惯,但是基本上收藏起来的东西都没再打开过。哪怕再遇到类似的问题,首先想到的却是搜索,置于收藏的东西根本想不到去查看。所以当然可以收藏,但是我更建议大家输出(写出文字、敲出代码)自己的理解,比如在评论区写上自己对本文的理解和疑问。不要小看自己写的这几十个字,帮你加深记忆的效果绝对比收藏要好得多!

所以,请在评论区输出你的收获吧!

一、历史的问题

这个历史问题,指的是之前代码对于对话历史记录的处理逻辑,其实是存在问题的。在之前的逻辑中,我们将每次的对话的历史信息加入到历史信息数组里,并被转为字符串植入提示语模板中。这样在程序运行时,确实可以执行的很好。但是当关闭程序,重新执行的情况下,我们再提出问题时,大模型是不知道答案的。那是因为历史对话记录是存储在内存中的。一旦我们终止应用程序,所有的历史记录都会丢失。所以如果我们希望应用程序能够记住用户和大模型之间的对话时长几周、几个月或几年时,我们就需要为大模型增加记忆 - Memory。使得每次我们运行应用程序时,大模型都能够记得我们之间的对话历史。

在之前的文章中,我曾经提过历史记录可以通过接口存入外部数据库、通过接口从外部数据库导入,这样确实能够实现,但这需要依赖于后端提供相关的api。借助于LangChain提供的相关api,本文将介绍前端程序可以直接将数据读取或存储到外部存储系统中。

二、了解记忆 Memory

Memory这个词,通常被翻译为内存。在AI应用场景,我认为将memory理解为大模型与用户之间的会话过程更适合一些。所以,记忆可以被定义为获取、储存、保留以及后来检索信息的过程。

人脑中有几种类型的记忆:

  • 感觉记忆(Sensory Memory):这是记忆的最早阶段,提供在原始刺激结束后保留感官信息(视觉、听觉等)的印象的能力。感觉记忆通常只持续几秒钟。子类别包括视觉记忆(iconic memory)、回声记忆(echoic memory)和触觉记忆(haptic memory)。
  • 短期记忆(Short-Term Memory, STM)或工作记忆(Working Memory):它储存我们当前意识到的信息,以执行复杂的认知任务,如学习和推理。短期记忆被认为有大约7个项目的容量(Miller 1956)并持续20-30秒。
  • 长期记忆(Long-Term Memory, LTM):长期记忆可以储存信息很长一段时间,从几天到几十年,其储存容量基本上是无限的。LTM有两个子类型:
    • 显性 / 陈述记忆(Explicit / declarative memory):这是对事实和事件的记忆,指的是那些可以被有意识地回忆的记忆,包括情景记忆(事件和经验)和语义记忆(事实和概念)。
    • 隐性 / 程序记忆(Implicit / procedural memory):这种记忆是无意识的,涉及自动执行的技能和例行程序,如骑自行车或在键盘上打字。
记忆类型映射例子
感觉记忆学习原始输入的嵌入表示,包括文本、图像或其他形式,短暂保留感觉印象。看一张图片,然后在图片消失后能够在脑海中回想起它的视觉印象。
短期记忆上下文学习(比如直接写入prompt中的信息),处理复杂任务的临时存储空间,受Transformer有限的上下文窗口长度限制。在进行心算时记住几个数字,但短期记忆是有限的,只能暂时保持几个项目。
长期记忆在查询时智能Agent可以关注的外部向量存储,具有快速检索和基本无限的存储容量。学会骑自行车后,多年后再次骑起来时仍能掌握这项技能,这要归功于长期记忆的持久存储。

可以大致考虑以下对应关系:

  • 将感觉记忆视为学习原始输入(包括文本、图像或其他模式)的嵌入表示;
  • 将短期记忆视为在上下文中(prompt)学习。它是短暂且有限的,因为它受到Transformer的上下文窗口长度的限制。
  • 将长期记忆视为代理在查询时可以注意到的外部向量存储,可以通过快速检索访问。

三、为大模型加上记忆Memory

3.1 增加记忆

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()方法,来获取当前记忆中的变量列表是空字符串。在第一次调用完以后,记忆被更新。通过将记忆传入到提示语中,大模型才能够回答问题。

这个例子实际上也是运行在内存之中,当我们移动重新运行时,记忆中的变量仍然会被清空,这并不是本文的目的。因此我们要了解如果将记忆存到外部系统,以及如何从外部系统读取记忆。

3.2 记忆的外部存储与读取

LangChain支持多种类型的外部存储,如Postgresql、Redis、MongoDB等,详细的支持列表可以访问网址:https://js.langchain.com/docs/integrations/chat_memory

我将使用本机安装的Postgresql数据库来作为外部存储目的地。

3.2.1 配置数据库

首先,创建一个新的数据库:ai_start,在pgAdmin4中查看如图:
在这里插入图片描述
这是新创建的数据库,所以在默认的Schema:public下,并没有任何表存在。

3.2.2 改造代码

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();

3.3.3 理解代码逻辑

  1. poolConfig是本机数据库的配置信息。此处主要指定了目标数据库的配置信息,特别是数据库名称。
  2. 由于我们对输出进行了格式化,需要消息变量的替换,所以在使用提示语模板时,使用了 fromMessages。
  3. RunnableWithMessageHistory,是一个可以使用历史记录的链。它主要接收一个对象类型的4个参数。
  • 可运行的链对象
  • 消息占位符标识
  • 历史数据库标识
  • 异步的获取历史数据的方法,接收一个sessionId,利用这个sessionId和数据库连接信息来获取历史记录
  1. 在调用链时,我们需要配置当次调用时的sessionId。

我们来看看控制台执行结果:
在这里插入图片描述
可以看到,正确的回答了问题。再来看看数据库:
在这里插入图片描述
我们会发现,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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1518449.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Vite为什么比Webpack快

一、引言 主流的前端构建工具包括以下几种: Webpack:当下最热门的前端资源模块化管理和打包工具。它能够将许多松散的模块按照依赖和规则打包成符合生产环境部署的前端资源。同时,Webpack还支持代码分割,可以按需加载模块&#…

普林斯顿算法讲义(四)

原文:普林斯顿大学算法课程 译者:飞龙 协议:CC BY-NC-SA 4.0 6.1 事件驱动模拟 原文:algs4.cs.princeton.edu/61event 译者:飞龙 协议:CC BY-NC-SA 4.0 本章节正在建设中。 根据弹性碰撞的法则使用事件驱动…

STM32使用常见错误合集(正在更新版)

本文章记录一些学习STM32的一些错误问题 一、编译、烧录类问题 1、烧录不成功,Keil提示RDDI-DAP Error【场景:PWM驱动直流电机】 解决方案:将电机断开再进行烧录,断开后就可以美美烧录不报错啦~ 二、Keil使用问题 1、打开一个…

中宣部防沉迷系统PHP版本(管局防沉迷验证-PHP-全版本-接口测试样例)

现在对接游戏,无论是登录还是支付都是要去对接防沉迷实名认证接口,但前期的话你要登录网络游戏防沉迷实名认证系统进行接口测试,$appid ,$bizId,$key去接口测试页面找(正式上线在密钥管理)&…

2024.3.14

1、成员函数版本实现算术运算符的重载 全局函数版本实现算术运算符的重载 #include <iostream>using namespace std;class Team {friend const Team operator-(const Team &t1, const Team &t2); private:int a;int b; public:Team(){}Team(int a, int b):a(a…

建筑安全监测系统解决方案-GNSS位移监测站

方案背景 房屋建筑安全是人们生产、经营、居住、学习、娱乐等经济生活和人身安全的基本保证。近年来&#xff0c;房屋安全事故频发&#xff0c;造成了人员伤亡和极大财产损失。因此&#xff0c;在保护居民的人身安全和财产安全方面&#xff0c;房屋建筑安全管理就显得尤为重要…

python--字符串切片和常用的写法

python--字符串切片和常用的写法 正序切片格式注意点 倒序切片格式 字符串运算字符串转义字符串常用方法大小写相关的统计相关的拆分&替换字符串连接&#xff08;面试&#xff09;字符串格式化 正序切片 格式 str1[起始索引:结束索引]左闭右开&#xff08;取左边下标的值&…

python 调用redis创建查询key

部署redis apiVersion: apps/v1 # 描述api版本&#xff0c;默认都用这个 kind: Deployment # 资源类型&#xff0c;可以配置为pod&#xff0c;deployment&#xff0c;service&#xff0c;statefulset等等 metadata: # deployment相关的元数据&#xff0c;用于描述deployment的…

2024哥本哈根major跟以往有什么区别?

2024哥本哈根major跟以往有什么区别&#xff1f; 时隔将近300天&#xff0c;CS2的第一场major终于即将到来。在预选赛中&#xff0c;来自世界各地成千上万支队伍经历过一层又一层的厮杀搏斗&#xff0c;最终遴选出这24支最顶尖的战队&#xff0c;将于3月17日齐聚于哥本哈根的皇…

C#_Array数组_多维数组_交错数组

文章目录 C#数组类型分析多维数组交错数组数组的属性和方法上期习题答案本期习题 C#数组 数组是一个用来存储相同类型数据的、固定大小的、具有连续内存位置的顺序集合。数组中的每个元素都对应一个索引值&#xff0c;索引从 0 开始依次递增&#xff0c;我们可以通过索引来访问…

html5的css使用display: flex进行div居中的坑!

最近做项目的时候&#xff0c;有个需求&#xff0c;一个高度宽度不确定的Div在另一个Div内上下左右居中。 然后以前上下居中用的都是很繁琐的&#xff0c;就打算去百度搜索一个更优秀的方法。 百度AI自己给我一个例子&#xff1a; /* div在容器里居中显示&#xff0c;设置外容…

如何零基础入门Prometheus

本公众号的精品教程《玩转Prometheus监控》是一套零基础的入门教程&#xff0c;基于多年实战经验编写而成&#xff0c;内容完整覆盖了产品的核心技术要点&#xff0c;适合想入门和进阶技术的朋友学习。 整个系列总共24篇课程&#xff0c;由基础知识开始&#xff0c;逐步进阶学…

IDEA编写各种WordCount运行

目录 一、编写WordCount(Spark_scala)提交到spark高可用集群 1.项目结构 2.导入依赖 3.编写scala版的WordCount 4.maven打包 5.运行jar包 ​6.查询hdfs的输出结果 二、本地编写WordCount(Spark_scala)读取本地文件 1.项目结构 2.编写scala版的WordCount 3.编辑Edit …

mupdf渲染过程(一):颜色

mupdf除了解析PDF功能之外&#xff0c;还有一个强大的功能就是渲染文字和图像&#xff0c;本文介绍mupdf渲染过程中涉及到的颜色问题&#xff1a;包括颜色空间&#xff0c;颜色转换&#xff0c;lcms的使用。 1.初始化 mupdf初始化第一步是实例化fz_context *ctx&#xff0c;fz…

2W10-ASEMI适配器专用2W10

编辑&#xff1a;ll 2W10-ASEMI适配器专用2W10 型号&#xff1a;2W10 品牌&#xff1a;ASEMI 封装&#xff1a;WOB-4 最大重复峰值反向电压&#xff1a;1000V 最大正向平均整流电流(Vdss)&#xff1a;2A 功率(Pd)&#xff1a;中小功率 芯片个数&#xff1a;4 引脚数量…

钡铼技术有限公司R40路由器工业4G让养殖环境监控更高效

钡铼技术有限公司的R40路由器是一款专为养殖环境监控而设计的工业级4G路由器。该路由器的出现极大地提高了养殖行业的监控效率&#xff0c;为养殖场主和管理者提供了更可靠、高效的解决方案。本文将从功能特点、优势以及应用案例等方面介绍钡铼技术有限公司的R40路由器在养殖环…

【SpringBoot】自定义工具类实现Excel数据新建表存入MySQL数据库

&#x1f3e1;浩泽学编程&#xff1a;个人主页 &#x1f525; 推荐专栏&#xff1a;《深入浅出SpringBoot》《java对AI的调用开发》 《RabbitMQ》《Spring》《SpringMVC》《项目实战》 &#x1f6f8;学无止境&#xff0c;不骄不躁&#xff0c;知行合一 文章目录 …

hololens2发布unity设置

生成vs工程再向hololens发布时&#xff0c; Architecture选X64或ARM64都可以成功发布

python-0002-linux安装pycharm

下载软件包 下载地址&#xff1a;https://download.csdn.net/download/qq_41833259/88944791 安装 # 解压 tar -zxvf 你的软件包 # 进入软件解压后的路径&#xff0c;如解压到了/home/soft/pycharm cd /home/soft/pycharm cd bin # 执行启动命令 sh pycharm.sh # 等待软件启…

京东云主机+京美建站SaaS版

京美建站SaaS版 京美建站搭建企业网站、小程序、3000精美模板 链接:https://daili.jd.com/s?linkNo57UBX34BZMWGNFYTOCPVUE7SN36CCIPKLTFLPCUCPYBKSYYBIPS2BJ57GP7RACLDHU66X526ZOULMIXL2VN7DT7IHU 京东云主机&#xff0c;安全稳定&#xff0c;性能强劲&#xff0c;新客下单…