feat: add session titles and migration

This commit is contained in:
2026-07-30 19:42:32 +02:00
parent 00a5a6ce8c
commit 112ae6d1da
5 changed files with 227 additions and 25 deletions
+17 -1
View File
@@ -2,7 +2,7 @@ import tempfile
import unittest
from pathlib import Path
from javis.core.chat_service import ChatService
from javis.core.chat_service import ChatService, derive_session_title
from javis.memory.sqlite_store import SQLiteSessionStore
from javis.providers.base import ChatMessage, LocalModelProvider, ProviderUnavailableError
@@ -64,6 +64,22 @@ class ChatServiceTests(unittest.TestCase):
"Antwort 2",
],
)
self.assertEqual(loaded.session.title, "Hallo")
def test_title_is_local_short_and_redacts_obvious_sensitive_content(self) -> None:
self.assertEqual(
derive_session_title("Erkläre SQLite-Transaktionen für Anfänger"),
"Erkläre SQLite-Transaktionen für Anfänger",
)
self.assertLessEqual(derive_session_title("Wort " * 30).__len__(), 60)
self.assertEqual(
derive_session_title("Mein API-Key ist ABC123 und funktioniert nicht"),
"Sensible Anfrage",
)
self.assertEqual(
derive_session_title("Wenn ich blute, sollte ich zum Arzt?"),
"Gesundheitsfrage",
)
def test_streaming_response_is_persisted_exactly_once_after_completion(self) -> None:
provider = StreamingProvider()
+66
View File
@@ -1,3 +1,4 @@
import sqlite3
import tempfile
import unittest
from pathlib import Path
@@ -21,6 +22,8 @@ class SQLiteSessionStoreTests(unittest.TestCase):
self.assertEqual(loaded.id, created.id)
self.assertEqual(loaded.provider, "ollama")
self.assertEqual(loaded.model, "test-model")
self.assertEqual(loaded.title, "Neue Sitzung")
self.assertEqual(loaded.last_provider, "ollama")
self.assertEqual(self.store.list_sessions()[0].id, created.id)
def test_messages_keep_exchange_order(self) -> None:
@@ -53,6 +56,69 @@ class SQLiteSessionStoreTests(unittest.TestCase):
self.assertEqual(self.store.get_messages(session.id), [])
self.assertEqual(self.store.get_session(session.id).message_count, 0)
def test_first_exchange_sets_title_and_last_provider_atomically(self) -> None:
session = self.store.create_session("hybrid", "auto")
self.store.append_exchange(
session.id,
"Frage",
"Antwort",
title_if_first="Kurzer Titel",
last_provider="gemini",
)
self.store.append_exchange(
session.id,
"Noch eine Frage",
"Noch eine Antwort",
title_if_first="Darf Titel nicht ersetzen",
last_provider="ollama",
)
loaded = self.store.get_session(session.id)
self.assertEqual(loaded.title, "Kurzer Titel")
self.assertEqual(loaded.last_provider, "ollama")
def test_old_database_is_migrated_idempotently_without_data_loss(self) -> None:
database = Path(self.temporary_directory.name) / "legacy.sqlite3"
with sqlite3.connect(database) as connection:
connection.executescript(
"""
CREATE TABLE sessions (
id TEXT PRIMARY KEY,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
provider TEXT NOT NULL,
model TEXT NOT NULL
);
CREATE TABLE messages (
id INTEGER PRIMARY KEY AUTOINCREMENT,
session_id TEXT NOT NULL,
position INTEGER NOT NULL,
role TEXT NOT NULL,
content TEXT NOT NULL,
created_at TEXT NOT NULL,
UNIQUE (session_id, position)
);
INSERT INTO sessions VALUES
('legacy', '2026-01-01', '2026-01-02', 'ollama', 'old-model');
INSERT INTO messages
(session_id, position, role, content, created_at)
VALUES ('legacy', 0, 'user', 'Alte Nachricht', '2026-01-02');
"""
)
connection.close()
migrated = SQLiteSessionStore(database)
migrated_again = SQLiteSessionStore(database)
session = migrated_again.get_session("legacy")
self.assertEqual(session.title, "Neue Sitzung")
self.assertEqual(session.last_provider, "ollama")
self.assertEqual(
[message.content for message in migrated.get_messages("legacy")],
["Alte Nachricht"],
)
if __name__ == "__main__":
unittest.main()