axio-context-sqlite 0.6.2__tar.gz → 0.8.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {axio_context_sqlite-0.6.2 → axio_context_sqlite-0.8.0}/PKG-INFO +4 -4
- {axio_context_sqlite-0.6.2 → axio_context_sqlite-0.8.0}/README.md +1 -1
- {axio_context_sqlite-0.6.2 → axio_context_sqlite-0.8.0}/pyproject.toml +3 -3
- axio_context_sqlite-0.8.0/tests/test_autocompact_sqlite.py +158 -0
- {axio_context_sqlite-0.6.2 → axio_context_sqlite-0.8.0}/.gitignore +0 -0
- {axio_context_sqlite-0.6.2 → axio_context_sqlite-0.8.0}/src/axio_context_sqlite/__init__.py +0 -0
- {axio_context_sqlite-0.6.2 → axio_context_sqlite-0.8.0}/src/axio_context_sqlite/py.typed +0 -0
- {axio_context_sqlite-0.6.2 → axio_context_sqlite-0.8.0}/src/axio_context_sqlite/store.py +0 -0
- {axio_context_sqlite-0.6.2 → axio_context_sqlite-0.8.0}/tests/test_store.py +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: axio-context-sqlite
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.0
|
|
4
4
|
Summary: SQLite-backed context store for Axio
|
|
5
|
-
Project-URL: Homepage, https://github.com/axio-agent
|
|
6
|
-
Project-URL: Repository, https://github.com/axio-agent
|
|
5
|
+
Project-URL: Homepage, https://github.com/mosquito/axio-agent
|
|
6
|
+
Project-URL: Repository, https://github.com/mosquito/axio-agent
|
|
7
7
|
License: MIT
|
|
8
8
|
Keywords: agent,ai,context,llm,sqlite,storage
|
|
9
9
|
Requires-Python: >=3.12
|
|
@@ -17,7 +17,7 @@ Description-Content-Type: text/markdown
|
|
|
17
17
|
[](https://pypi.org/project/axio-context-sqlite/)
|
|
18
18
|
[](LICENSE)
|
|
19
19
|
|
|
20
|
-
SQLite-backed persistent context store for [axio](https://github.com/axio-agent
|
|
20
|
+
SQLite-backed persistent context store for [axio](https://github.com/mosquito/axio-agent).
|
|
21
21
|
|
|
22
22
|
## Installation
|
|
23
23
|
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
[](https://pypi.org/project/axio-context-sqlite/)
|
|
5
5
|
[](LICENSE)
|
|
6
6
|
|
|
7
|
-
SQLite-backed persistent context store for [axio](https://github.com/axio-agent
|
|
7
|
+
SQLite-backed persistent context store for [axio](https://github.com/mosquito/axio-agent).
|
|
8
8
|
|
|
9
9
|
## Installation
|
|
10
10
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "axio-context-sqlite"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.8.0"
|
|
4
4
|
description = "SQLite-backed context store for Axio"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.12"
|
|
@@ -9,8 +9,8 @@ keywords = ["llm", "agent", "ai", "sqlite", "context", "storage"]
|
|
|
9
9
|
dependencies = ["axio", "aiosqlite>=0.20"]
|
|
10
10
|
|
|
11
11
|
[project.urls]
|
|
12
|
-
Homepage = "https://github.com/axio-agent
|
|
13
|
-
Repository = "https://github.com/axio-agent
|
|
12
|
+
Homepage = "https://github.com/mosquito/axio-agent"
|
|
13
|
+
Repository = "https://github.com/mosquito/axio-agent"
|
|
14
14
|
|
|
15
15
|
[build-system]
|
|
16
16
|
requires = ["hatchling"]
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"""Tests for AutoCompactStore wrapping SQLiteContextStore."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import AsyncGenerator, AsyncIterator
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any
|
|
8
|
+
from uuid import uuid4
|
|
9
|
+
|
|
10
|
+
import aiosqlite
|
|
11
|
+
import pytest
|
|
12
|
+
from axio.blocks import TextBlock
|
|
13
|
+
from axio.compaction import AutoCompactStore
|
|
14
|
+
from axio.events import StreamEvent
|
|
15
|
+
from axio.messages import Message
|
|
16
|
+
from axio.testing import StubTransport, make_text_response
|
|
17
|
+
from axio.tool import Tool
|
|
18
|
+
|
|
19
|
+
from axio_context_sqlite import SQLiteContextStore, connect
|
|
20
|
+
|
|
21
|
+
# ---------------------------------------------------------------------------
|
|
22
|
+
# Helpers
|
|
23
|
+
# ---------------------------------------------------------------------------
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _msg(role: str = "user", text: str = "x") -> Message:
|
|
27
|
+
return Message(role=role, content=[TextBlock(text=text)]) # type: ignore[arg-type]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class FailTransport:
|
|
31
|
+
def stream(self, messages: list[Message], tools: list[Tool[Any]], system: str) -> AsyncIterator[StreamEvent]:
|
|
32
|
+
raise RuntimeError("boom")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
# ---------------------------------------------------------------------------
|
|
36
|
+
# Fixtures
|
|
37
|
+
# ---------------------------------------------------------------------------
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@pytest.fixture
|
|
41
|
+
def db_path(tmp_path: Path) -> Path:
|
|
42
|
+
return tmp_path / "autocompact_test.db"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@pytest.fixture
|
|
46
|
+
async def conn(db_path: Path) -> AsyncGenerator[aiosqlite.Connection, None]:
|
|
47
|
+
c = await connect(db_path)
|
|
48
|
+
yield c
|
|
49
|
+
await c.close()
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@pytest.fixture
|
|
53
|
+
async def sqlite_store(conn: aiosqlite.Connection) -> SQLiteContextStore:
|
|
54
|
+
return SQLiteContextStore(conn, uuid4().hex, "test-project")
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
# ---------------------------------------------------------------------------
|
|
58
|
+
# Tests
|
|
59
|
+
# ---------------------------------------------------------------------------
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class TestAutoCompactWithSQLite:
|
|
63
|
+
async def test_history_written_back_to_sqlite(self, sqlite_store: SQLiteContextStore) -> None:
|
|
64
|
+
"""After compaction, messages in the DB are the compacted ones."""
|
|
65
|
+
for i in range(14):
|
|
66
|
+
role = "user" if i % 2 == 0 else "assistant"
|
|
67
|
+
await sqlite_store.append(_msg(role, f"msg-{i}"))
|
|
68
|
+
|
|
69
|
+
transport = StubTransport([make_text_response("Summary of earlier work")])
|
|
70
|
+
store = AutoCompactStore(sqlite_store, transport, max_tokens=1, keep_recent=4)
|
|
71
|
+
|
|
72
|
+
await store.add_context_tokens(2, 0)
|
|
73
|
+
|
|
74
|
+
history = await sqlite_store.get_history()
|
|
75
|
+
# 2 (summary pair) + 4 recent
|
|
76
|
+
assert len(history) == 6
|
|
77
|
+
assert history[0].role == "user"
|
|
78
|
+
block0 = history[0].content[0]
|
|
79
|
+
assert isinstance(block0, TextBlock)
|
|
80
|
+
assert "Summary of earlier work" in block0.text
|
|
81
|
+
assert history[1].role == "assistant"
|
|
82
|
+
|
|
83
|
+
async def test_cumulative_tokens_persisted_in_sqlite(self, sqlite_store: SQLiteContextStore) -> None:
|
|
84
|
+
"""Cumulative token counts survive compaction in the SQLite table."""
|
|
85
|
+
for i in range(14):
|
|
86
|
+
role = "user" if i % 2 == 0 else "assistant"
|
|
87
|
+
await sqlite_store.append(_msg(role, f"msg-{i}"))
|
|
88
|
+
await sqlite_store.set_context_tokens(400_000, 15_000)
|
|
89
|
+
|
|
90
|
+
transport = StubTransport([make_text_response("summary")])
|
|
91
|
+
store = AutoCompactStore(sqlite_store, transport, max_tokens=1, keep_recent=4)
|
|
92
|
+
|
|
93
|
+
await store.add_context_tokens(2, 0)
|
|
94
|
+
|
|
95
|
+
in_tok, out_tok = await sqlite_store.get_context_tokens()
|
|
96
|
+
assert in_tok == 400_002
|
|
97
|
+
assert out_tok == 15_000
|
|
98
|
+
|
|
99
|
+
async def test_sqlite_session_id_preserved(self, sqlite_store: SQLiteContextStore) -> None:
|
|
100
|
+
"""session_id of wrapper matches the inner SQLiteContextStore."""
|
|
101
|
+
store = AutoCompactStore(sqlite_store, StubTransport([]), max_tokens=999_999)
|
|
102
|
+
assert store.session_id == sqlite_store.session_id
|
|
103
|
+
|
|
104
|
+
async def test_compact_does_not_corrupt_on_failure(self, sqlite_store: SQLiteContextStore) -> None:
|
|
105
|
+
"""If compaction fails, the original history in the DB is untouched."""
|
|
106
|
+
msgs = [_msg("user" if i % 2 == 0 else "assistant", f"msg-{i}") for i in range(14)]
|
|
107
|
+
for m in msgs:
|
|
108
|
+
await sqlite_store.append(m)
|
|
109
|
+
|
|
110
|
+
store = AutoCompactStore(sqlite_store, FailTransport(), max_tokens=1, keep_recent=4)
|
|
111
|
+
await store.add_context_tokens(2, 0)
|
|
112
|
+
|
|
113
|
+
history = await sqlite_store.get_history()
|
|
114
|
+
assert len(history) == 14
|
|
115
|
+
|
|
116
|
+
async def test_fork_creates_independent_sqlite_session(self, sqlite_store: SQLiteContextStore) -> None:
|
|
117
|
+
"""fork() returns an AutoCompactStore with a new session_id; mutations are independent."""
|
|
118
|
+
for i in range(6):
|
|
119
|
+
role = "user" if i % 2 == 0 else "assistant"
|
|
120
|
+
await sqlite_store.append(_msg(role, f"msg-{i}"))
|
|
121
|
+
|
|
122
|
+
store = AutoCompactStore(sqlite_store, StubTransport([]), max_tokens=999_999)
|
|
123
|
+
forked = await store.fork()
|
|
124
|
+
|
|
125
|
+
assert forked.session_id != store.session_id
|
|
126
|
+
|
|
127
|
+
# append to fork — parent unaffected
|
|
128
|
+
await forked.append(_msg(text="extra"))
|
|
129
|
+
assert len(await store.get_history()) == 6
|
|
130
|
+
assert len(await forked.get_history()) == 7
|
|
131
|
+
|
|
132
|
+
async def test_compact_uses_fork_as_snapshot(self, sqlite_store: SQLiteContextStore) -> None:
|
|
133
|
+
"""compact_context receives a fork of the store, not the live store itself."""
|
|
134
|
+
for i in range(14):
|
|
135
|
+
role = "user" if i % 2 == 0 else "assistant"
|
|
136
|
+
await sqlite_store.append(_msg(role, f"msg-{i}"))
|
|
137
|
+
|
|
138
|
+
seen_session_ids: list[str] = []
|
|
139
|
+
|
|
140
|
+
import axio.compaction as _mod
|
|
141
|
+
|
|
142
|
+
original = _mod.compact_context
|
|
143
|
+
|
|
144
|
+
async def _capturing(ctx, transport, **kw): # type: ignore[no-untyped-def]
|
|
145
|
+
seen_session_ids.append(ctx.session_id)
|
|
146
|
+
return await original(ctx, StubTransport([make_text_response("summary")]), **kw)
|
|
147
|
+
|
|
148
|
+
_mod.compact_context = _capturing # type: ignore[assignment]
|
|
149
|
+
try:
|
|
150
|
+
store = AutoCompactStore(
|
|
151
|
+
sqlite_store, StubTransport([make_text_response("summary")]), max_tokens=1, keep_recent=4
|
|
152
|
+
)
|
|
153
|
+
await store.add_context_tokens(2, 0)
|
|
154
|
+
finally:
|
|
155
|
+
_mod.compact_context = original
|
|
156
|
+
|
|
157
|
+
assert len(seen_session_ids) == 1
|
|
158
|
+
assert seen_session_ids[0] != sqlite_store.session_id
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|