feat(storage): add app settings and model-scoped collections
Why: - Different Embedding Models must not share Qdrant collections; Admin needs persisted role defaults and Experiment provenance. Changes: - Scope collection names by model id; add app_settings, embedding provenance columns, and queries.expansion_tree. Impact: - Process/query write only Model Corpus collections; legacy unscoped names are read-only for Admin labeling. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -62,6 +62,7 @@ CREATE TABLE IF NOT EXISTS queries (
|
||||
question TEXT NOT NULL,
|
||||
answer TEXT NOT NULL,
|
||||
retrieved_chunks TEXT NOT NULL DEFAULT '[]',
|
||||
expansion_tree TEXT NOT NULL DEFAULT '[]',
|
||||
latency_breakdown TEXT NOT NULL DEFAULT '{}',
|
||||
token_usage TEXT NOT NULL DEFAULT '{}',
|
||||
created_at TEXT NOT NULL,
|
||||
@@ -76,17 +77,100 @@ CREATE TABLE IF NOT EXISTS experiments (
|
||||
per_question TEXT NOT NULL DEFAULT '[]',
|
||||
aggregate_metrics TEXT NOT NULL DEFAULT '{}',
|
||||
strategies_used TEXT NOT NULL DEFAULT '[]',
|
||||
embedding_model_id TEXT,
|
||||
embedding_provider TEXT,
|
||||
boundary_embedding_model_id TEXT,
|
||||
created_at TEXT NOT NULL,
|
||||
FOREIGN KEY (document_id) REFERENCES documents(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS app_settings (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT NOT NULL
|
||||
);
|
||||
"""
|
||||
|
||||
|
||||
def init_db() -> None:
|
||||
"""Create tables if they don't exist."""
|
||||
"""Create tables if they don't exist and apply light migrations."""
|
||||
conn = _connect()
|
||||
try:
|
||||
conn.executescript(_SCHEMA)
|
||||
_migrate_schema(conn)
|
||||
conn.commit()
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
def _migrate_schema(conn: sqlite3.Connection) -> None:
|
||||
"""Add Embedding Model provenance columns and backfill legacy experiments."""
|
||||
cols = {
|
||||
row[1]
|
||||
for row in conn.execute("PRAGMA table_info(experiments)").fetchall()
|
||||
}
|
||||
if "embedding_model_id" not in cols:
|
||||
conn.execute("ALTER TABLE experiments ADD COLUMN embedding_model_id TEXT")
|
||||
if "embedding_provider" not in cols:
|
||||
conn.execute("ALTER TABLE experiments ADD COLUMN embedding_provider TEXT")
|
||||
if "boundary_embedding_model_id" not in cols:
|
||||
conn.execute(
|
||||
"ALTER TABLE experiments ADD COLUMN boundary_embedding_model_id TEXT"
|
||||
)
|
||||
|
||||
dcols = {
|
||||
row[1]
|
||||
for row in conn.execute("PRAGMA table_info(documents)").fetchall()
|
||||
}
|
||||
if "last_corpus_embedding_model_id" not in dcols:
|
||||
conn.execute(
|
||||
"ALTER TABLE documents ADD COLUMN last_corpus_embedding_model_id TEXT"
|
||||
)
|
||||
if "last_boundary_embedding_model_id" not in dcols:
|
||||
conn.execute(
|
||||
"ALTER TABLE documents ADD COLUMN last_boundary_embedding_model_id TEXT"
|
||||
)
|
||||
|
||||
qcols = {
|
||||
row[1]
|
||||
for row in conn.execute("PRAGMA table_info(queries)").fetchall()
|
||||
}
|
||||
if "expansion_tree" not in qcols:
|
||||
conn.execute(
|
||||
"ALTER TABLE queries ADD COLUMN expansion_tree TEXT NOT NULL DEFAULT '[]'"
|
||||
)
|
||||
|
||||
# Legacy Experiments without provenance → historical OpenAI small (ADR-0019)
|
||||
from src.chunking.embedding_models import LEGACY_CLOUD_MODEL_ID, Provider
|
||||
|
||||
conn.execute(
|
||||
"""UPDATE experiments
|
||||
SET embedding_model_id = ?, embedding_provider = ?
|
||||
WHERE embedding_model_id IS NULL OR embedding_model_id = ''""",
|
||||
(LEGACY_CLOUD_MODEL_ID, Provider.CLOUD.value),
|
||||
)
|
||||
|
||||
|
||||
def get_app_setting(key: str) -> str | None:
|
||||
"""Read a persisted app setting value."""
|
||||
conn = _connect()
|
||||
try:
|
||||
row = conn.execute(
|
||||
"SELECT value FROM app_settings WHERE key = ?", (key,)
|
||||
).fetchone()
|
||||
return row["value"] if row else None
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
def set_app_setting(key: str, value: str) -> None:
|
||||
"""Upsert a persisted app setting."""
|
||||
conn = _connect()
|
||||
try:
|
||||
conn.execute(
|
||||
"""INSERT INTO app_settings (key, value) VALUES (?, ?)
|
||||
ON CONFLICT(key) DO UPDATE SET value = excluded.value""",
|
||||
(key, value),
|
||||
)
|
||||
conn.commit()
|
||||
finally:
|
||||
conn.close()
|
||||
@@ -157,6 +241,27 @@ def update_chunk_counts(doc_id: str, chunk_counts: dict[str, int]) -> None:
|
||||
conn.close()
|
||||
|
||||
|
||||
def update_process_embedding_provenance(
|
||||
doc_id: str,
|
||||
*,
|
||||
corpus_model_id: str,
|
||||
boundary_model_id: str | None = None,
|
||||
) -> None:
|
||||
"""Record Corpus/Boundary used on the last successful process run."""
|
||||
conn = _connect()
|
||||
try:
|
||||
conn.execute(
|
||||
"""UPDATE documents
|
||||
SET last_corpus_embedding_model_id = ?,
|
||||
last_boundary_embedding_model_id = ?
|
||||
WHERE id = ?""",
|
||||
(corpus_model_id, boundary_model_id, doc_id),
|
||||
)
|
||||
conn.commit()
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
def delete_document(doc_id: str) -> bool:
|
||||
"""Delete a document and its cascaded queries/experiments."""
|
||||
conn = _connect()
|
||||
@@ -178,6 +283,7 @@ def save_query(
|
||||
question: str,
|
||||
answer: str,
|
||||
retrieved_chunks: list[dict] | None = None,
|
||||
expansion_tree: list[dict] | None = None,
|
||||
latency_breakdown: dict[str, float] | None = None,
|
||||
token_usage: dict[str, int] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
@@ -188,10 +294,11 @@ def save_query(
|
||||
conn.execute(
|
||||
"""INSERT INTO queries
|
||||
(id, document_id, strategy_name, question, answer,
|
||||
retrieved_chunks, latency_breakdown, token_usage, created_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)""",
|
||||
retrieved_chunks, expansion_tree, latency_breakdown, token_usage, created_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
|
||||
(query_id, document_id, strategy_name, question, answer,
|
||||
json.dumps(retrieved_chunks or []),
|
||||
json.dumps(expansion_tree or []),
|
||||
json.dumps(latency_breakdown or {}),
|
||||
json.dumps(token_usage or {}),
|
||||
_now()),
|
||||
@@ -250,22 +357,36 @@ def save_experiment(
|
||||
per_question: list[dict] | None = None,
|
||||
aggregate_metrics: dict | None = None,
|
||||
strategies_used: list[str] | None = None,
|
||||
embedding_model_id: str | None = None,
|
||||
embedding_provider: str | None = None,
|
||||
boundary_embedding_model_id: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Insert a benchmark experiment."""
|
||||
"""Insert a benchmark experiment.
|
||||
|
||||
embedding_model_id is the Corpus Embedding Model (ADR-0024).
|
||||
"""
|
||||
from src.chunking.embedding_models import DEFAULT_CLOUD_MODEL_ID, Provider
|
||||
|
||||
experiment_id = experiment_id or _new_id()
|
||||
model_id = embedding_model_id or DEFAULT_CLOUD_MODEL_ID
|
||||
provider = embedding_provider or Provider.CLOUD.value
|
||||
conn = _connect()
|
||||
try:
|
||||
conn.execute(
|
||||
"""INSERT INTO experiments
|
||||
(id, document_id, benchmark_config, questions, per_question,
|
||||
aggregate_metrics, strategies_used, created_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)""",
|
||||
aggregate_metrics, strategies_used, embedding_model_id,
|
||||
embedding_provider, boundary_embedding_model_id, created_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
|
||||
(experiment_id, document_id,
|
||||
json.dumps(benchmark_config or {}),
|
||||
json.dumps(questions or []),
|
||||
json.dumps(per_question or []),
|
||||
json.dumps(aggregate_metrics or {}),
|
||||
json.dumps(strategies_used or []),
|
||||
model_id,
|
||||
provider,
|
||||
boundary_embedding_model_id,
|
||||
_now()),
|
||||
)
|
||||
conn.commit()
|
||||
@@ -324,7 +445,7 @@ def delete_experiment(experiment_id: str) -> bool:
|
||||
|
||||
# ── Internal helpers ───────────────────────────────────────────────
|
||||
|
||||
_JSON_FIELDS = {"chunk_counts", "retrieved_chunks", "latency_breakdown",
|
||||
_JSON_FIELDS = {"chunk_counts", "retrieved_chunks", "expansion_tree", "latency_breakdown",
|
||||
"token_usage", "document_tree", "benchmark_config",
|
||||
"questions", "per_question", "aggregate_metrics",
|
||||
"strategies_used"}
|
||||
|
||||
Reference in New Issue
Block a user