Nottybro commited on
Commit
92a11ef
Β·
verified Β·
1 Parent(s): 6e2cade

fix: full fresh acra.py with Jina web search, no duckduckgo

Browse files
Files changed (1) hide show
  1. acra.py +42 -40
acra.py CHANGED
@@ -1,9 +1,8 @@
1
- import os
2
  from google import genai
3
  from google.genai import types
4
  from db import supabase
5
  from classifier_inference import classify_query
6
- from duckduckgo_search import DDGS
7
  from typing import List
8
 
9
  client = genai.Client(api_key=os.environ["GEMINI_API_KEY"])
@@ -34,29 +33,35 @@ def adaptive_chunk(text, max_tok=512):
34
  return chunks or [text]
35
 
36
  def web_search(query: str, max_results: int = 5) -> List[dict]:
37
- """Web search via Jina AI β€” no API key, works from cloud, LLM-ready output."""
38
- import httpx
39
  try:
40
- url = f"https://s.jina.ai/?q={httpx.URL(query)}"
41
- r = httpx.get(url,
42
- headers={"Accept": "application/json",
43
- "X-Respond-With": "no-content"},
44
- timeout=15.0)
 
 
 
45
  if r.status_code != 200:
46
- print(f"Jina search returned {r.status_code}")
47
  return []
48
- data = r.json()
49
- results = data.get("data", [])
50
- return [{"title": item.get("title", ""),
51
- "snippet": item.get("description", item.get("content",""))[:500],
52
- "url": item.get("url", "")}
53
- for item in results[:max_results]
54
- if item.get("description") or item.get("content")]
 
 
 
 
 
55
  except Exception as e:
56
  print(f"Web search error: {e}")
57
  return []
58
 
59
-
60
  def decompose(query):
61
  r = client.models.generate_content(model=GEN_MODEL,
62
  contents=f"Decompose into 2-4 simpler sub-queries. Numbered list only.\n\nQuery: {query}")
@@ -68,9 +73,8 @@ def compress(query, chunks):
68
  numbered = "\n\n".join(f"[{i+1}]\n{c}" for i, c in enumerate(chunks))
69
  r = client.models.generate_content(model=GEN_MODEL, contents=(
70
  f"You have {len(chunks)} text chunks and a query.\n"
71
- f"For each chunk, extract ONLY sentences directly relevant to the query.\n"
72
- f"Reply in this exact format:\n"
73
- f"[1] <extracted sentences or EMPTY>\n[2] <...> etc.\n\n"
74
  f"Query: {query}\n\nChunks:\n{numbered}"))
75
  import re
76
  out = []
@@ -88,14 +92,14 @@ def vsearch(query, namespace, user_id, k):
88
  }).execute().data or [])
89
 
90
  PROMPTS = {
91
- 0: "Answer this from your knowledge:\n\n{q}",
92
  1: "Answer using ONLY the context. Be concise.\n\nContext:\n{ctx}\n\nQuestion: {q}\nAnswer:",
93
  2: "Synthesize the context step by step.\n\nContext:\n{ctx}\n\nQuestion: {q}\nAnswer:",
94
  3: "Use chain-of-thought reasoning.\n\nContext:\n{ctx}\n\nQuestion: {q}\nAnswer:",
95
  }
96
  WEB_PROMPT = (
97
  "Answer the question using ONLY the web search results below.\n"
98
- "Be factual and concise. Cite sources where relevant.\n\n"
99
  "Web results:\n{ctx}\n\nQuestion: {q}\nAnswer:"
100
  )
101
 
