亚马逊AWS官方博客
相得益彰 — AI Character 角色扮演应用的聊天对话管理方案
随着人工智能技术在近年来的飞速发展,尤其是生成式 AI 在各个领域展现出的惊人的潜力,正在改变着各行各业,而数据库在构建 AI 应用中发挥着关键作用,它不仅能为生成式 AI 提供稳定、可靠的数据来源,还能帮助系统有效地管理和组织海量数据,甚至提供一些优化基础,例如,在对话系统中,数据库可以储存用户的历史交互记录和偏好信息,帮助模型更好地理解用户的意图,生成更加个性化的回复。
我们将会持续撰写一系列博客, 探讨数据库如何为生成式 AI 应用提供动力支持,让生成式 AI 和数据库“相得益彰”。
首先,我们将重点关注 AI Character 角色扮演应用中的聊天对话管理。这是生成式 AI 在实际应用中的一个重要场景,需要借助数据库来有效管理大量的对话数据,以确保角色表现的连贯性和自然性,体现场景的快速响应和高频互动。
其次,我们将介绍向量数据库在生成式 AI 中的应用。这种新兴的数据库技术能够高效存储和检索基于向量的数据,为生成式 AI 模型提供快速、精准的相似性匹配,从而增强其语义理解能力。
后续我们还会探讨基于图数据库的生成式 AI 应用,比如 GraphRAG。图数据库能够更好地捕捉实体之间的复杂关系,为生成式 AI 模型提供更为丰富的支持,从而生成更加贴近现实的内容。
接下来就进入我们的第一篇博客。
随着生成式 AI 的日新月异,飞速演进,AI 角色在各种应用领域发挥着越来越重要的作用,如游戏、虚拟助理、教育等。构建一个用户体验良好的 GenAI 应用程序需要考虑以下几个关键方面:
- 提示词工程和对话模板设计:通过优化用户输入提示词的方式,并配合相关的对话模板,可以更好地引导大语言模型生成相关且高质量的响应内容。
- 对话内容管理:记录并利用与用户的历史对话,有助于大语言模型生成更加贴合上下文和避免重复的响应。
- 大模型处理策略:可根据不同用户群体的需求,采用多模型网关调用不同的大语言模型(如 Mistral 7B、Llama 等),为不同类型的用户提供差异化服务,同时,利用 RAG 知识增强或微调等技术,为大模型提供外部知识输入或生成定制化模型,并对模型输出结果进行评估。
- 数据处理与分析:采集、清洗 GenAI 应用服务产生的数据,并对数据进行深入分析,从而制定相应的运营策略,持续优化应用程序。
角色扮演对话应用具有实时性、快速响应和高频互动等特点,因此会话管理至关重要。本文将聚焦于此,旨在为用户提供更自然、流畅的体验,并提高 AI 角色在上下文记录、短期记忆和长期记忆等方面的对话管理能力。 首先,我们将探讨展开会话场景的挑战,随后,我们将介绍亚马逊云科技数据库的优势,最后,针对不同诉求,我们将介绍几种不同的会话管理方案,并阐述相应的选择策略。
会话场景挑战
在对话式 AI 系统中保存历史聊天记录并进行会话管理存在较大的挑战:
海量对话数据处理: 以某头部客户 APP 为例,每日拥有百万级别的活跃用户,加上平台上成百上千的角色卡,每个用户与角色的平均互动时长约 15 分钟,会产生海量的对话数据,需要具备海量数据的存储和管理能力。
快速查询与上下文管理:用户在与 AI 角色进行对话时,需要快速检索相关的角色信息和历史对话记录,作为 Prompt 的一部分进行推理。数据库的查询性能必须足够高,以支持实时响应。同时需要有效管理对话的上下文信息,处理复杂的查询和关联操作。用户与 AI 角色的对话通常是 1 对 1 的,但偶尔也存在一个用户同时和 2 到 3 个 AI 角色同时聊天的情况。
并发访问与数据一致性:多个用户同时进行对话时,数据库需要支持高并发的读取请求,避免成为性能瓶颈。此外,角色有可能由一个用户创建,而被其他用户使用。也有可能存在同一用户在不同终端进行登录,需要确保信息的一致性和完整性。
长期存储与固定对话轮次:充分考虑数据的长期存储策略,包括数据归档和清理机制,以确保数据库性能不会因过时数据的积累而下降。同时,对于固定对话轮次的应用,数据库设计需要有效存储和检索这些结构化数据,需要在对话过程中进行动态调用。
数据分析:对对话数据进行分析,提供有价值的洞察。包括分析用户行为模式、对话主题、角色偏好、情感倾向等等,这样的分析结果,可以改进用户体验、提升模型的效能。实现个性化和智能化的服务。
对数据库的诉求
角色扮演类应用会话管理对数据库端的具体诉求有:
- 存储会话信息。包含用户 ID、角色 ID、时间戳、会话文本消息、sender、其他信息的元数据(比如图片的 url)。
- 查询会话信息。需要查询出用户 ID 和某个角色 ID 的最近 N 条(比如 100 条)聊天记录,作为 prompt 合到一起发送给大模型。保留 100 条记录的原因是为了让模型不要“跳戏”,能够提供前后链接的会话信息支持。
- 会话信息过期自动删除。由于角色扮演类游戏产生会话信息较多,而且通常不是严肃的信息,所以过期信息可以自动删除。比如 N 条信息之前的信息可以自动删除,比如每个用户和每个角色交互的 100 条信息之后的信息可以自动删除。
- 用户显式删除信息。有时用户处于存储或者隐私考虑,需要删除和特定角色的聊天记录。所以需要删除和某个角色的全部聊天记录。
- 对话分析。统计每个用户的聊天频率,哪些用户喜欢聊天, 以及喜欢和哪个 role 聊天,可以针对性地对不同用户提供不同营销策略等。
会话场景中的数据库方案介绍
本文会介绍三种不同的会话管理方案,分别是利用 Amazon DocumentDB、Amazon DynamoDB 、Aurora Serverless V2 来进行会话管理。总体而言,DocumentDB 提供 JSON 文件的处理,不需要事先定义 schema,支持全文检索、聚合查询等较丰富的功能; DynamoDB 作为 key-value 的存储,能够提供良好的可扩展性,用户无需担心由于数据增长扩容数据库等操作;Aurora Serverless V2 作为关系数据库,上手较为容易,且提供存储和计算级别的扩展能力。用户可以根据自己的实际情况进行合适的选择。
利用 DocumentDB 存储/处理对话消息
DocumentDB 是一个云原生的 JSON 数据库,旨在为 WEB 应用提供可扩展的高性能数据存储解决方案。DocumentDB 最大的特点是它支持的查询语言非常强大,其语法有点类似于面向对象的查询语言,几乎可以实现类似关系数据库单表查询的绝大部分功能,而且还支持对数据建立二级索引以及全文检索索引。DocumentDB 提供了数据压缩的功能,可以通过压缩集合(表)中的数据降低存储成本和 IO 成本。DocumentDB 兼容 MongoDB API, 适合客户的多云战略和迁移,消除了客户云提供商的”锁定”担心。此外,DocumentDB 是完全托管的数据库,计算层和存储分离,存储可以随着数据的增长自动增长;读计算实例可以随读负载自由增/删, 从而可以节省实例的成本。
设计一个 DocumentDB 的 Collection 来存储 GenAI 的聊天对话消息时,我们可以考虑以下几个关键部分:
- 角色 ID:每个对话都应该有一个唯一的 ID,和哪个 GenAI 的角色聊天。这有助于我们跟踪和检索整个对话的历史记录。
- 用户 ID:与 GenAI 交互的用户或实体的唯一标识符。
- 消息内容:用户或 GenAI 发送的文本消息。
- 时间戳:消息发送的时间。
- 其他元数据(可选):如消息类型(例如,文本、图片等)、对话的上下文 ID(如果有多个并行的对话)等。
为对话聊天信息启用数据压缩功能
使用 db.createCollection() 方法在 Amazon DocumentDB 上创建某集合的同时启用文档压缩:
假如我们有以下对话消息:
针对后续对聊天信息不同的查询,归档,统计需求, 利用DocumentDB灵活的数据结构特性,我们可以有两种不同存储设计方案。
方案 1: 每个对话一行存储
会话历史表设计
将每个用户和 AI 角色每一次对话消息存储一次。这样可以很方便地利用 DocumentDB 的聚合管道功能,统计每个用户的聊天频率,哪些用户喜欢聊天, 以及喜欢和哪个 role 聊天;可以利用 DocumentDB 的全文检索功能,查询用户对哪些内容感兴趣,哪些是热词。
role_id 和 user_id 可以根据您的应用程序的需求进行设计。例如,您可以使用 UUID、哈希值或任何其他唯一标识符作为 ID。other_metadata 是一个可选字段,您可以根据需要添加其他与对话相关的元数据。
插入会话消息
查询会话消息
查找某用户(例如:user_abc123)跟某个 Role(例如:role_12345)聊天的消息记录
列出某用户(例如:user_abc123)跟哪些 Role 聊过天
查找哪些用户的聊天消息中提到了某个关键词(例如:“Amazon”)
查找包含指定关键字的对话(例如:“Amazon”)。查询按用户对结果进行分组,统计提及次数,并为每个用户标识最新的对话时间戳。最后,结果按提及次数降序排列
查找某用户和某角色之间最近 20 轮的聊天对话记录
删除旧的会话消息, 只保留最新的 100 条消息
方案 2:每个用户的每个 role 对话存储一行
会话历史表设计
按照每个用户和 role 的对话(session)管理对话消息,每个 session 的对话内容,多数是强关联的,这种方式可以按照用户感兴趣的话题进行查询, 并按照时间来计划保留对话消息的策略。这样可以减少很多冗余的数据存储, 不必要每个对话都存储一遍”User_id”和”Role_id”, 从而节省了存储空间。
查询会话消息
查找某用户(例如:user_abc123)跟某个 Role(例如:role_123456)聊天的消息记录
列出某用户(例如:user_abc123)跟哪些 Role 聊过天
查找用户和代理角色之间最近 20 轮的聊天对话记录
计算某个用户和所有 role 聊天的频率,来分析他比较喜欢哪个 role
删除会话消息
删除所有用户的旧对话消息,每个用户的每个 role 对话消息只保留最新的 100 条
删除 7 天前的消息
DocumentDB 对聊天记录这个场景的存储有一定优势的,结构 schema 灵活,可以应对经常性的代码迭代,而不用在业务代码层做过多的适配;支持数据压缩, 可以减低存储成本,提高写入吞吐量;丰富的查询, 可以满足客户对对话消息的查询和分析。
基于 DynamoDB 的会话管理方案
DynamoDB 是亚马逊云科技上的一款 NoSQL 数据库,具有很强的可扩展性,能够在应用负载变化时,保持个位数毫秒级别的响应。DynamoDB 是无服务器化的架构,用户只需要进行表级别操作即可,无需自己指明机型并时刻监控调整机型大小。对于 Role Play 面向终端用户的应用而言,终端用户数目的变化以及聊天信息的变化波动是很常见的,DynamoDB 可以节约这部分的时间成本,更加及时快速响应。
DynamoDB 的表由 partition key,sort key 以及其他列组成。数据会按照 partition key 进行组织,相同 partition key 的数据会按照 sort key 进行排序。Partition key 和 sort key 共同构成了表的主键。DynamoDB 根据对表的读写操作计费。事先根据可能的表操作来设计表结构,对 DynamoDB 比较关键。
DynamoDB 有 TTL(Time-to-Live)标识,对表开启了 TTL 之后,在插入或者更新某条数据时可以指定 RetiredAt 值,来标明该记录什么时候过期可以删除,DynamoDB 会自动在 TTL 到期后进行数据的清理。
对于用户聊天记录场景,可以考虑的一种设计思路是将 user_id 和 role_id 一起作为 partition key,时间 timestamp 作为 sort key,可以将消息存放在其他字段,同时在插入时设置 TTL 到期时间。这样,在读取 DynamoDB 表记录时,可以根据 user_id 和 role_id 进行查询,直接取前 100 条记录。同时因为存在 TTL,表数据会在到期时自动删除。
会话历史表设计
插入会话消息
查询会话消息
查询某个用户和某个 role 的最近 100 条会话记录
如果需要设置表中的 item 过期后自动 expire,可以参照该文档设置表的 expireAt 字段为 TTL 并在查询时,加入下面条件即可
以上描述的是一种简化的设计。读取最近多少条记录是按照消息条数来设置,而 TTL 是按照时间来设置的。如果 TTL 设置过短,比如 1 天,有可能有些用户最近 1 天内聊天记录较少,在读取最近 100 条记录时只能拿到几条记录,信息不足的情况。如果 TTL 设置过长,比如设置 1 个月,有可能存在数据过多导致占用较大存储空间的情况。如果您的业务场景需要严格地读取 100 条记录,也可以考虑如下设计。
大致思路类似于回环设计,类似于简化循环队列,不过只有入队操作,没有主动出队操作。队满以后再入队会触发出队和直接替换。同时需要有一个 marker 来描述队头位置,即现在的最新数据处于队列中的哪个位置。
具体而言,每个用户每个 role 只需要取前 100 条记录。所以 100 条记录都是 active。满了 100 条之后,循环替换,比如第 101 条插入到 slot 1 中。如果系统只需要保留 100 条记录,直接就不用存储 history 了,直接 update 即可。如果系统需要将所有记录保留比如 7 天,就在更新 slot 1 之前,将 slot 1 的数据读取出来,放到 history 行,同时设置 TTL 为消息本身写入时间的 create_time+7 天。
Partition key | Sort key | Latest_position | message | Create_time | expireAt |
User_id | Role_id#latest_position | 1 | |||
User_id | Role_id#active#slot1 | “Hello” | 00:01 | ||
User_id | Role_id#active#slot2 | “world” | 00:02 | ||
User_id | Role_id#active#slotN | “word N” | 00:10 | ||
User_id | Role_id#active#slot100 | “word 100” | 00:12 | ||
User_id | Role_id#history#ULID (timestamp) | “history1” | 00:00 | Create_time+7 days | |
User_id | Role_id#history#ULID (timestamp) | “history2“ | 00:00 | Create_time+7 days |
写入新消息流程
getItem(user_id, role_id#latest_position)
,拿到 latest_position- 计算下条记录需要写入的 position。
Position=(latest_position+1)%100
- (如果需要保留历史)
GetItem(user_id, role_id#active#slot$position)
得到 history_messageputItem(user_id, role_id#history#ULID, history_message, create_time+7days)
putItem(user_id, role_id#active#slot$position, message)
putItem(user_id, role_id#latest_position, position)
读取用户历史 100 条聊天记录信息
- 可以直接 Query(user_id, role_id#active#slot),读取出 100 条记录
- 按照记录的 Create_time 进行升序排列
删除用户的聊天记录
如果只保存 100 条,写入新记录时会自动替换原来的记录;如果需要保存更久的信息,history 表中设置的 TTL 可以自动完成删除操作。
用户显示删除所有信息 (DynamoDB delete 必须要指明 primary key 所有字段)
DynamoDB 用户一般使用 TTL 来控制数据的删除,不会显式直接删除数据。如果需要直接删除数据的话,可以采用下列方式:
- 先 list 所有符合条件的记录。Query 所有用户和 role_id 的数据,得到 sort key 的具体值。
- 逐条删除。可以直接 deleteItem 或者 updateItem 设置 delete marker 标记位。
上述设计是基于用户 ID 来进行表设计的,不需要实现每次对话时用户和角色的会话层级管理。如果需要进行会话层级的管理,或者一个会话里可能涉及一个用户和多个角色或者多个用户和一个用户之间的共同对话,也可以考虑按照会话进行表设计, 比如按照 chatID 来进行相应管理。应用程序诉求不同,可以进行相应的设计改动。这篇博客列出了另外一种设计思路。
基于 Aurora Serverless V2 的会话管理方案
如果您的消息记录不多,或者非常熟悉 SQL 方式,想复用关系数据库的话,我们建议您考虑 Aurora Serverless V2。 Aurora Serverless V2 可以做到实例级别的自动扩容,在您的负载发生变化时,自动扩展来满足业务诉求。目前 Aurora Serverless V2 单节点最大支持 128ACU,即 256GB 内存的大小。存储和普通 Aurora 一样,可以扩展到 128TB 的存储。如果 Role Play 的终端用户数量较少,沿用关系数据库也可以,但与此同时要考虑冷数据的归档处理。可以参考的设计如下:
会话信息表
存储会话信息。包含用户 ID、角色 ID、时间戳、会话消息、其他元信息材料。
也可以考虑增添一个标识位 deleted,来做软删除,即用户删除数据时,先将标记位置 1。
插入会话信息
查询会话信息
需要查询出用户 ID 和某个角色 ID 的最近 N 条(比如 100 条)聊天记录。
会话信息过期自动删除
由于角色扮演类游戏产生会话信息较多,而且通常不是严肃的信息,所以过期信息可以自动删除。比如 N 条信息之前的信息可以自动删除,比如每个用户和每个角色交互的 100 条信息之后的信息可以自动删除。
每次查找信息的时候拿到 tobeDeletedTimeStamp,即从上个步骤拿到的 timestamp。保存成一个 global 变量,即早于这个时间戳的记录都可以被删除。
用户显式删除信息
有时用户处于存储或者隐私考虑,需要删除和特定角色的聊天记录。所以需要删除和某个角色的全部聊天记录。
删除操作本身对关系数据库来说消耗比较大,也可以进行相应优化,比如创建分区表,按照时间,建立新的分区,并阶段性 drop 原来的分区,带来的成本消耗会比 delete 要低。此外,用户量较大时,也可以考虑根据用户 id 进行分库分表,存放在不同的数据库上。实际上,消息历史记录存储没有严格的 ACID 的需求,如果可能的话,考虑扩展性更好的 DynamoDB 或者擅长文档处理的 DocumentDB 不失为更好的选择。
结论
角色扮演场景作为一类 GenAI 的应用,考虑如何进行有效的消息存储和管理也非常重要。本篇博客抽取了角色扮演类应用对数据存取的需求,并介绍了三种数据库的实现逻辑:更适合文档处理、进行文本汇聚等操作的 DocumentDB,扩展性强、能够稳定提供个位数毫秒延迟的 DynamoDB,以及自动扩展的关系型数据库 Aurora Serverless V2。 希望能对您设计角色扮演应用消息对话系统时有所帮助。