feat: consult embeddings UI, vector search FLOAT32 fix, and base64 compartment decode
- Add Consult Embeddings sub-menu with chat-like Q&A interface - Fix VECTOR_DISTANCE FLOAT32/FLOAT64 mismatch (array 'd' → 'f') - Decode base64 compartment_id in _resolve_embed_config - Sidebar sub-item navigation for Embeddings hierarchy - Fallback to raw document display when no GenAI config available
This commit is contained in:
@@ -2419,7 +2419,7 @@ def _resolve_embed_config(oci_config_id: str = None, genai_cfg: dict = None) ->
|
||||
"oci_config_id": oc["id"],
|
||||
"genai_region": oc["region"],
|
||||
"endpoint": f"https://inference.generativeai.{oc['region']}.oci.oraclecloud.com",
|
||||
"compartment_id": (dict(oc).get("compartment_id") or oc["tenancy_ocid"]),
|
||||
"compartment_id": _safe_dec(dict(oc).get("compartment_id") or oc["tenancy_ocid"]),
|
||||
}
|
||||
# Last resort: any genai config
|
||||
with db() as c:
|
||||
@@ -2432,7 +2432,7 @@ def _resolve_embed_config(oci_config_id: str = None, genai_cfg: dict = None) ->
|
||||
"oci_config_id": oc["id"],
|
||||
"genai_region": oc["region"],
|
||||
"endpoint": f"https://inference.generativeai.{oc['region']}.oci.oraclecloud.com",
|
||||
"compartment_id": (dict(oc).get("compartment_id") or oc["tenancy_ocid"]),
|
||||
"compartment_id": _safe_dec(dict(oc).get("compartment_id") or oc["tenancy_ocid"]),
|
||||
}
|
||||
raise HTTPException(400, "Nenhuma credencial OCI configurada para gerar embeddings.")
|
||||
|
||||
@@ -2466,7 +2466,7 @@ def _vector_search(cfg: dict, query_embedding: list, top_k: int = 5, table_name:
|
||||
conn = _get_adb_connection(cfg)
|
||||
try:
|
||||
cur = conn.cursor()
|
||||
vec = array.array('d', query_embedding)
|
||||
vec = array.array('f', query_embedding)
|
||||
if tenancy:
|
||||
# Filter by tenancy in METADATA JSON field using LIKE for broad compatibility
|
||||
# Matches both structured JSON {"tenancy":"X",...} and legacy "tenancy: X, ..."
|
||||
@@ -3319,6 +3319,83 @@ async def embed_upload_url(
|
||||
_audit(u["id"], u["username"], "embed_url", url, f"{len(documents)} chunks")
|
||||
return {"ok": True, "message": f"Embedding de {len(documents)} chunks iniciado", "chunks": len(documents), "url": url}
|
||||
|
||||
class ConsultQuery(BaseModel):
|
||||
query: str
|
||||
table_name: str = ""
|
||||
top_k: int = 10
|
||||
|
||||
@app.post("/api/embeddings/consult")
|
||||
async def consult_embeddings(req: ConsultQuery, u=Depends(current_user)):
|
||||
"""Query embeddings via vector search + GenAI to get a formatted answer."""
|
||||
if not req.query.strip():
|
||||
raise HTTPException(400, "Query não pode ser vazia")
|
||||
adb_configs = _get_active_adb_configs(u["id"])
|
||||
if not adb_configs:
|
||||
raise HTTPException(400, "Nenhuma conexão ADB ativa configurada")
|
||||
# Collect results from all active ADB configs + tables
|
||||
all_docs = []
|
||||
for adb_cfg in adb_configs:
|
||||
log.info(f"Consult: processing ADB {adb_cfg['id']} ({adb_cfg.get('config_name','')})")
|
||||
try:
|
||||
emb_genai = _resolve_embed_config(oci_config_id=adb_cfg.get("oci_config_id"))
|
||||
log.info(f"Consult: resolved embed config, compartment={emb_genai.get('compartment_id','?')[:30]}, region={emb_genai.get('genai_region','?')}")
|
||||
except Exception as e:
|
||||
log.warning(f"Consult: resolve config failed for {adb_cfg['id']}: {e}")
|
||||
continue
|
||||
emb_model = adb_cfg.get("embedding_model_id", "")
|
||||
if not emb_model:
|
||||
log.warning(f"Consult: no embedding_model_id for {adb_cfg['id']}")
|
||||
continue
|
||||
try:
|
||||
query_embedding = _embed_text(req.query, emb_genai, emb_model)
|
||||
log.info(f"Consult: embedded query, dims={len(query_embedding)}")
|
||||
except Exception as e:
|
||||
log.warning(f"Consult: embed failed for {adb_cfg['id']}: {e}")
|
||||
continue
|
||||
tables = _get_tables_for_config(adb_cfg["id"], active_only=True)
|
||||
log.info(f"Consult: {len(tables)} active tables, filter='{req.table_name}'")
|
||||
if req.table_name:
|
||||
tables = [t for t in tables if t["table_name"] == req.table_name]
|
||||
for tbl in tables:
|
||||
try:
|
||||
docs = _vector_search(adb_cfg, query_embedding, top_k=req.top_k, table_name=tbl["table_name"])
|
||||
for d in docs:
|
||||
d["source"] = f"{tbl['table_name']}"
|
||||
all_docs.extend(docs)
|
||||
except Exception as e:
|
||||
log.warning(f"Consult: search failed on {tbl['table_name']}: {type(e).__name__}: {e}")
|
||||
if not all_docs:
|
||||
return {"answer": "Nenhum resultado encontrado nas bases vetoriais.", "documents": [], "total": 0}
|
||||
# Sort by distance and take top results
|
||||
all_docs.sort(key=lambda d: d.get("distance", 999))
|
||||
top_docs = all_docs[:req.top_k]
|
||||
# Build context and call GenAI
|
||||
rag_context = _build_rag_context(top_docs)
|
||||
augmented = RAG_CONTEXT_TEMPLATE.format(context=rag_context, question=req.query)
|
||||
# Get GenAI config for answering
|
||||
with db() as c:
|
||||
gc_row = c.execute("SELECT * FROM genai_configs WHERE is_default=1 ORDER BY created_at DESC").fetchone()
|
||||
if not gc_row:
|
||||
gc_row = c.execute("SELECT * FROM genai_configs ORDER BY created_at DESC").fetchone()
|
||||
if not gc_row:
|
||||
# No GenAI config — return raw documents formatted as answer
|
||||
parts = []
|
||||
for i, d in enumerate(top_docs, 1):
|
||||
content = d.get("content", "")
|
||||
if len(content) > 800: content = content[:800] + "..."
|
||||
parts.append(f"**Documento {i}** — `{d.get('source', '?')}` (distância: {d.get('distance', 0):.4f})\n\n{content}")
|
||||
raw_answer = "**Resultados da busca vetorial** (sem GenAI configurado para sumarizar):\n\n---\n\n" + "\n\n---\n\n".join(parts)
|
||||
doc_list = [{"content": d.get("content", "")[:500], "source": d.get("source", ""), "distance": round(d.get("distance", 0), 4), "metadata": d.get("metadata", "")} for d in top_docs]
|
||||
return {"answer": raw_answer, "documents": doc_list, "total": len(all_docs)}
|
||||
gc = dict(gc_row)
|
||||
try:
|
||||
answer, _, _ = _call_genai(gc, [{"role": "user", "content": augmented}], system_prompt=RAG_DEFAULT_SYSTEM_PROMPT)
|
||||
except Exception as e:
|
||||
log.error(f"Consult GenAI error: {e}")
|
||||
answer = f"Erro ao consultar GenAI: {str(e)[:300]}"
|
||||
doc_list = [{"content": d.get("content", "")[:500], "source": d.get("source", ""), "distance": round(d.get("distance", 0), 4), "metadata": d.get("metadata", "")} for d in top_docs]
|
||||
return {"answer": answer, "documents": doc_list, "total": len(all_docs)}
|
||||
|
||||
@app.get("/api/embeddings/{vid}/list")
|
||||
async def list_embeddings(vid: str, table_name: str = Query(""), limit: int = Query(50), offset: int = Query(0), u=Depends(current_user)):
|
||||
with db() as c:
|
||||
|
||||
Reference in New Issue
Block a user