feat(cli): tht memory solved-index / solved-search (question->SQL exemplars)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -441,3 +441,109 @@ def search_cmd(
|
||||
table.add_row(r["id"], r["type"], r["subject"],
|
||||
r["question_context"][:60], f"{r['score']:.3f}")
|
||||
Console().print(table)
|
||||
|
||||
|
||||
def index_solved_session(cfg, session_id: str) -> int:
|
||||
"""Indicizza la coppia domanda->SQL della sessione (kind solved_question).
|
||||
|
||||
Solleva RuntimeError se manca la writer key e SolvedIndexError se mancano gli
|
||||
artefatti: il finalize li degrada a warning, il comando CLI li converte in
|
||||
errori espliciti."""
|
||||
from tht.cli.sql_cmd import promoted_tables_for
|
||||
from tht.cli.vector_cmd import make_embedder
|
||||
from tht.solved import build_solved_record, save_solved_question
|
||||
from tht.vectorstore.rest_client import VectorRestClient
|
||||
|
||||
if not has_vector_write_rest(cfg):
|
||||
raise RuntimeError(
|
||||
"vector_write_rest assente: la coppia domanda->SQL si indicizza con la "
|
||||
"writer key (workstation) o dal server"
|
||||
)
|
||||
manifest = load_session_or_exit(cfg, session_id)
|
||||
record = build_solved_record(
|
||||
session_dir(cfg, session_id), manifest, promoted_tables_for(cfg, session_id)
|
||||
)
|
||||
return save_solved_question(
|
||||
record,
|
||||
writer=VectorRestClient(cfg.vector_write_rest),
|
||||
embedder=make_embedder(cfg.embeddings),
|
||||
)
|
||||
|
||||
|
||||
@memory_app.command("solved-index")
|
||||
def solved_index_cmd(
|
||||
session_id: str = typer.Argument(..., help="Id sessione con sql_final.sql approvato."),
|
||||
json_out: bool = typer.Option(False, "--json", help="Output JSON (per Pi)."),
|
||||
config: Path = CONFIG_OPT,
|
||||
) -> None:
|
||||
"""Indicizza la coppia domanda->SQL nel vectordb (backfill; il finalize lo fa da solo)."""
|
||||
import json as _json
|
||||
|
||||
from tht.solved import SolvedIndexError
|
||||
|
||||
cfg = _load_config_or_exit(config)
|
||||
require_vector_write_allowed(cfg, "memory solved-index")
|
||||
try:
|
||||
count = index_solved_session(cfg, session_id)
|
||||
except RuntimeError as e:
|
||||
typer.secho(f"ERRORE: {e}", fg=typer.colors.RED, err=True)
|
||||
raise typer.Exit(code=4)
|
||||
except SolvedIndexError as e:
|
||||
typer.secho(f"ERRORE: sessione {session_id} non indicizzabile: {e}",
|
||||
fg=typer.colors.RED, err=True)
|
||||
raise typer.Exit(code=3)
|
||||
msg = (
|
||||
f"1 coppia domanda->SQL indicizzata (solved:{session_id})."
|
||||
if count else "Nessun upsert: coppia gia' aggiornata."
|
||||
)
|
||||
if json_out:
|
||||
typer.echo(_json.dumps({"upserted": count, "id": f"solved:{session_id}"},
|
||||
ensure_ascii=False))
|
||||
return
|
||||
typer.secho(f"OK: {msg}", fg=typer.colors.GREEN)
|
||||
|
||||
|
||||
@memory_app.command("solved-search")
|
||||
def solved_search_cmd(
|
||||
question: str = typer.Argument(..., help="Domanda da confrontare con quelle risolte."),
|
||||
top: int = typer.Option(3, "--top"),
|
||||
json_out: bool = typer.Option(False, "--json", help="Output JSON (per Pi)."),
|
||||
config: Path = CONFIG_OPT,
|
||||
) -> None:
|
||||
"""Domande gia' risolte simili (kind solved_question): domanda, SQL e tabelle."""
|
||||
from rich.console import Console
|
||||
from rich.table import Table
|
||||
|
||||
from tht.cli.vector_cmd import make_embedder, open_searcher
|
||||
from tht.solved import SOLVED_KIND
|
||||
|
||||
cfg = _load_config_or_exit(config)
|
||||
require_vector_cfg(cfg)
|
||||
searcher = open_searcher(cfg)
|
||||
embedder = make_embedder(cfg.embeddings)
|
||||
hits = searcher.search(embedder.embed_query(question), top_n=top, kinds=[SOLVED_KIND])
|
||||
results = [
|
||||
{
|
||||
"session_id": h.metadata.get("session_id", h.ref),
|
||||
"question": h.metadata.get("question", h.content),
|
||||
"sql": h.metadata.get("sql", ""),
|
||||
"tables": h.metadata.get("tables", []),
|
||||
"score": round(h.similarity, 4),
|
||||
}
|
||||
for h in hits
|
||||
]
|
||||
if json_out:
|
||||
typer.echo(json.dumps(results, ensure_ascii=False, indent=2))
|
||||
return
|
||||
if not results:
|
||||
typer.secho("Nessuna domanda risolta simile.", fg=typer.colors.YELLOW)
|
||||
return
|
||||
table = Table(title=f"Domande risolte simili a: {question}")
|
||||
table.add_column("Sessione")
|
||||
table.add_column("Domanda")
|
||||
table.add_column("Tabelle")
|
||||
table.add_column("Score", justify="right")
|
||||
for r in results:
|
||||
table.add_row(r["session_id"], r["question"][:60],
|
||||
", ".join(r["tables"]), f"{r['score']:.3f}")
|
||||
Console().print(table)
|
||||
|
||||
Reference in New Issue
Block a user