LlamaIndex GelChatStore 详解:用 Gel 数据库持久化聊天记忆存储(Chat Store)
【免费下载链接】llama_indexLlamaIndex is the leading document agent and OCR platform项目地址: https://gitcode.com/GitHub_Trending/ll/llama_index
本文围绕 LlamaIndex 的llama_index.storage.chat_store.gel模块展开,讲解官方 API 参考文档中唯一的成员GelChatStore:它如何基于 Gel 数据库实现 LlamaIndex 的聊天存储接口(BaseChatStore),包括 Gel 项目初始化、Record数据类型的 schema 定义、同步/异步双客户端机制、各持久化方法背后的 GEL 查询语句,以及如何与ChatMemoryBuffer配合实现跨会话的聊天历史自动持久化。读完后你可以独立完成一个基于 Gel 的聊天存储后端部署与集成。
1. GelChatStore 在 LlamaIndex 存储体系中的定位
LlamaIndex 通过抽象基类 BaseChatStore 定义了"按 key 存取聊天历史"的存储接口,要求实现以下 7 个抽象方法:
| 抽象方法 | 作用 |
|---|---|
set_messages(key, messages) | 整体覆盖写入某 key 的完整消息列表 |
get_messages(key) | 读取某 key 的消息列表 |
add_message(key, message) | 向某 key 追加一条消息 |
delete_messages(key) | 删除某 key 的全部消息 |
delete_message(key, idx) | 删除某 key 下指定索引的消息 |
delete_last_message(key) | 删除某 key 的最后一条消息 |
get_keys() | 列出所有 key |
基类同时为每个方法提供了默认的异步实现(aset_messages、aget_messages、async_add_message、adelete_messages、adelete_message、adelete_last_message、aget_keys),默认通过asyncio.to_thread在线程池中执行同步方法(见 base.py)。
GelChatStore位于集成包llama-index-storage-chat-store-gel中,继承自BaseChatStore(见 base.py),并不是简单复用基类的线程池默认实现,而是针对 Gel 官方驱动的异步客户端实现了原生的async方法。官方 API 参考文档 gel.md 正是对该模块(llama_index.storage.chat_store.gel)及GelChatStore类的全量接口文档。
2. 安装与环境要求
集成包 README(README.md)给出的安装方式:
pip install llama-index-storage-chat-store-gel从 pyproject.toml 可确认该包的约束与依赖:
| 项目 | 取值 |
|---|---|
| 包名 | llama-index-storage-chat-store-gel(当前版本 0.3.0) |
| Python | >=3.10,<4.0 |
| 核心依赖 | llama-index-core>=0.13.0,<0.15、gel>=3.0.1、jinja2>=3.1.4 |
gel驱动负责连接本地 Gel 服务实例,jinja2则用于在运行期渲染"schema 缺失 record 类型"时的报错模板(见第 5 节)。模块导入处对gel包做了强制检查,未安装时会记录错误日志并抛出ImportError,提示pip install gel(见 base.py#L44-L48)。
3. Gel 项目初始化与 Record 类型 Schema
GelChatStore的前置条件是:工作目录中必须已存在一个初始化好的 Gel 项目,且 schema 中定义了 store 所使用的 record 类型。
3.1 初始化项目
集成包的测试文件(test_chat_store_gel_chat_store.py)展示了初始化命令:
gel project init --non-interactive其中--non-interactive用于在非交互式环境(如 CI、脚本)中完成初始化;在 CI 环境中(检测到CI环境变量)该步骤会被跳过,相关用例以skip_in_cicd标记跳过。
3.2 必须存在的 Record 类型
集成包自带的 schema 文件 dbschema/default.gel 给出了完整定义:
module default { type Record { required key: str { constraint exclusive; } value: array<json>; } }要点说明:
key: str是唯一约束的会话键(constraint exclusive),即每个chat_store_key(如"user1")对应 Gel 中的一行;value: array<json>存放该会话的全部消息,每条ChatMessage以 JSON 字符串形式(model_dump_json()序列化)作为数组元素,消息顺序即数组下标。
该 schema 也与源码内置的报错模板 MISSING_RECORD_TYPE_TEMPLATE 完全一致——当 schema 中缺少 record 类型时,日志会原样打印上述定义并提示执行迁移:
$ gel migration create $ gel migrate集成包目录中同样保留了迁移文件 00001-m1ze2pu.edgeql 供参考;本地 gel.toml 声明了server-version = "6.4",可视为该集成验证过的 Gel 服务端版本参考。
4. 构造函数与 record_type 参数
GelChatStore的构造签名(见 base.py#L148-L162):
class GelChatStore(BaseChatStore): record_type: str _sync_client: Optional[gel.Client] = PrivateAttr() _async_client: Optional[gel.AsyncIOClient] = PrivateAttr() def __init__(self, record_type: str = "Record"): super().__init__(record_type=record_type) self._sync_client = None self._async_client = None参数说明:
| 参数 | 默认值 | 说明 |
|---|---|---|
record_type | "Record" | Gel schema 中 record 类型名,默认与dbschema/default.gel中的type Record对应;若你在 schema 中自定义了类型名,需在此传入 |
两个客户端属性_sync_client(gel.Client)与_async_client(gel.AsyncIOClient)均为PrivateAttr惰性创建,首次调用对应模式的方法时才建立连接。
5. 客户端管理:同步/异步互斥与启动校验
5.1 惰性连接与启动自检
get_sync_client() 与 get_async_client() 的逻辑一致,首次调用时依次完成:
gel.create_client()/gel.create_async_client()创建客户端;ensure_connected()建立连接——失败时记录NO_PROJECT_MESSAGE(提示执行gel project init)并抛出ClientConnectionError;- 执行自检查询
select {record_type};——若 schema 中不存在该类型,捕获InvalidReferenceError,用 Jinja2 模板渲染缺失类型的完整 schema 片段和迁移步骤后抛出。
这套"连接 + schema 自检"机制把最常见的两类部署错误(未初始化项目、忘记加 Record 类型/迁移)转化成了带操作指引的错误日志。
5.2 同一实例禁止混用同步与异步
两个get_*_client()均带有互斥检查:若实例已经以异步方式使用过(_async_client is not None),再调用同步方法会抛出RuntimeError,提示"如需同时使用不同 IO 模式,请创建新实例"(反向亦然,见 base.py#L166-L171 与 base.py#L195-L200)。测试代码中为同步用例和异步用例分别提供了独立的GelChatStore实例(fixture 定义),印证了这一约束。
6. 持久化方法全览与对应 GEL 查询
GelChatStore共暴露 14 个方法(7 个同步 + 7 个异步),每个方法都对应一条模块级常量定义的 GEL 查询语句。下面逐条说明。
6.1 写入:set_messages / aset_messages
SET_MESSAGES_QUERY = format_query( """ insert Record { key := <str>$key, value := <array<json>>$value } unless conflict on .key else ( update Record set { value := <array<json>>$value } ) """ )实现(base.py#L222-L238):
def set_messages(self, key: str, messages: list[ChatMessage]) -> None: client = self.get_sync_client() client.query( SET_MESSAGES_QUERY, key=key, value=[message.model_dump_json() for message in messages], )- 消息序列化为 JSON 字符串数组;
- 使用
insert ... unless conflict on .key else (update ...)的upsert 语义:key 不存在则插入,已存在则整体覆盖value,实现"设置"而非"追加"的语义。
6.2 追加:add_message / async_add_message
ADD_MESSAGE_QUERY = format_query( """ insert Record { key := <str>$key, value := <array<json>>$value } unless conflict on .key else ( update Record set { value := .value ++ <array<json>>$value } ) """ )与SET_MESSAGES_QUERY的结构相同,区别在冲突分支:value := .value ++ <array<json>>$value使用数组拼接把新消息追加到原数组末尾,保留历史顺序。
6.3 读取:get_messages / aget_messages
GET_MESSAGES_QUERY = format_query( """ with record := (select Record filter .key = <str>$key), select record.value; """ )def get_messages(self, key: str) -> list[ChatMessage]: client = self.get_sync_client() result = client.query_single(GET_MESSAGES_QUERY, key=key) or [] return [ChatMessage.model_validate_json(message) for message in result]注意query_single(...) or []的兜底:key 不存在时查询返回空,直接得到[]而不抛异常;反序列化通过ChatMessage.model_validate_json逐条完成。
6.4 整键删除:delete_messages / adelete_messages
DELETE_MESSAGES_QUERY = format_query( """ delete Record filter .key = <str>$key """ )直接删除整行 Record。
6.5 按索引删除与删除最后一条
DELETE_MESSAGE_QUERY = format_query( """ with idx := <int64>$idx, value := (select Record filter .key = <str>$key).value, idx_item := value[idx], new_value := value[:idx] ++ value[idx+1:], updated_record := ( update Record filter .key = <str>$key set { value := new_value } ) select idx_item; """ )DELETE_MESSAGE_QUERY在一次查询内完成三件事:切片重建数组value[:idx] ++ value[idx+1:](GEL 支持数组切片)、更新 Record、并select idx_item返回被删除的消息本身——这正是delete_message能够返回Optional[ChatMessage]的原因。DELETE_LAST_MESSAGE_QUERY同理,用value[len(value) - 1]取出末位元素并返回。
def delete_message(self, key: str, idx: int) -> Optional[ChatMessage]: client = self.get_sync_client() result = client.query_single(DELETE_MESSAGE_QUERY, key=key, idx=idx) return ChatMessage.model_validate_json(result) if result else None6.6 列出全部 key:get_keys / aget_keys
GET_KEYS_QUERY = format_query( """ select Record.key; """ )返回数据库中所有会话键,可用于清理或审计。
6.7 方法对照表
| 同步方法 | 异步方法 | 语义 | 对应查询 |
|---|---|---|---|
set_messages(key, messages) | aset_messages | 覆盖写入 | SET_MESSAGES_QUERY(upsert 覆盖) |
get_messages(key) | aget_messages | 读取全部 | GET_MESSAGES_QUERY |
add_message(key, message) | async_add_message | 追加一条 | ADD_MESSAGE_QUERY(upsert 拼接) |
delete_messages(key) | adelete_messages | 删除整键 | DELETE_MESSAGES_QUERY |
delete_message(key, idx) | adelete_message | 删除指定索引并返回被删消息 | DELETE_MESSAGE_QUERY |
delete_last_message(key) | adelete_last_message | 删除末位并返回被删消息 | DELETE_LAST_MESSAGE_QUERY |
get_keys() | aget_keys | 列出所有 key | GET_KEYS_QUERY |
与BaseChatStore不同,这里的异步方法并非asyncio.to_thread包装同步调用,而是直接使用gel.AsyncIOClient执行await client.query(...)/await client.query_single(...)(见 base.py#L231-L250),在高并发异步服务中避免了线程池开销。
7. 与 ChatMemoryBuffer 集成:聊天历史自动持久化
README 给出的典型用法(完整保留如下):
from llama_index.storage.chat_store.gel import GelChatStore from llama_index.core.memory import ChatMemoryBuffer chat_store = GelChatStore() chat_memory = ChatMemoryBuffer.from_defaults( token_limit=3000, chat_store=chat_store, chat_store_key="user1", )该用法背后的调用链可以从核心源码得到印证:ChatMemoryBuffer持有chat_store与chat_store_key字段(chat_memory_buffer.py),并在构造时执行chat_store.set_messages(chat_store_key, chat_history)恢复历史(chat_memory_buffer.py#L75);其内部ChatMemoryBufferStore通过self.chat_store.add_message(self.chat_store_key, message)/set_messages/delete_messages等方法读写存储(见 types.py)。因此:
chat_store_key="user1"对应 Gel 中Record.key的一个取值,每个用户/会话一个键,天然实现多租户隔离;- 每次对话追加消息走
add_message(数组拼接),超出token_limit时由 memory 层触发裁剪,裁剪后通过set_messages覆盖回写; - 应用重启后再次以相同 key 构建
ChatMemoryBuffer,即可从 Gel 恢复历史,无需手动保存/加载。
8. 测试用例:可复用的行为验证方式
集成包测试 test_chat_store_gel_chat_store.py 覆盖了对称的同步/异步全量行为,可作为自验脚本参考:
test_gel_add_message/test_async_gel_add_message:追加单条后校验content与role;test_set_and_retrieve_messages/ 异步版:set_messages写入两条后按序读回;test_delete_messages/ 异步版:整键删除后get_messages返回[];test_delete_specific_message/ 异步版:delete_message(key, 1)返回被删消息对象,剩余消息保持原序;test_delete_last_message/ 异步版:删除末位消息并校验其内容;test_get_keys/ 异步版:确认写入的两个键均可被get_keys()列出。
测试的 fixture 在结束后会遍历get_keys()逐一delete_messages(key)清理数据(L24-L48),本地验证时可直接借鉴该清理逻辑,避免残留测试会话。
9. 排错要点汇总
| 现象 | 根因 | 处置 |
|---|---|---|
ImportError(gel 包缺失) | 未安装驱动 | pip install gel(版本要求>=3.0.1) |
| 连接失败,日志提示未初始化项目 | 当前目录没有 Gel 项目 | 执行gel project init(非交互环境加--non-interactive) |
InvalidReferenceError,日志打印 schema 片段 | schema 缺少record_type类型 | 将type Record { required key: str { constraint exclusive; } value: array<json>; }加入dbschema/default.gel,然后gel migration create+gel migrate |
RuntimeError: GelChatStore has already been used in ... mode | 同一实例混用同步/异步 API | 为不同 IO 模式分别创建GelChatStore实例 |
10. 小结
GelChatStore是 LlamaIndex 聊天存储接口在 Gel 数据库上的完整落地:一条key + array<json>的极简 schema 支撑起会话级覆盖、追加、按序删除与末位删除等全部语义;通过record_type参数适配自定义类型名;通过惰性双客户端 + 启动自检把部署错误提前暴露;通过原生异步实现满足async场景。配合ChatMemoryBuffer(chat_store=..., chat_store_key=...),即可用几行代码获得按用户隔离、跨进程重启可恢复的聊天记忆。参考文件:模块文档、实现源码、schema 定义、测试用例。
【免费下载链接】llama_indexLlamaIndex is the leading document agent and OCR platform项目地址: https://gitcode.com/GitHub_Trending/ll/llama_index
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考