LlamaIndex UpstashChatStore API 深度解析:基于 Upstash Redis 的对话历史存储
【免费下载链接】llama_indexLlamaIndex is the leading document agent and OCR platform项目地址: https://gitcode.com/GitHub_Trending/ll/llama_index
本篇围绕 LlamaIndex 的llama_index.storage.chat_store.upstash模块(API 参考页 upstash.md 所指向的UpstashChatStore类)展开,讲解如何在 LlamaIndex 应用中用 Upstash Redis 作为对话记忆的远端持久化后端:覆盖安装、构造参数、全部同步/异步 API 的实际调用方式,以及源码层面基于 Redis List 的存取、按索引删除与 TTL 过期实现细节,帮助你在构建多用户、多实例的对话应用时正确选型和排错。
模块定位:API 参考页指向什么
关联文档 docs/api_reference/api_reference/storage/chat_store/upstash.md 的内容非常简短:
::: llama_index.storage.chat_store.upstash options: members: - UpstashChatStore这是 mkdocs 的自动 API 文档指令,它声明本页面的文档对象是llama_index.storage.chat_store.upstash模块,且只列出其中的UpstashChatStore一个成员。也就是说,该 API 参考页的实质就是UpstashChatStore这个类的公开接口。这个类定义在集成包 base.py 中,由init.py 通过__all__ = ["UpstashChatStore"]对外导出。
UpstashChatStore继承自核心包中的 BaseChatStore(llama_index.core.storage.chat_store.base.BaseChatStore),因此它遵循 LlamaIndex 统一的"按 key 存储对话消息"抽象:每个 key(通常是用户 ID 或会话 ID)对应一个ChatMessage列表。
BaseChatStore定义了 7 个同步抽象方法(set_messages、get_messages、add_message、delete_messages、delete_message、delete_last_message、get_keys),其默认异步实现只是把同步方法丢进线程池执行(见 base.py#L52-L78)。而UpstashChatStore额外实现了原生异步方法(async_set_messages、async_get_messages等),直接使用 Upstash Redis 官方 SDK 的 asyncio 客户端,这是它相对基础实现的关键差异点。
安装与依赖版本
按照集成包 README.md 与框架文档 chat_stores.md 中的说明,安装命令为:
pip install llama-index-storage-chat-store-upstash结合 pyproject.toml 可以看到当前仓库中该包的版本约束:
| 项目 | 约束 | 说明 |
|---|---|---|
| 包版本 | 0.4.0 | 当前仓库内的发布版本 |
| Python | >=3.10,<4.0 | 需要 Python 3.10 及以上 |
upstash_redis | >=1.1.0,<2 | Upstash 官方 Redis 客户端(含 REST URL + Token 的 serverless 接入方式) |
llama-index-core | >=0.13.0,<0.15 | 与核心包的兼容区间 |
使用前提:你需要一个 Upstash Redis 实例,并拿到它的 REST URL 与 Token(通常形如https://<database-id>.upstash.io)。
构造参数详解
UpstashChatStore的构造函数签名与参数语义(见 base.py#L46-L73):
from llama_index.storage.chat_store.upstash import UpstashChatStore chat_store = UpstashChatStore( redis_url="YOUR_UPSTASH_REDIS_URL", # Upstash Redis 实例的 URL,必填 redis_token="YOUR_UPSTASH_REDIS_TOKEN", # 实例的认证 Token,必填 ttl=300, # 可选,单位:秒;为 key 设置过期时间 )源码层面有三个值得注意的实现细节:
- 参数校验:
redis_url或redis_token为空字符串时直接抛出ValueError("Please provide a valid URL and token"),即两者都缺省值("")的情况下无法构造成功; - 双客户端实例化:构造函数会同时创建同步客户端
SyncRedis(upstash_redis.Redis)和异步客户端AsyncRedis(upstash_redis.asyncio.Redis),分别存于私有属性_sync_redis_client与_async_redis_client,所以同步与异步 API 可以混用; - 初始化失败只记日志不抛异常:如果客户端初始化过程中发生异常,代码只调用
logger.error(...)记录错误而不会中断构造,后续调用会在使用客户端时才暴露问题——排错时建议留意日志输出。
ttl同时是 pydantic 的公开字段(ttl: Optional[int] = Field(default=None, description="Time to live in seconds.")),默认为None表示不设过期;也可以在构造后直接赋值(例如chat_store.ttl = 300),源码中的 TTL 测试正是这样做的(见 test_chat_store_upstash_chat_store.py#L108-L117)。
完整 API:同步与异步方法一览
UpstashChatStore公开的方法成对出现(同步版 +async_前缀异步版),与BaseChatStore接口一一对应:
| 同步方法 | 异步方法 | Redis 操作 | 行为 |
|---|---|---|---|
set_messages(key, messages) | async_set_messages(key, messages) | DEL+ 逐条RPUSH+EXPIRE | 整体替换某 key 下的消息列表 |
get_messages(key) | async_get_messages(key) | LRANGE key 0 -1 | 取回完整消息列表,空时返回[] |
add_message(key, message, idx=None) | async_add_message(key, message, idx=None) | RPUSH(或读改写)+EXPIRE | 追加或按索引插入单条消息 |
delete_messages(key) | async_delete_messages(key) | DEL | 删除整个 key,固定返回None |
delete_message(key, idx) | async_delete_message(key, idx) | LINDEX+LSET+LREM | 删除指定索引的消息并返回它 |
delete_last_message(key) | async_delete_last_message(key) | RPOP | 弹出并返回最后一条消息 |
get_keys() | async_get_keys() | KEYS * | 返回 store 中全部 key |
此外还有一个类方法class_name(),固定返回字符串"UpstashChatStore",用于 LlamaIndex 的组件序列化/反序列化标识。
源码级实现解析
消息的序列化:JSON 字符串存入 Redis List
每个 key 在 Redis 中就是一个 List,每条消息是一个 JSON 字符串。写入路径是 _message_to_dict(对ChatMessage调用.dict())再经json.dumps序列化后rpush到列表尾部;读取路径则是lrange(key, 0, -1)取全量后逐条ChatMessage.parse_raw(item)反序列化(见 base.py#L118-L133)。这意味着消息的 role、content、additional_kwargs 等字段都会原样保留在远端。
set_messages的"先删后写"语义
同步版实现(base.py#L86-L100)先delete(key)清掉旧列表,再逐条add_message追加。由于每条add_message在设置ttl时都会刷新一次EXPIRE,最后set_messages末尾还会再显式expire(key, self.ttl),保证整个列表从写入完成时刻开始计 TTL。
按索引删除:占位符 + LSET/LREM 两步法
Redis 没有原子的"按索引删除列表元素"命令,源码用了一个巧妙的两步方案(base.py#L222-L250):
lindex(key, idx)先取出要删除的消息;lset(key, idx, placeholder)把该位置替换为占位符字符串f"{key}:{idx}:deleted";lrem(key, 1, placeholder)按值匹配删除这一个占位符。
异常(例如 idx 越界导致lindex失败)会被捕获并记录日志后返回None,与BaseChatStore接口中Optional[ChatMessage]的返回类型一致。
按索引插入:读—改—写
add_message(key, message, idx=...)指定索引时,会走_insert_element_at_index(base.py#L334-L356):先get_messages拉取当前全部消息,在 Python 列表中insert(idx, message),然后delete(key)清库并set_messages整体重写。从源码结构看,这是一种简单直白的实现,代价是每次索引插入产生一次全量读加一次全量写,对超长会话列表有一定开销,但对常规对话长度完全够用。
TTL 的触发点
只要构造时(或运行中)设置了ttl,以下写操作后都会调用expire(key, self.ttl)刷新过期时间:set_messages、add_message、delete_message。也就是说 TTL 是"每次活动后重置"的滑动过期模式,适合做"不活跃会话自动清理"。
与 ChatMemoryBuffer 集成
框架文档 chat_stores.md 将UpstashChatStore描述为:借助 Upstash 提供的 serverless Redis 服务,将聊天历史存储到远端,适合需要可扩展、高效聊天存储的应用场景。典型用法是把 chat store 挂到ChatMemoryBuffer上:
from llama_index.storage.chat_store.upstash import UpstashChatStore from llama_index.core.memory import ChatMemoryBuffer chat_store = UpstashChatStore( redis_url="YOUR_UPSTASH_REDIS_URL", redis_token="YOUR_UPSTASH_REDIS_TOKEN", ttl=300, # 可选:过期时间(秒) ) chat_memory = ChatMemoryBuffer.from_defaults( token_limit=3000, chat_store=chat_store, chat_store_key="user1", # 用 key 区分不同用户/会话 )其中chat_store_key决定对话落在 Redis 的哪个 List key 下,多租户场景下通常传用户 ID;token_limit控制内存中保留最近多少 token 的上下文,超出的部分仍保存在远端 store 中。
在异步上下文中也可以直接操作 store(来自集成包 README 的示例):
import asyncio from llama_index.core.llms import ChatMessage async def main(): messages = [ ChatMessage(content="Hello", role="user"), ChatMessage(content="Hi there!", role="assistant"), ] await chat_store.async_set_messages("conversation1", messages) retrieved_messages = await chat_store.async_get_messages("conversation1") print(retrieved_messages) deleted_message = await chat_store.async_delete_last_message("conversation1") print(f"Deleted message: {deleted_message}") asyncio.run(main())测试用例与验证现状
集成包附带了完整的测试套件 test_chat_store_upstash_chat_store.py,覆盖:非法参数初始化(期望ValueError)、追加/读取/删除单条与全部消息、按索引插入后的顺序断言、TTL 过期后列表为空(ttl=3后等待 4 秒)、get_keys返回已写入的 key,以及对应的全套 async 版本测试。测试通过环境变量UPSTASH_REDIS_REST_URL与UPSTASH_REDIS_REST_TOKEN连接真实 Upstash 实例。
需要说明的是:从源码看,该测试文件中所有用例当前都标注了@pytest.mark.skip(reason="Skipping all tests"),即仓库内这些集成测试处于跳过状态,不会在 CI 中实际运行。如果你要在本地验证该 store 的行为,需要在设置好上述两个环境变量的前提下手动解除跳过。测试文件中的断言(例如"删除索引 1 后剩余 1 条且内容是 First message"、"TTL=3 秒等待 4 秒后取回空列表")也恰好印证了上文对delete_message与 TTL 行为的源码分析。
使用建议与注意事项
- URL 与 Token 缺一不可:两者任一为空会直接
ValueError;建议从环境变量读取,避免硬编码到代码库。 - 初始化失败是"静默"的:客户端创建异常只记日志,程序要继续靠
redis_url/redis_token的有效性自行保证,上线前建议先用get_keys()做一次连通性检查。 get_keys使用KEYS *:base.py#L312-L321 直接执行keys("*")并做了一次"若非 list 则包一层 list"的防御性处理。生产环境中如果该 Redis 实例上有大量其他 key,从源码结构看,这一步的扫描代价值得留意,必要时可以在业务侧自行维护 key 前缀约定。- TTL 语义是滑动过期:每次写操作刷新过期时间,适合会话级别的自动清理,而不适合做严格的绝对过期时间。
- 版本兼容区间:当前仓库中该包锁定
llama-index-core>=0.13.0,<0.15与 Python 3.10+,升级核心包或客户端大版本时建议先跑一遍本地集成验证。
相关文档与源码入口
| 资源 | 路径 |
|---|---|
| API 参考页(本文对应文档) | docs/api_reference/api_reference/storage/chat_store/upstash.md |
UpstashChatStore实现 | base.py |
| 集成包 README(安装与示例) | README.md |
| 集成包测试 | test_chat_store_upstash_chat_store.py |
| 依赖声明 | pyproject.toml |
| Chat Store 框架指南(含 Upstash 章节) | chat_stores.md |
抽象基类BaseChatStore | llama-index-core/llama_index/core/storage/chat_store/base.py |
【免费下载链接】llama_indexLlamaIndex is the leading document agent and OCR platform项目地址: https://gitcode.com/GitHub_Trending/ll/llama_index
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考