@@ -117,42 +121,42 @@ async def query_pipeline(query, namespace, top_k, rerank, user_id, use_web=False
117
  level = cls["level"]
118
  k = DEPTH[level]
119
 
120
- # ── Web search mode β€” skip ALL local retrieval, pure web only ──
121
  if use_web:
122
- web_hits = web_search(query, max_results=6)
123
- if not web_hits:
124
- return {"answer": "No web results found for this query.",
125
- "sources": [], "complexity": cls, "retrieval_source": "none"}
126
  ctx = "\n\n---\n\n".join(
127
- f"Source: {h['title']}\n{h['snippet']}" for h in web_hits)
128
  r = client.models.generate_content(model=GEN_MODEL,
129
  contents=WEB_PROMPT.format(ctx=ctx, q=query))
130
  return {
131
  "answer": r.text.strip(),
132
  "sources": [{"content": h["snippet"][:200],
133
  "metadata": {"title": h["title"], "url": h["url"]},
134
- "score": 1.0, "source": "web"} for h in web_hits],
135
  "complexity": cls,
136
  "retrieval_source": "web",
137
  }
138
 
139
- # ── L0: check docs first, fall back to model knowledge ────────
140
  if level == 0:
141
- hits = vsearch(query, namespace, user_id, 2)
142
- if hits:
143
- ctx = "\n\n---\n\n".join(h["content"] for h in hits)
144
  r = client.models.generate_content(model=GEN_MODEL, contents=(
145
  f"Use the context if it contains a relevant answer. "
146
  f"Otherwise answer from your own knowledge.\n\n"
147
  f"Context:\n{ctx}\n\nQuestion: {query}\nAnswer:"))
148
- top_score = hits[0].get("similarity", 0)
149
  return {
150
  "answer": r.text.strip(),
151
  "sources": [{"content": h["content"][:200],
152
  "metadata": h.get("metadata", {}),
153
  "score": h.get("similarity", 0),
154
  "source": "local"}
155
- for h in hits if h.get("similarity", 0) > 0.5],
156
  "complexity": cls,
157
  "retrieval_source": "local" if top_score > 0.5 else "model_knowledge",
158
  }
@@ -161,7 +165,7 @@ async def query_pipeline(query, namespace, top_k, rerank, user_id, use_web=False
161
  return {"answer": r.text.strip(), "sources": [],
162
  "complexity": cls, "retrieval_source": "model_knowledge"}
163
 
164
- # ── L1–L3: local vector retrieval ─────────────────────────────
165
  hits = []
166
  if level == 3:
167
  seen = set()
@@ -172,7 +176,6 @@ async def query_pipeline(query, namespace, top_k, rerank, user_id, use_web=False
172
  hits = vsearch(query, namespace, user_id, k)
173
 
174
  if not hits:
175
- # Auto-fallback to web if no local docs found
176
  web_hits = web_search(query, max_results=k)
177
  if not web_hits:
178
  return {"answer": "Nothing found locally or on the web.",
@@ -197,8 +200,7 @@ async def query_pipeline(query, namespace, top_k, rerank, user_id, use_web=False
197
  "sources": [{"content": h["content"][:200], "metadata": h.get("metadata", {}),
198
  "score": h.get("similarity", 0), "source": "local"}
199
  for h in hits[:len(lc)]],
200
- "complexity": cls,
201
- "retrieval_source": "local",
202
  }
203
 
204
  async def run_acra_pipeline(mode, **kw):
 
1
+ import os, httpx
2
  from google import genai
3
  from google.genai import types
4
  from db import supabase
5
  from classifier_inference import classify_query
 
6
  from typing import List
7
 
8
  client = genai.Client(api_key=os.environ["GEMINI_API_KEY"])
 
33
  return chunks or [text]
34
 
35
  def web_search(query: str, max_results: int = 5) -> List[dict]:
36
+ """Web search via Jina AI s.jina.ai β€” no API key needed, works from cloud."""
 
37
  try:
38
+ import urllib.parse
39
+ encoded = urllib.parse.quote(query)
40
+ r = httpx.get(
41
+ f"https://s.jina.ai/?q={encoded}",
42
+ headers={"Accept": "application/json", "X-Respond-With": "no-content"},
43
+ timeout=20.0,
44
+ follow_redirects=True
45
+ )
46
  if r.status_code != 200:
47
+ print(f"Jina returned {r.status_code}: {r.text[:200]}")
48
  return []
49
+ data = r.json()
50
+ items = data.get("data", [])
51
+ results = []
52
+ for item in items[:max_results]:
53
+ snippet = item.get("description") or item.get("content","")
54
+ if snippet:
55
+ results.append({
56
+ "title": item.get("title", ""),
57
+ "snippet": snippet[:500],
58
+ "url": item.get("url", "")
59
+ })
60
+ return results
61
  except Exception as e:
62
  print(f"Web search error: {e}")
63
  return []
64
 
 
65
  def decompose(query):
66
  r = client.models.generate_content(model=GEN_MODEL,
67
  contents=f"Decompose into 2-4 simpler sub-queries. Numbered list only.\n\nQuery: {query}")
 
73
  numbered = "\n\n".join(f"[{i+1}]\n{c}" for i, c in enumerate(chunks))
74
  r = client.models.generate_content(model=GEN_MODEL, contents=(
75
  f"You have {len(chunks)} text chunks and a query.\n"
76
+ f"For each chunk extract ONLY sentences relevant to the query.\n"
77
+ f"Reply as [1] <text or EMPTY> [2] <text or EMPTY> etc.\n\n"
 
78
  f"Query: {query}\n\nChunks:\n{numbered}"))
79
  import re
80
  out = []
 
92
  }).execute().data or [])
93
 
94
  PROMPTS = {
95
+ 0: "Answer from your knowledge:\n\n{q}",
96
  1: "Answer using ONLY the context. Be concise.\n\nContext:\n{ctx}\n\nQuestion: {q}\nAnswer:",
97
  2: "Synthesize the context step by step.\n\nContext:\n{ctx}\n\nQuestion: {q}\nAnswer:",
98
  3: "Use chain-of-thought reasoning.\n\nContext:\n{ctx}\n\nQuestion: {q}\nAnswer:",
99
  }
100
  WEB_PROMPT = (
101
  "Answer the question using ONLY the web search results below.\n"
102
+ "Be factual and concise.\n\n"
103
  "Web results:\n{ctx}\n\nQuestion: {q}\nAnswer:"
104
  )
105
 
 
121
  level = cls["level"]
122
  k = DEPTH[level]
123
 
124
+ # use_web=True: skip ALL local retrieval, pure Jina web search
125
  if use_web:
126
+ hits = web_search(query, max_results=6)
127
+ if not hits:
128
+ return {"answer": "No web results found.", "sources": [],
129
+ "complexity": cls, "retrieval_source": "none"}
130
  ctx = "\n\n---\n\n".join(
131
+ f"Source: {h['title']}\nURL: {h['url']}\n{h['snippet']}" for h in hits)
132
  r = client.models.generate_content(model=GEN_MODEL,
133
  contents=WEB_PROMPT.format(ctx=ctx, q=query))
134
  return {
135
  "answer": r.text.strip(),
136
  "sources": [{"content": h["snippet"][:200],
137
  "metadata": {"title": h["title"], "url": h["url"]},
138
+ "score": 1.0, "source": "web"} for h in hits],
139
  "complexity": cls,
140
  "retrieval_source": "web",
141
  }
142
 
143
+ # L0: check docs first, fall back to model knowledge
144
  if level == 0:
145
+ doc_hits = vsearch(query, namespace, user_id, 2)
146
+ if doc_hits:
147
+ ctx = "\n\n---\n\n".join(h["content"] for h in doc_hits)
148
  r = client.models.generate_content(model=GEN_MODEL, contents=(
149
  f"Use the context if it contains a relevant answer. "
150
  f"Otherwise answer from your own knowledge.\n\n"
151
  f"Context:\n{ctx}\n\nQuestion: {query}\nAnswer:"))
152
+ top_score = doc_hits[0].get("similarity", 0)
153
  return {
154
  "answer": r.text.strip(),
155
  "sources": [{"content": h["content"][:200],
156
  "metadata": h.get("metadata", {}),
157
  "score": h.get("similarity", 0),
158
  "source": "local"}
159
+ for h in doc_hits if h.get("similarity", 0) > 0.5],
160
  "complexity": cls,
161
  "retrieval_source": "local" if top_score > 0.5 else "model_knowledge",
162
  }
 
165
  return {"answer": r.text.strip(), "sources": [],
166
  "complexity": cls, "retrieval_source": "model_knowledge"}
167
 
168
+ # L1-L3: local vector retrieval
169
  hits = []
170
  if level == 3:
171
  seen = set()
 
176
  hits = vsearch(query, namespace, user_id, k)
177
 
178
  if not hits:
 
179
  web_hits = web_search(query, max_results=k)
180
  if not web_hits:
181
  return {"answer": "Nothing found locally or on the web.",
 
200
  "sources": [{"content": h["content"][:200], "metadata": h.get("metadata", {}),
201
  "score": h.get("similarity", 0), "source": "local"}
202
  for h in hits[:len(lc)]],
203
+ "complexity": cls, "retrieval_source": "local",
 
204
  }
205
 
206
  async def run_acra_pipeline(mode, **kw):