接入存储适配器#
包装一个已有的存储后端,以记录持久化操作,同时让持久化扩展管理对话历史记录。
运行#
在仓库根目录下执行:
uv run python docs/_examples/storage_adapter.py
输出包含:
load, append:system, append:user, append:assistant
这个程序在需要时使用确定性的离线模型和临时存储。它不需要 API 密钥,并由文档测试套件执行。
适配存储,而不是智能体循环#
AuditedStorage 把读写委托给 SQLite,同时记录它们的顺序。扩展只需要构造这个适配器:
class AuditedPersistence(BaseSessionPersistenceExtension[AuditedStorage]):
"""Only construct the adapter; the base owns all lifecycle integration."""
def __init__(self, database: SQLiteSessionStorage) -> None:
super().__init__(AuditedStorage(database))
完整源码实现了加载历史记录、追加消息和保存压缩检查点所需的存储方法。如果你用其他数据库替换 SQLite,请保留顺序、事务和版本校验;这些保证在安全恢复对话时很重要。
完整源码#
1"""Adapt a storage implementation without reimplementing persistence hooks."""
2
3import asyncio
4from collections.abc import AsyncIterator, Mapping
5from pathlib import Path
6from tempfile import TemporaryDirectory
7
8from zett_agent.agent import AgentRunConfig
9from zett_agent.client import create_agent
10from zett_agent.extensions.compaction import CompactedMessage
11from zett_agent.extensions.events import MessageTiming
12from zett_agent.extensions.persistence import (
13 BaseSessionPersistenceExtension,
14 ContextSnapshot,
15 SessionView,
16)
17from zett_agent.json_types import JsonValue
18from zett_agent.messages import (
19 AnyMessage,
20 AssistantMessage,
21)
22from zett_agent.model import (
23 ModelEvent,
24 ModelRequest,
25 ModelResponse,
26 ModelUsage,
27 RetryOptions,
28)
29from zett_agent.storage import SQLiteSessionStorage
30
31
32class AuditedStorage:
33 """A transparent adapter with an audit trail around an existing SQLite store."""
34
35 def __init__(self, database: SQLiteSessionStorage) -> None:
36 self.database = database
37 self.operations: list[str] = []
38
39 async def load(self, session_id: str) -> SessionView:
40 self.operations.append("load")
41 return await self.database.load(session_id)
42
43 async def append(
44 self,
45 session_id: str,
46 request_id: str,
47 message: AnyMessage,
48 timing: MessageTiming | None = None,
49 parent_session_id: str | None = None,
50 title: str | None = None,
51 agent_name: str | None = None,
52 metadata: Mapping[str, JsonValue] | None = None,
53 tags: Mapping[str, JsonValue] | None = None,
54 usage: ModelUsage | None = None,
55 ) -> int:
56 self.operations.append(f"append:{message.role.value}")
57 return await self.database.append(
58 session_id, request_id, message, timing, parent_session_id, title, agent_name, metadata, tags, usage
59 )
60
61 async def snapshot(
62 self,
63 session_id: str,
64 compacted_message: CompactedMessage,
65 compacted_through_sequence: int,
66 expected_version: int,
67 ) -> ContextSnapshot:
68 self.operations.append("snapshot")
69 return await self.database.snapshot(session_id, compacted_message, compacted_through_sequence, expected_version)
70
71
72class AuditedPersistence(BaseSessionPersistenceExtension[AuditedStorage]):
73 """Only construct the adapter; the base owns all lifecycle integration."""
74
75 def __init__(self, database: SQLiteSessionStorage) -> None:
76 super().__init__(AuditedStorage(database))
77
78
79class FixedModel:
80 retry = RetryOptions(max_retries=0)
81
82 async def stream(self, request: ModelRequest) -> AsyncIterator[ModelEvent]:
83 yield ModelEvent.completed(ModelResponse(AssistantMessage(content="Saved.")))
84
85
86async def main() -> None:
87 with TemporaryDirectory(prefix="zett-storage-adapter-") as directory:
88 database = SQLiteSessionStorage(Path(directory) / "sessions.sqlite")
89 try:
90 extension = AuditedPersistence(database)
91 client = await create_agent(FixedModel(), config=AgentRunConfig(session_id="audit"), extensions=[extension])
92 await client.run("Remember this")
93 assert extension.storage.operations == ["load", "append:system", "append:user", "append:assistant"]
94 print(", ".join(extension.storage.operations))
95 assert len(await database.list_raw_messages("audit")) == 3
96 finally:
97 await database.close()
98
99
100if __name__ == "__main__":
101 asyncio.run(main())