恢复 SQLite 历史记录#

重新打开一个真实的临时数据库,恢复历史记录,对消息分页,更新标题,然后删除会话。

运行示例#

在仓库根目录下:

uv run python docs/_examples/sessions.py

输出包含:

Raw roles: system, user, assistant, system, user, assistant

这个程序使用确定性的离线模型,并在需要时使用临时存储。它不需要 API 密钥,由文档测试套件执行。

可以带入你应用的做法#

为一个对话保留一个稳定的 session_id,并在进程重启时针对同一个数据库打开持久化扩展。示例在创建第二个客户端之前先关闭了第一个连接,因此恢复的历史记录来自 SQLite,而不是内存缓存。

async def main() -> None:
    # No demo touches the default database under the user's home directory.
    with TemporaryDirectory(prefix="zett-docs-") as directory:
        path = Path(directory) / "sessions.sqlite"
        session_id = "project-notes"
        config = AgentRunConfig(session_id=session_id)
        first_storage = SQLiteSessionExtension(path)
        try:
            first = await create_agent(TurnModel(), config=config, extensions=[first_storage])
            print((await first.run("Remember Python", metadata={"origin": "tutorial"})).content)
        finally:
            await first_storage.close()

        restored_storage = SQLiteSessionExtension(path)
        try:
            restored = await create_agent(TurnModel(), config=config, extensions=[restored_storage])
            print((await restored.run("Continue")).content)
            rows = await restored_storage.list_raw_messages(session_id)
            assert [row.message.role.value for row in rows] == [
                "system",
                "user",
                "assistant",
                "system",
                "user",
                "assistant",
            ]
            assert rows[0].metadata == {"origin": "tutorial"}
            assert len(await restored_storage.list_raw_messages(session_id, limit=2, offset=2)) == 2
            view = await restored_storage.storage.load(session_id)
            assert view.snapshot is None and len(view.raw_tail) == 6
            await restored_storage.update_session(session_id, title="Project notes")
            renamed = await restored_storage.get_session(session_id)
            assert renamed is not None and renamed.title == "Project notes"
            assert await restored_storage.delete_session(session_id)
            assert await restored_storage.list_raw_messages(session_id) == []
            print("Raw roles: system, user, assistant, system, user, assistant")
            print("Session restored, renamed, and deleted in a temporary database")
        finally:
            await restored_storage.close()

示例还会对消息分页、重命名对话并删除它。它只删除临时数据库中的会话。在你的应用中,删除会话会移除历史记录;应把它置于明确的用户操作之后。

完整源码#

下载 sessions.py。

 1"""Restore a conversation through a new SQLite connection and a new Agent."""
 2
 3import asyncio
 4from collections.abc import AsyncIterator
 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.sqlite import SQLiteSessionExtension
11from zett_agent.messages import (
12    AssistantMessage,
13    UserMessage,
14)
15from zett_agent.model import (
16    ModelEvent,
17    ModelRequest,
18    ModelResponse,
19    RetryOptions,
20)
21
22
23class TurnModel:
24    retry = RetryOptions(max_retries=0)
25
26    async def stream(self, request: ModelRequest) -> AsyncIterator[ModelEvent]:
27        turn = sum(isinstance(message, UserMessage) for message in request.messages)
28        yield ModelEvent.completed(ModelResponse(AssistantMessage(content=f"Stored turn {turn}")))
29
30
31async def main() -> None:
32    # No demo touches the default database under the user's home directory.
33    with TemporaryDirectory(prefix="zett-docs-") as directory:
34        path = Path(directory) / "sessions.sqlite"
35        session_id = "project-notes"
36        config = AgentRunConfig(session_id=session_id)
37        first_storage = SQLiteSessionExtension(path)
38        try:
39            first = await create_agent(TurnModel(), config=config, extensions=[first_storage])
40            print((await first.run("Remember Python", metadata={"origin": "tutorial"})).content)
41        finally:
42            await first_storage.close()
43
44        restored_storage = SQLiteSessionExtension(path)
45        try:
46            restored = await create_agent(TurnModel(), config=config, extensions=[restored_storage])
47            print((await restored.run("Continue")).content)
48            rows = await restored_storage.list_raw_messages(session_id)
49            assert [row.message.role.value for row in rows] == [
50                "system",
51                "user",
52                "assistant",
53                "system",
54                "user",
55                "assistant",
56            ]
57            assert rows[0].metadata == {"origin": "tutorial"}
58            assert len(await restored_storage.list_raw_messages(session_id, limit=2, offset=2)) == 2
59            view = await restored_storage.storage.load(session_id)
60            assert view.snapshot is None and len(view.raw_tail) == 6
61            await restored_storage.update_session(session_id, title="Project notes")
62            renamed = await restored_storage.get_session(session_id)
63            assert renamed is not None and renamed.title == "Project notes"
64            assert await restored_storage.delete_session(session_id)
65            assert await restored_storage.list_raw_messages(session_id) == []
66            print("Raw roles: system, user, assistant, system, user, assistant")
67            print("Session restored, renamed, and deleted in a temporary database")
68        finally:
69            await restored_storage.close()
70
71
72if __name__ == "__main__":
73    asyncio.run(main())

下一步:阅读保存并恢复对话了解存储选择和资源归属,或阅读压缩而不丢失历史记录了解长对话。