Nottybro commited on
Commit
7c073ac
·
verified ·
1 Parent(s): 90f2a5f

deploy: acra.py

Browse files
Files changed (1) hide show
  1. acra.py +124 -0
acra.py ADDED
@@ -0,0 +1,124 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import google.generativeai as genai
3
+ from db import supabase
4
+ from classifier_inference import classify_query
5
+ from typing import List
6
+
7
+ genai.configure(api_key=os.environ["GEMINI_API_KEY"])
8
+ EMBED_MODEL = "models/text-embedding-004"
9
+ GEN_MODEL = "gemma-3-27b-it"
10
+ DEPTH = {0: 0, 1: 3, 2: 6, 3: 10}
11
+
12
+ def embed_texts(texts):
13
+ return [genai.embed_content(model=EMBED_MODEL, content=t,
14
+ task_type="retrieval_document")["embedding"] for t in texts]
15
+
16
+ def embed_query(q):
17
+ return genai.embed_content(model=EMBED_MODEL, content=q,
18
+ task_type="retrieval_query")["embedding"]
19
+
20
+ def adaptive_chunk(text, max_tok=512):
21
+ paras = [p.strip() for p in text.split("\n\n") if p.strip()]
22
+ chunks, cur = [], ""
23
+ for p in paras:
24
+ if (len(cur.split()) + len(p.split())) / 0.75 < max_tok:
25
+ cur = (cur + "\n\n" + p).strip()
26
+ else:
27
+ if cur: chunks.append(cur)
28
+ cur = p
29
+ if cur: chunks.append(cur)
30
+ return chunks or [text]
31
+
32
+ def decompose(query):
33
+ m = genai.GenerativeModel(GEN_MODEL)
34
+ r = m.generate_content(
35
+ f"Decompose into 2-4 simpler sub-queries. "
36
+ f"Return numbered list only.\n\nQuery: {query}")
37
+ lines = [l.strip().lstrip("1234567890.). ")
38
+ for l in r.text.strip().split("\n") if l.strip()]
39
+ return lines[:4] or [query]
40
+
41
+ def compress(query, chunks):
42
+ m = genai.GenerativeModel(GEN_MODEL)
43
+ out = []
44
+ for c in chunks:
45
+ r = m.generate_content(
46
+ f"Extract only sentences relevant to the query. "
47
+ f"Return empty if none.\n\nQuery: {query}\nChunk: {c}")
48
+ if r.text.strip(): out.append(r.text.strip())
49
+ return out
50
+
51
+ def vsearch(query, namespace, user_id, k):
52
+ return (supabase.rpc("match_documents", {
53
+ "query_embedding": embed_query(query),
54
+ "match_count": k,
55
+ "filter_namespace": namespace,
56
+ "filter_user_id": user_id
57
+ }).execute().data or [])
58
+
59
+ PROMPTS = {
60
+ 1: "Answer using ONLY the context below. Be concise.\n\nContext:\n{ctx}\n\nQuestion: {q}\nAnswer:",
61
+ 2: "Synthesize the context to answer. Think step by step.\n\nContext:\n{ctx}\n\nQuestion: {q}\nAnswer:",
62
+ 3: "Use chain-of-thought to answer this complex question.\nAddress each aspect. Note any gaps.\n\nContext:\n{ctx}\n\nQuestion: {q}\nReasoning and answer:",
63
+ }
64
+
65
+ async def ingest_pipeline(texts, metadata, namespace, user_id):
66
+ chunks, meta = [], []
67
+ for i, t in enumerate(texts):
68
+ for j, c in enumerate(adaptive_chunk(t)):
69
+ chunks.append(c)
70
+ meta.append({**metadata[i], "source_index": i, "chunk_index": j})
71
+ rows = [{"content": c, "embedding": e, "metadata": m,
72
+ "namespace": namespace, "user_id": user_id}
73
+ for c, e, m in zip(chunks, embed_texts(chunks), meta)]
74
+ for i in range(0, len(rows), 50):
75
+ supabase.table("documents").insert(rows[i:i+50]).execute()
76
+ return len(chunks)
77
+
78
+ async def query_pipeline(query, namespace, top_k, rerank, user_id):
79
+ cls = classify_query(query)
80
+ level = cls["level"]
81
+ k = DEPTH[level]
82
+ model = genai.GenerativeModel(GEN_MODEL)
83
+
84
+ if level == 0:
85
+ r = model.generate_content(
86
+ f"Answer concisely from your knowledge:\n\n{query}")
87
+ return {"answer": r.text.strip(), "sources": [], "complexity": cls}
88
+
89
+ hits = []
90
+ if level == 3:
91
+ seen = set()
92
+ for sq in decompose(query):
93
+ for h in vsearch(sq, namespace, user_id, 4):
94
+ if h["id"] not in seen:
95
+ seen.add(h["id"]); hits.append(h)
96
+ else:
97
+ hits = vsearch(query, namespace, user_id, k)
98
+
99
+ if not hits:
100
+ return {"answer": "No relevant documents found. Ingest some first.",
101
+ "sources": [], "complexity": cls}
102
+
103
+ chunks = [h["content"] for h in hits]
104
+ if rerank and level >= 2:
105
+ chunks = [c for c in compress(query, chunks) if c.strip()]
106
+
107
+ ctx = "\n\n---\n\n".join(chunks[:k])
108
+ r = model.generate_content(PROMPTS[level].format(ctx=ctx, q=query))
109
+
110
+ return {
111
+ "answer": r.text.strip(),
112
+ "sources": [{"content": h["content"][:200],
113
+ "metadata": h.get("metadata", {}),
114
+ "score": h.get("similarity", 0)}
115
+ for h in hits[:len(chunks)]],
116
+ "complexity": cls
117
+ }
118
+
119
+ async def run_acra_pipeline(mode, **kw):
120
+ if mode == "ingest":
121
+ return await ingest_pipeline(kw["texts"], kw["metadata"],
122
+ kw["namespace"], kw["user_id"])
123
+ return await query_pipeline(kw["query"], kw["namespace"],
124
+ kw["top_k"], kw["rerank"], kw["user_id"])