Commit
·
06c0480
1
Parent(s):
91e1308
...
Browse files
app.py
CHANGED
|
@@ -81,7 +81,8 @@ DEFAULT_RAG_CONFIG = {
|
|
| 81 |
'processing': {
|
| 82 |
'trusted_sources': {'wikipedia.org': 0.8, 'reuters.com': 0.75, 'apnews.com': 0.75},
|
| 83 |
'evidence_categories': {'GENERAL': ['information', 'details', 'facts', 'explanation']},
|
| 84 |
-
'scoring_weights': {'source': 0.5, 'temporal': 0.3, 'category_match': 0.2}
|
|
|
|
| 85 |
},
|
| 86 |
'enrichment': {
|
| 87 |
'enabled': True, 'workers': 3, 'timeout': 10,
|
|
@@ -270,6 +271,12 @@ class CacheManager:
|
|
| 270 |
def __contains__(self, key): return key in self._cache and (time.time()-self._timestamps.get(key,0)<self.ttl)
|
| 271 |
|
| 272 |
class SearchProvider(ABC):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 273 |
@property
|
| 274 |
@abstractmethod
|
| 275 |
def provider_name(self) -> str:
|
|
@@ -363,33 +370,59 @@ class TavilyProvider(SearchProvider):
|
|
| 363 |
else:
|
| 364 |
self._enabled = False
|
| 365 |
gaia_logger.warning(f"✗ {self.provider_name} API key missing or TavilyClient not available in config.")
|
|
|
|
| 366 |
def _perform_search(self, query: str, max_results: int) -> Optional[List[Dict[str, str]]]:
|
| 367 |
-
if not self._enabled:
|
|
|
|
| 368 |
try:
|
| 369 |
response = self._client.search(query=query, max_results=max_results, search_depth=self._search_depth)
|
| 370 |
hits = response.get('results', [])
|
| 371 |
-
if not hits:
|
| 372 |
-
|
| 373 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 374 |
|
| 375 |
class DuckDuckGoProvider(SearchProvider):
|
| 376 |
@property
|
| 377 |
def provider_name(self) -> str:
|
| 378 |
return "DuckDuckGo"
|
|
|
|
| 379 |
def __init__(self, config_dict: Dict):
|
| 380 |
super().__init__(config_dict)
|
| 381 |
if DDGS:
|
| 382 |
-
try:
|
| 383 |
-
|
| 384 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 385 |
self._quota_limit = float('inf')
|
|
|
|
| 386 |
def _perform_search(self, query: str, max_results: int) -> Optional[List[Dict[str, str]]]:
|
| 387 |
-
if not self._enabled:
|
|
|
|
| 388 |
try:
|
| 389 |
hits = list(self._client.text(query, region='wt-wt', max_results=max_results))[:max_results]
|
| 390 |
-
if not hits:
|
| 391 |
-
|
| 392 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 393 |
|
| 394 |
class CompositeSearchClient:
|
| 395 |
def __init__(self, config_dict: Dict):
|
|
@@ -398,47 +431,66 @@ class CompositeSearchClient:
|
|
| 398 |
self.providers = self._init_providers(config_dict)
|
| 399 |
self.cache = CacheManager(
|
| 400 |
ttl=config_dict.get('caching', {}).get('search_cache_ttl', 300),
|
| 401 |
-
max_size=config_dict.get('caching', {}).get('search_cache_size', 50),
|
|
|
|
|
|
|
| 402 |
self._retry_att = self._search_config.get("retry_attempts", 2)
|
| 403 |
self._retry_del = self._search_config.get("retry_delay", 2)
|
| 404 |
self._def_max_r = self._search_config.get("default_max_results", 3)
|
|
|
|
| 405 |
def _init_providers(self, config_dict: Dict) -> List[SearchProvider]:
|
| 406 |
providers: List[SearchProvider] = []
|
| 407 |
if TAVILY_API_KEY and TavilyClient:
|
| 408 |
-
tavily_prov = TavilyProvider(config_dict)
|
| 409 |
-
if tavily_prov.available():
|
|
|
|
| 410 |
if GOOGLE_CUSTOM_SEARCH_API_KEY and GOOGLE_CUSTOM_SEARCH_CSE_ID:
|
| 411 |
-
google_prov = GoogleProvider(config_dict)
|
| 412 |
-
if google_prov.available():
|
|
|
|
| 413 |
if DDGS:
|
| 414 |
-
ddgs_prov = DuckDuckGoProvider(config_dict)
|
| 415 |
-
if ddgs_prov.available():
|
| 416 |
-
|
| 417 |
-
|
|
|
|
|
|
|
|
|
|
| 418 |
return providers
|
|
|
|
| 419 |
def search(self, query: str, max_results: Optional[int] = None, force_refresh: bool = False) -> List[Dict]:
|
| 420 |
q, actual_r = query.strip(), max_results if max_results is not None else self._def_max_r
|
| 421 |
-
if not q:
|
|
|
|
| 422 |
cache_key = (q, actual_r)
|
| 423 |
-
if not force_refresh and (cached := self.cache.get(cache_key)) is not None:
|
|
|
|
| 424 |
for prov in self.providers:
|
| 425 |
for attempt in range(self._retry_att + 1):
|
| 426 |
-
if not prov.available():
|
|
|
|
| 427 |
try:
|
| 428 |
results = prov.search(q, actual_r)
|
| 429 |
-
if results is not None:
|
|
|
|
|
|
|
| 430 |
gaia_logger.warning(f"[{prov.provider_name}] search None: '{q[:50]}' (att {attempt+1})")
|
| 431 |
-
if attempt < self._retry_att:
|
|
|
|
| 432 |
except Exception as e:
|
| 433 |
gaia_logger.error(f"[{prov.provider_name}] Ex during search '{q[:50]}': {e}", exc_info=True)
|
| 434 |
-
if attempt < self._retry_att:
|
|
|
|
| 435 |
gaia_logger.error(f"RAG: All providers failed for query: '{q[:50]}'.")
|
| 436 |
-
self.cache.set(cache_key, [])
|
|
|
|
| 437 |
|
| 438 |
class GaiaQueryBuilder:
|
| 439 |
def __init__(self, base_query: str, config_dict: Dict):
|
| 440 |
self.base_query = base_query.strip()
|
|
|
|
| 441 |
gaia_logger.debug(f"GaiaQueryBuilder init: '{self.base_query[:100]}'")
|
|
|
|
| 442 |
def get_queries(self) -> Dict[str, List[Tuple[str, str]]]:
|
| 443 |
queries = {'primary': [(self.base_query, 'GENERAL')]} if self.base_query else {'primary': []}
|
| 444 |
gaia_logger.debug(f"RAG Generated queries: {queries}")
|
|
@@ -451,105 +503,174 @@ class ResultProcessor:
|
|
| 451 |
self.seen_urls: Set[str] = set()
|
| 452 |
self.date_pattern = DEFAULT_RAG_CONFIG['processing'].get('date_pattern', r'\b\d{4}\b')
|
| 453 |
gaia_logger.debug("RAG ResultProcessor initialized.")
|
|
|
|
| 454 |
def process_batch(self, results: List[Dict], query_tag: str, initial_cat: str='GENERAL') -> List[Dict]:
|
| 455 |
processed: List[Dict] = []
|
| 456 |
-
if not results:
|
|
|
|
| 457 |
for r in results:
|
| 458 |
url = r.get('href')
|
| 459 |
-
if not url or self._normalize_url(url) in self.seen_urls:
|
|
|
|
| 460 |
self.seen_urls.add(self._normalize_url(url))
|
| 461 |
-
res_data = {
|
| 462 |
-
|
| 463 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 464 |
gaia_logger.debug(f"[RAG Proc] Batch: {len(processed)} new results from '{query_tag}'")
|
| 465 |
return processed
|
| 466 |
-
|
|
|
|
|
|
|
|
|
|
| 467 |
def _score_result(self, result: Dict):
|
| 468 |
-
url,body,title = result.get('href',''),result.get('body',''),result.get('title','')
|
| 469 |
source_q = 0.5
|
| 470 |
-
if domain_match := re.search(r'https?://(?:www\.)?([^/]+)', url or ""):
|
| 471 |
-
|
| 472 |
-
|
| 473 |
-
|
| 474 |
-
|
| 475 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 476 |
|
| 477 |
class ContentEnricher:
|
| 478 |
def __init__(self, config_dict: Dict):
|
| 479 |
self.enrich_config = config_dict.get('enrichment', {})
|
| 480 |
self._enabled = self.enrich_config.get('enabled', False) and bool(BeautifulSoup)
|
| 481 |
-
if not self._enabled:
|
| 482 |
-
|
| 483 |
-
|
| 484 |
-
self.
|
|
|
|
|
|
|
|
|
|
| 485 |
self.cache = CacheManager(
|
| 486 |
-
ttl=config_dict.get('caching',{}).get('enrich_cache_ttl',600),
|
| 487 |
-
max_size=config_dict.get('caching',{}).get('enrich_cache_size',25),
|
|
|
|
|
|
|
| 488 |
gaia_logger.info(f"RAG ContentEnricher Initialized. Enabled: {self._enabled}")
|
| 489 |
-
|
| 490 |
-
|
|
|
|
|
|
|
| 491 |
updated_res = []
|
| 492 |
with ThreadPoolExecutor(max_workers=self._max_w) as executor:
|
| 493 |
future_map = {executor.submit(self._fetch_single, r, force_refresh): r for r in results}
|
| 494 |
-
for future in as_completed(future_map):
|
|
|
|
| 495 |
return updated_res
|
|
|
|
| 496 |
def _fetch_single(self, result: Dict, force_refresh: bool) -> Dict:
|
| 497 |
-
url = result.get('href')
|
| 498 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 499 |
if not force_refresh and (cached := self.cache.get(url)) is not None:
|
| 500 |
-
if cached:
|
| 501 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 502 |
try:
|
| 503 |
headers = {'User-Agent': 'Mozilla/5.0 GaiaRAGAgent/1.0'}
|
| 504 |
-
response = requests.get(url, headers=headers, timeout=self._timeout, allow_redirects=True)
|
| 505 |
-
|
|
|
|
|
|
|
|
|
|
| 506 |
soup = BeautifulSoup(response.text, 'lxml')
|
| 507 |
-
for el_name in ["script","style","nav","header","footer","aside","form","iframe","img","svg",".ad",".advertisement"]:
|
| 508 |
-
for el in soup.select(el_name):
|
|
|
|
| 509 |
main_el = soup.select_one('article, main, [role="main"], .entry-content, .post-content, #content, #main') or soup.body
|
| 510 |
-
text = main_el.get_text(separator='\n',strip=True) if main_el else ""
|
| 511 |
text = re.sub(r'(\s*\n\s*){2,}', '\n\n', text).strip()
|
| 512 |
if len(text) >= self._min_l:
|
| 513 |
result['body'] = text[:self._max_l] + ("..." if len(text) > self._max_l else "")
|
| 514 |
-
result['enriched'] = True
|
|
|
|
| 515 |
gaia_logger.info(f"[Enrich] OK: {url} ({len(result['body'])} chars).")
|
| 516 |
-
else:
|
| 517 |
-
|
|
|
|
|
|
|
|
|
|
| 518 |
return result
|
| 519 |
|
| 520 |
class GeneralRAGPipeline:
|
| 521 |
def __init__(self, config_dict: Optional[Dict] = None):
|
| 522 |
self.config = config_dict if config_dict is not None else DEFAULT_RAG_CONFIG
|
| 523 |
self.search_client = CompositeSearchClient(self.config)
|
| 524 |
-
enrich_cfg = self.config.get('enrichment', {})
|
| 525 |
-
|
| 526 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 527 |
gaia_logger.info("GeneralRAGPipeline initialized.")
|
|
|
|
| 528 |
def analyze(self, query: str, force_refresh: bool = False) -> List[Dict]:
|
| 529 |
-
q=query.strip()
|
| 530 |
-
if not q:
|
| 531 |
-
|
| 532 |
-
|
| 533 |
-
|
| 534 |
-
|
| 535 |
-
|
| 536 |
-
|
| 537 |
-
|
| 538 |
-
|
| 539 |
-
|
| 540 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 541 |
gaia_logger.info(f"[RAG Analyze] Stage '{stage}': Search '{query_s[:70]}'")
|
| 542 |
-
s_res=self.search_client.search(query_s,max_results=max_r_pq,force_refresh=force_refresh)
|
| 543 |
all_res.extend(res_proc.process_batch(s_res or [], query_s, initial_cat=cat))
|
| 544 |
-
all_res.sort(key=lambda x: x.get('combined_score',0), reverse=True)
|
| 545 |
if enrich_en and self.enricher and all_res:
|
| 546 |
-
to_enrich=[r for r in all_res[:enrich_cnt] if r.get('href')]
|
| 547 |
gaia_logger.info(f"[RAG Analyze] Enriching {len(to_enrich)} items...")
|
| 548 |
-
enriched_map
|
| 549 |
-
|
| 550 |
-
|
| 551 |
-
|
| 552 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 553 |
|
| 554 |
class GaiaLevel1Agent:
|
| 555 |
def __init__(self, api_url: str = DEFAULT_API_URL):
|
|
@@ -563,10 +684,13 @@ class GaiaLevel1Agent:
|
|
| 563 |
model_name = 'gemini-1.0-pro'
|
| 564 |
self.llm_model = genai.GenerativeModel(model_name)
|
| 565 |
gaia_logger.info(f"Gemini LLM ('{model_name}') initialized.")
|
| 566 |
-
except Exception as e:
|
| 567 |
-
|
|
|
|
|
|
|
| 568 |
|
| 569 |
-
if not self.llm_model:
|
|
|
|
| 570 |
gaia_logger.info(f"GaiaLevel1Agent (RAG & FileProcessor) initialized. API: {self.api_url}")
|
| 571 |
|
| 572 |
@lru_cache(maxsize=32)
|
|
@@ -582,7 +706,8 @@ class GaiaLevel1Agent:
|
|
| 582 |
content_disposition = response.headers.get('Content-Disposition')
|
| 583 |
if content_disposition:
|
| 584 |
header_filename = FileProcessor._get_filename_from_url(content_disposition)
|
| 585 |
-
if header_filename != "unknown_file":
|
|
|
|
| 586 |
|
| 587 |
content_type = response.headers.get("Content-Type", "")
|
| 588 |
|
|
@@ -591,21 +716,27 @@ class GaiaLevel1Agent:
|
|
| 591 |
return processed_content
|
| 592 |
|
| 593 |
except requests.exceptions.HTTPError as e:
|
| 594 |
-
if e.response.status_code == 404:
|
|
|
|
|
|
|
| 595 |
gaia_logger.warning(f"HTTP error fetching file {task_id}: {e}")
|
| 596 |
except requests.exceptions.Timeout:
|
| 597 |
gaia_logger.warning(f"Timeout fetching file {task_id}")
|
| 598 |
-
if attempt < 1:
|
|
|
|
| 599 |
except Exception as e:
|
| 600 |
gaia_logger.error(f"Error fetching/processing file {task_id} ({file_url}): {e}", exc_info=True)
|
| 601 |
-
if attempt < 1:
|
|
|
|
| 602 |
return None
|
| 603 |
|
| 604 |
def _formulate_answer_with_llm(self, question: str, file_context: Optional[str], web_context: Optional[str]) -> str:
|
| 605 |
if not self.llm_model:
|
| 606 |
gaia_logger.warning("LLM model (Gemini) not available for answer formulation.")
|
| 607 |
-
if web_context:
|
| 608 |
-
|
|
|
|
|
|
|
| 609 |
return "I am currently unable to process this request fully as the LLM is not available."
|
| 610 |
|
| 611 |
prompt_parts = [
|
|
@@ -626,8 +757,8 @@ class GaiaLevel1Agent:
|
|
| 626 |
truncated_web_context = web_context[:available_len_for_web] + "\n... (web context truncated)"
|
| 627 |
gaia_logger.info(f"Truncated web context from {len(web_context)} to {len(truncated_web_context)} chars.")
|
| 628 |
elif available_len_for_web <= 0 and web_context:
|
| 629 |
-
|
| 630 |
-
|
| 631 |
|
| 632 |
prompt_parts.extend(["\n\nContext from Web Search Results:\n---", truncated_web_context, "---"])
|
| 633 |
combined_context_len += len(truncated_web_context)
|
|
@@ -652,7 +783,7 @@ class GaiaLevel1Agent:
|
|
| 652 |
top_p=0.95,
|
| 653 |
max_output_tokens=2048
|
| 654 |
)
|
| 655 |
-
safety_set = [{"category":c,"threshold":"BLOCK_MEDIUM_AND_ABOVE"} for c in ["HARM_CATEGORY_HARASSMENT","HARM_CATEGORY_HATE_SPEECH","HARM_CATEGORY_SEXUALLY_EXPLICIT","HARM_CATEGORY_DANGEROUS_CONTENT"]]
|
| 656 |
|
| 657 |
response = self.llm_model.generate_content(
|
| 658 |
final_prompt,
|
|
@@ -672,7 +803,8 @@ class GaiaLevel1Agent:
|
|
| 672 |
return llm_answer
|
| 673 |
except Exception as e:
|
| 674 |
gaia_logger.error(f"Error calling Gemini API: {e}", exc_info=True)
|
| 675 |
-
if "429" in str(e) or "ResourceExhausted" in str(type(e).__name__):
|
|
|
|
| 676 |
return "Error generating LLM answer."
|
| 677 |
|
| 678 |
def __call__(self, question: str, task_id: Optional[str] = None) -> str:
|
|
@@ -697,7 +829,9 @@ class GaiaLevel1Agent:
|
|
| 697 |
if not any(kw in q_lower for kw in web_still_needed_kws):
|
| 698 |
needs_web = False
|
| 699 |
gaia_logger.info("Substantial file context present and question doesn't strongly imply web search. Skipping web search.")
|
| 700 |
-
if "don't search" in q_lower or "do not search" in q_lower:
|
|
|
|
|
|
|
| 701 |
|
| 702 |
if needs_web:
|
| 703 |
search_q = question.replace("?", "").strip()
|
|
@@ -707,14 +841,15 @@ class GaiaLevel1Agent:
|
|
| 707 |
snippets = []
|
| 708 |
for i, res_item in enumerate(rag_res):
|
| 709 |
title, body, href = res_item.get('title','N/A'), res_item.get('body',''), res_item.get('href','#')
|
| 710 |
-
provider = res_item.get('query_tag','WebSearch')
|
| 711 |
prefix = "Enriched" if res_item.get('enriched') else "Snippet"
|
| 712 |
body_str = str(body) if body is not None else ""
|
| 713 |
body_prompt = body_str[:(MAX_CONTEXT_LENGTH_LLM // (len(rag_res) if rag_res else 1)) - 200] + "..." if len(body_str) > 2800 else body_str
|
| 714 |
snippets.append(f"Source [{i+1} - {provider}]: {title}\nURL: {href}\n{prefix}: {body_prompt}\n---")
|
| 715 |
web_ctx_str = "\n\n".join(snippets)
|
| 716 |
gaia_logger.info(f"RAG results: {len(web_ctx_str)} chars from {len(rag_res)} sources.")
|
| 717 |
-
else:
|
|
|
|
| 718 |
|
| 719 |
answer = self._formulate_answer_with_llm(question, file_ctx_str, web_ctx_str)
|
| 720 |
gaia_logger.info(f"Final answer (first 70): {answer[:70]}...")
|
|
@@ -722,50 +857,73 @@ class GaiaLevel1Agent:
|
|
| 722 |
|
| 723 |
def run_and_submit_all(profile: gr.OAuthProfile | None):
|
| 724 |
space_id = os.getenv("SPACE_ID")
|
| 725 |
-
if profile:
|
| 726 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 727 |
questions_url, submit_url = f"{DEFAULT_API_URL}/questions", f"{DEFAULT_API_URL}/submit"
|
| 728 |
try:
|
| 729 |
agent = GaiaLevel1Agent(api_url=DEFAULT_API_URL)
|
| 730 |
gaia_logger.info("GaiaLevel1Agent (RAG & FileProcessor) initialized for evaluation.")
|
| 731 |
-
except Exception as e:
|
|
|
|
|
|
|
| 732 |
agent_code = f"https://huggingface.co/spaces/{space_id}/tree/main" if space_id else "Code link unavailable"
|
| 733 |
gaia_logger.info(f"Agent code link: {agent_code}")
|
| 734 |
try:
|
| 735 |
-
response=requests.get(questions_url,timeout=15)
|
| 736 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 737 |
gaia_logger.info(f"Fetched {len(questions_data)} questions.")
|
| 738 |
-
except Exception as e:
|
| 739 |
-
|
| 740 |
-
|
|
|
|
|
|
|
|
|
|
| 741 |
gaia_logger.info(f"LLM Rate: {GEMINI_RPM_LIMIT} RPM. Sleep ~{sleep_llm:.2f}s between LLM calls.")
|
| 742 |
gaia_logger.info(f"Running agent on {len(questions_data)} questions...")
|
| 743 |
-
for i,item in enumerate(questions_data):
|
| 744 |
-
task_id,q_text=item.get("task_id"),item.get("question")
|
| 745 |
-
if not task_id or q_text is None:
|
|
|
|
|
|
|
| 746 |
gaia_logger.info(f"Q {i+1}/{len(questions_data)} - Task: {task_id}")
|
| 747 |
try:
|
| 748 |
-
answer=agent(question=q_text,task_id=task_id)
|
| 749 |
-
answers_payload.append({"task_id":task_id,"submitted_answer":answer})
|
| 750 |
-
results_log.append({"Task ID":task_id,"Question":q_text,"Submitted Answer":answer})
|
| 751 |
except Exception as e:
|
| 752 |
-
|
| 753 |
-
|
| 754 |
-
if i<len(questions_data)-1:
|
| 755 |
-
|
| 756 |
-
|
|
|
|
|
|
|
|
|
|
| 757 |
gaia_logger.info(f"Submitting {len(answers_payload)} answers for '{username}'...")
|
| 758 |
try:
|
| 759 |
-
response=requests.post(submit_url,json=submission,timeout=60)
|
| 760 |
-
|
| 761 |
-
|
| 762 |
-
|
| 763 |
-
|
|
|
|
|
|
|
|
|
|
| 764 |
except requests.exceptions.HTTPError as e:
|
| 765 |
-
err_detail=f"Server: {e.response.status_code}. Detail: {e.response.text[:200]}"
|
| 766 |
-
gaia_logger.error(f"Submission Fail HTTP: {err_detail}",exc_info=False)
|
| 767 |
-
return f"Submission Failed: {err_detail}",pd.DataFrame(results_log)
|
| 768 |
-
except Exception as e:
|
|
|
|
|
|
|
| 769 |
|
| 770 |
with gr.Blocks(title="GAIA RAG Agent - Advanced") as demo:
|
| 771 |
gr.Markdown("# Gaia Level 1 Agent (RAG & FileProcessor) Evaluation Runner")
|
|
@@ -794,15 +952,23 @@ if __name__ == "__main__":
|
|
| 794 |
}
|
| 795 |
missing_keys = [key_name for key_name, key_val in required_env.items() if not key_val]
|
| 796 |
for key_name in required_env:
|
| 797 |
-
if required_env[key_name]:
|
| 798 |
-
|
| 799 |
-
|
| 800 |
-
|
| 801 |
-
|
| 802 |
-
if not
|
| 803 |
-
|
| 804 |
-
|
| 805 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 806 |
|
| 807 |
if missing_keys:
|
| 808 |
print(f"\n--- PLEASE SET THE FOLLOWING MISSING ENVIRONMENT VARIABLES FOR FULL FUNCTIONALITY: {', '.join(missing_keys)} ---\n")
|
|
|
|
| 81 |
'processing': {
|
| 82 |
'trusted_sources': {'wikipedia.org': 0.8, 'reuters.com': 0.75, 'apnews.com': 0.75},
|
| 83 |
'evidence_categories': {'GENERAL': ['information', 'details', 'facts', 'explanation']},
|
| 84 |
+
'scoring_weights': {'source': 0.5, 'temporal': 0.3, 'category_match': 0.2},
|
| 85 |
+
'date_pattern': r'\b\d{4}\b'
|
| 86 |
},
|
| 87 |
'enrichment': {
|
| 88 |
'enabled': True, 'workers': 3, 'timeout': 10,
|
|
|
|
| 271 |
def __contains__(self, key): return key in self._cache and (time.time()-self._timestamps.get(key,0)<self.ttl)
|
| 272 |
|
| 273 |
class SearchProvider(ABC):
|
| 274 |
+
def __init__(self, config_dict: Dict):
|
| 275 |
+
self.provider_config = config_dict.get('search', {})
|
| 276 |
+
self._enabled = False
|
| 277 |
+
self._quota_limit = self.provider_config.get("quota_limit", float('inf'))
|
| 278 |
+
self._quota_used = 0
|
| 279 |
+
|
| 280 |
@property
|
| 281 |
@abstractmethod
|
| 282 |
def provider_name(self) -> str:
|
|
|
|
| 370 |
else:
|
| 371 |
self._enabled = False
|
| 372 |
gaia_logger.warning(f"✗ {self.provider_name} API key missing or TavilyClient not available in config.")
|
| 373 |
+
|
| 374 |
def _perform_search(self, query: str, max_results: int) -> Optional[List[Dict[str, str]]]:
|
| 375 |
+
if not self._enabled:
|
| 376 |
+
return None
|
| 377 |
try:
|
| 378 |
response = self._client.search(query=query, max_results=max_results, search_depth=self._search_depth)
|
| 379 |
hits = response.get('results', [])
|
| 380 |
+
if not hits:
|
| 381 |
+
gaia_logger.info(f"[{self.provider_name}] No results: '{query[:70]}'")
|
| 382 |
+
return []
|
| 383 |
+
return [{
|
| 384 |
+
'href': h.get('url'),
|
| 385 |
+
'title': h.get('title',''),
|
| 386 |
+
'body': h.get('content','')
|
| 387 |
+
} for h in hits]
|
| 388 |
+
except Exception as e:
|
| 389 |
+
gaia_logger.warning(f"[{self.provider_name}] Search fail: '{query[:70]}': {e}")
|
| 390 |
+
return None
|
| 391 |
|
| 392 |
class DuckDuckGoProvider(SearchProvider):
|
| 393 |
@property
|
| 394 |
def provider_name(self) -> str:
|
| 395 |
return "DuckDuckGo"
|
| 396 |
+
|
| 397 |
def __init__(self, config_dict: Dict):
|
| 398 |
super().__init__(config_dict)
|
| 399 |
if DDGS:
|
| 400 |
+
try:
|
| 401 |
+
self._client = DDGS(timeout=10)
|
| 402 |
+
self._enabled = True
|
| 403 |
+
gaia_logger.info(f"✓ {self.provider_name} Search initialized.")
|
| 404 |
+
except Exception as e:
|
| 405 |
+
gaia_logger.warning(f"✗ {self.provider_name} init fail: {e}", exc_info=False)
|
| 406 |
+
else:
|
| 407 |
+
gaia_logger.warning(f"✗ {self.provider_name}: DDGS lib missing.")
|
| 408 |
self._quota_limit = float('inf')
|
| 409 |
+
|
| 410 |
def _perform_search(self, query: str, max_results: int) -> Optional[List[Dict[str, str]]]:
|
| 411 |
+
if not self._enabled:
|
| 412 |
+
return None
|
| 413 |
try:
|
| 414 |
hits = list(self._client.text(query, region='wt-wt', max_results=max_results))[:max_results]
|
| 415 |
+
if not hits:
|
| 416 |
+
gaia_logger.info(f"[{self.provider_name}] No results: '{query[:70]}'")
|
| 417 |
+
return []
|
| 418 |
+
return [{
|
| 419 |
+
'href': r.get('href'),
|
| 420 |
+
'title': r.get('title',''),
|
| 421 |
+
'body': r.get('body','')
|
| 422 |
+
} for r in hits]
|
| 423 |
+
except Exception as e:
|
| 424 |
+
gaia_logger.warning(f"[{self.provider_name}] Search fail: '{query[:70]}': {e}")
|
| 425 |
+
return None
|
| 426 |
|
| 427 |
class CompositeSearchClient:
|
| 428 |
def __init__(self, config_dict: Dict):
|
|
|
|
| 431 |
self.providers = self._init_providers(config_dict)
|
| 432 |
self.cache = CacheManager(
|
| 433 |
ttl=config_dict.get('caching', {}).get('search_cache_ttl', 300),
|
| 434 |
+
max_size=config_dict.get('caching', {}).get('search_cache_size', 50),
|
| 435 |
+
name="SearchClientCache"
|
| 436 |
+
)
|
| 437 |
self._retry_att = self._search_config.get("retry_attempts", 2)
|
| 438 |
self._retry_del = self._search_config.get("retry_delay", 2)
|
| 439 |
self._def_max_r = self._search_config.get("default_max_results", 3)
|
| 440 |
+
|
| 441 |
def _init_providers(self, config_dict: Dict) -> List[SearchProvider]:
|
| 442 |
providers: List[SearchProvider] = []
|
| 443 |
if TAVILY_API_KEY and TavilyClient:
|
| 444 |
+
tavily_prov = TavilyProvider(config_dict)
|
| 445 |
+
if tavily_prov.available():
|
| 446 |
+
providers.append(tavily_prov)
|
| 447 |
if GOOGLE_CUSTOM_SEARCH_API_KEY and GOOGLE_CUSTOM_SEARCH_CSE_ID:
|
| 448 |
+
google_prov = GoogleProvider(config_dict)
|
| 449 |
+
if google_prov.available():
|
| 450 |
+
providers.append(google_prov)
|
| 451 |
if DDGS:
|
| 452 |
+
ddgs_prov = DuckDuckGoProvider(config_dict)
|
| 453 |
+
if ddgs_prov.available():
|
| 454 |
+
providers.append(ddgs_prov)
|
| 455 |
+
if not providers:
|
| 456 |
+
gaia_logger.error("RAG: No search providers initialized!")
|
| 457 |
+
else:
|
| 458 |
+
gaia_logger.info(f"RAG Providers: {[p.provider_name for p in providers]}")
|
| 459 |
return providers
|
| 460 |
+
|
| 461 |
def search(self, query: str, max_results: Optional[int] = None, force_refresh: bool = False) -> List[Dict]:
|
| 462 |
q, actual_r = query.strip(), max_results if max_results is not None else self._def_max_r
|
| 463 |
+
if not q:
|
| 464 |
+
return []
|
| 465 |
cache_key = (q, actual_r)
|
| 466 |
+
if not force_refresh and (cached := self.cache.get(cache_key)) is not None:
|
| 467 |
+
return cached
|
| 468 |
for prov in self.providers:
|
| 469 |
for attempt in range(self._retry_att + 1):
|
| 470 |
+
if not prov.available():
|
| 471 |
+
break
|
| 472 |
try:
|
| 473 |
results = prov.search(q, actual_r)
|
| 474 |
+
if results is not None:
|
| 475 |
+
self.cache.set(cache_key, results)
|
| 476 |
+
return results
|
| 477 |
gaia_logger.warning(f"[{prov.provider_name}] search None: '{q[:50]}' (att {attempt+1})")
|
| 478 |
+
if attempt < self._retry_att:
|
| 479 |
+
time.sleep(self._retry_del)
|
| 480 |
except Exception as e:
|
| 481 |
gaia_logger.error(f"[{prov.provider_name}] Ex during search '{q[:50]}': {e}", exc_info=True)
|
| 482 |
+
if attempt < self._retry_att:
|
| 483 |
+
time.sleep(self._retry_del)
|
| 484 |
gaia_logger.error(f"RAG: All providers failed for query: '{q[:50]}'.")
|
| 485 |
+
self.cache.set(cache_key, [])
|
| 486 |
+
return []
|
| 487 |
|
| 488 |
class GaiaQueryBuilder:
|
| 489 |
def __init__(self, base_query: str, config_dict: Dict):
|
| 490 |
self.base_query = base_query.strip()
|
| 491 |
+
self.config = config_dict # Fixed: store config_dict for potential future use.
|
| 492 |
gaia_logger.debug(f"GaiaQueryBuilder init: '{self.base_query[:100]}'")
|
| 493 |
+
|
| 494 |
def get_queries(self) -> Dict[str, List[Tuple[str, str]]]:
|
| 495 |
queries = {'primary': [(self.base_query, 'GENERAL')]} if self.base_query else {'primary': []}
|
| 496 |
gaia_logger.debug(f"RAG Generated queries: {queries}")
|
|
|
|
| 503 |
self.seen_urls: Set[str] = set()
|
| 504 |
self.date_pattern = DEFAULT_RAG_CONFIG['processing'].get('date_pattern', r'\b\d{4}\b')
|
| 505 |
gaia_logger.debug("RAG ResultProcessor initialized.")
|
| 506 |
+
|
| 507 |
def process_batch(self, results: List[Dict], query_tag: str, initial_cat: str='GENERAL') -> List[Dict]:
|
| 508 |
processed: List[Dict] = []
|
| 509 |
+
if not results:
|
| 510 |
+
return processed
|
| 511 |
for r in results:
|
| 512 |
url = r.get('href')
|
| 513 |
+
if not url or self._normalize_url(url) in self.seen_urls:
|
| 514 |
+
continue
|
| 515 |
self.seen_urls.add(self._normalize_url(url))
|
| 516 |
+
res_data = {
|
| 517 |
+
'title': r.get('title',''),
|
| 518 |
+
'body': r.get('body',''),
|
| 519 |
+
'href': url,
|
| 520 |
+
'query_tag': query_tag,
|
| 521 |
+
'category': initial_cat,
|
| 522 |
+
'source_quality': 0.5,
|
| 523 |
+
'temporal_relevance': 0.1,
|
| 524 |
+
'combined_score': 0.0
|
| 525 |
+
}
|
| 526 |
+
self._score_result(res_data)
|
| 527 |
+
processed.append(res_data)
|
| 528 |
gaia_logger.debug(f"[RAG Proc] Batch: {len(processed)} new results from '{query_tag}'")
|
| 529 |
return processed
|
| 530 |
+
|
| 531 |
+
def _normalize_url(self, url: str) -> str:
|
| 532 |
+
return re.sub(r'^https?://(?:www\.)?', '', str(url)).rstrip('/') if url else ""
|
| 533 |
+
|
| 534 |
def _score_result(self, result: Dict):
|
| 535 |
+
url, body, title = result.get('href', ''), result.get('body', ''), result.get('title', '')
|
| 536 |
source_q = 0.5
|
| 537 |
+
if domain_match := re.search(r'https?://(?:www\.)?([^/]+)', url or ""):
|
| 538 |
+
source_q = self.trusted_sources.get(domain_match.group(1), 0.5)
|
| 539 |
+
result['source_quality'] = source_q
|
| 540 |
+
temporal_r = 0.1
|
| 541 |
+
text_combo = (str(title) + ' ' + str(body)).lower()
|
| 542 |
+
if any(k in text_combo for k in ['today', 'current', 'latest']) or re.search(r'\b\d+\s+hours?\s+ago', text_combo):
|
| 543 |
+
temporal_r = 0.9
|
| 544 |
+
elif re.search(self.date_pattern, text_combo):
|
| 545 |
+
temporal_r = 0.5
|
| 546 |
+
result['temporal_relevance'] = temporal_r
|
| 547 |
+
result['combined_score'] = (source_q * 0.6 + temporal_r * 0.4)
|
| 548 |
|
| 549 |
class ContentEnricher:
|
| 550 |
def __init__(self, config_dict: Dict):
|
| 551 |
self.enrich_config = config_dict.get('enrichment', {})
|
| 552 |
self._enabled = self.enrich_config.get('enabled', False) and bool(BeautifulSoup)
|
| 553 |
+
if not self._enabled:
|
| 554 |
+
gaia_logger.warning("RAG ContentEnricher disabled (BeautifulSoup missing or config).")
|
| 555 |
+
return
|
| 556 |
+
self._timeout = self.enrich_config.get('timeout', 10)
|
| 557 |
+
self._max_w = self.enrich_config.get('workers', 3)
|
| 558 |
+
self._min_l, self._max_l = self.enrich_config.get('min_text_length', 200), self.enrich_config.get('max_text_length', 8000)
|
| 559 |
+
self._skip_ext = tuple(self.enrich_config.get('skip_extensions', []))
|
| 560 |
self.cache = CacheManager(
|
| 561 |
+
ttl=config_dict.get('caching', {}).get('enrich_cache_ttl', 600),
|
| 562 |
+
max_size=config_dict.get('caching', {}).get('enrich_cache_size', 25),
|
| 563 |
+
name="EnrichCache"
|
| 564 |
+
)
|
| 565 |
gaia_logger.info(f"RAG ContentEnricher Initialized. Enabled: {self._enabled}")
|
| 566 |
+
|
| 567 |
+
def enrich_batch(self, results: List[Dict], force_refresh: bool = False) -> List[Dict]:
|
| 568 |
+
if not self._enabled or not results:
|
| 569 |
+
return results
|
| 570 |
updated_res = []
|
| 571 |
with ThreadPoolExecutor(max_workers=self._max_w) as executor:
|
| 572 |
future_map = {executor.submit(self._fetch_single, r, force_refresh): r for r in results}
|
| 573 |
+
for future in as_completed(future_map):
|
| 574 |
+
updated_res.append(future.result())
|
| 575 |
return updated_res
|
| 576 |
+
|
| 577 |
def _fetch_single(self, result: Dict, force_refresh: bool) -> Dict:
|
| 578 |
+
url = result.get('href')
|
| 579 |
+
result.setdefault('enriched', False)
|
| 580 |
+
result.setdefault('enrichment_failed', None)
|
| 581 |
+
result.setdefault('enrichment_skipped_type', None)
|
| 582 |
+
if not url:
|
| 583 |
+
result['enrichment_skipped_type'] = 'no_url'
|
| 584 |
+
return result
|
| 585 |
if not force_refresh and (cached := self.cache.get(url)) is not None:
|
| 586 |
+
if cached:
|
| 587 |
+
result.update(cached)
|
| 588 |
+
gaia_logger.debug(f"[Enrich] Cache hit: {url}")
|
| 589 |
+
return result
|
| 590 |
+
if url.lower().endswith(self._skip_ext):
|
| 591 |
+
result['enrichment_skipped_type'] = 'extension'
|
| 592 |
+
return result
|
| 593 |
try:
|
| 594 |
headers = {'User-Agent': 'Mozilla/5.0 GaiaRAGAgent/1.0'}
|
| 595 |
+
response = requests.get(url, headers=headers, timeout=self._timeout, allow_redirects=True)
|
| 596 |
+
response.raise_for_status()
|
| 597 |
+
if 'text/html' not in response.headers.get('Content-Type', '').lower():
|
| 598 |
+
result['enrichment_skipped_type'] = 'non-html'
|
| 599 |
+
return result
|
| 600 |
soup = BeautifulSoup(response.text, 'lxml')
|
| 601 |
+
for el_name in ["script", "style", "nav", "header", "footer", "aside", "form", "iframe", "img", "svg", ".ad", ".advertisement"]:
|
| 602 |
+
for el in soup.select(el_name):
|
| 603 |
+
el.decompose()
|
| 604 |
main_el = soup.select_one('article, main, [role="main"], .entry-content, .post-content, #content, #main') or soup.body
|
| 605 |
+
text = main_el.get_text(separator='\n', strip=True) if main_el else ""
|
| 606 |
text = re.sub(r'(\s*\n\s*){2,}', '\n\n', text).strip()
|
| 607 |
if len(text) >= self._min_l:
|
| 608 |
result['body'] = text[:self._max_l] + ("..." if len(text) > self._max_l else "")
|
| 609 |
+
result['enriched'] = True
|
| 610 |
+
self.cache.set(url, {'body': result['body'], 'enriched': True})
|
| 611 |
gaia_logger.info(f"[Enrich] OK: {url} ({len(result['body'])} chars).")
|
| 612 |
+
else:
|
| 613 |
+
result['enrichment_failed'] = 'too_short'
|
| 614 |
+
except Exception as e:
|
| 615 |
+
result['enrichment_failed'] = type(e).__name__
|
| 616 |
+
gaia_logger.warning(f"[Enrich] Fail: {url}: {e}", exc_info=False)
|
| 617 |
return result
|
| 618 |
|
| 619 |
class GeneralRAGPipeline:
|
| 620 |
def __init__(self, config_dict: Optional[Dict] = None):
|
| 621 |
self.config = config_dict if config_dict is not None else DEFAULT_RAG_CONFIG
|
| 622 |
self.search_client = CompositeSearchClient(self.config)
|
| 623 |
+
enrich_cfg = self.config.get('enrichment', {})
|
| 624 |
+
self.enricher = ContentEnricher(self.config) if enrich_cfg.get('enabled', False) and BeautifulSoup else None
|
| 625 |
+
if not self.enricher:
|
| 626 |
+
gaia_logger.info("RAG Content Enrichment is disabled (no BeautifulSoup or config).")
|
| 627 |
+
self.pipeline_cache = CacheManager(
|
| 628 |
+
ttl=self.config.get('caching', {}).get('analyzer_cache_ttl', 3600),
|
| 629 |
+
max_size=self.config.get('caching', {}).get('analyzer_cache_size', 30),
|
| 630 |
+
name="RAGPipelineCache"
|
| 631 |
+
)
|
| 632 |
gaia_logger.info("GeneralRAGPipeline initialized.")
|
| 633 |
+
|
| 634 |
def analyze(self, query: str, force_refresh: bool = False) -> List[Dict]:
|
| 635 |
+
q = query.strip()
|
| 636 |
+
if not q:
|
| 637 |
+
return []
|
| 638 |
+
cfg_res, cfg_search = self.config.get('results', {}), self.config.get('search', {})
|
| 639 |
+
total_lim, enrich_cnt = cfg_res.get('total_limit', 3), cfg_res.get('enrich_count', 2)
|
| 640 |
+
enrich_en = self.config.get('enrichment', {}).get('enabled', False) and bool(self.enricher)
|
| 641 |
+
max_r_pq = cfg_search.get('default_max_results', 3)
|
| 642 |
+
cache_key = (q, max_r_pq, total_lim, enrich_en, enrich_cnt)
|
| 643 |
+
if not force_refresh and (cached := self.pipeline_cache.get(cache_key)) is not None:
|
| 644 |
+
gaia_logger.info(f"[RAG Analyze] Cache hit: '{q[:50]}'")
|
| 645 |
+
return cached
|
| 646 |
+
if force_refresh:
|
| 647 |
+
self.search_client.cache.clear()
|
| 648 |
+
if self.enricher:
|
| 649 |
+
self.enricher.cache.clear()
|
| 650 |
+
all_res, res_proc = [], ResultProcessor(self.config)
|
| 651 |
+
staged_qs = GaiaQueryBuilder(q, self.config).get_queries()
|
| 652 |
+
for stage, qs_in_stage in staged_qs.items():
|
| 653 |
+
for query_s, cat in qs_in_stage:
|
| 654 |
+
if len(all_res) >= total_lim * 2:
|
| 655 |
+
break
|
| 656 |
gaia_logger.info(f"[RAG Analyze] Stage '{stage}': Search '{query_s[:70]}'")
|
| 657 |
+
s_res = self.search_client.search(query_s, max_results=max_r_pq, force_refresh=force_refresh)
|
| 658 |
all_res.extend(res_proc.process_batch(s_res or [], query_s, initial_cat=cat))
|
| 659 |
+
all_res.sort(key=lambda x: x.get('combined_score', 0), reverse=True)
|
| 660 |
if enrich_en and self.enricher and all_res:
|
| 661 |
+
to_enrich = [r for r in all_res[:enrich_cnt] if r.get('href')]
|
| 662 |
gaia_logger.info(f"[RAG Analyze] Enriching {len(to_enrich)} items...")
|
| 663 |
+
enriched_map = {
|
| 664 |
+
item['href']: item for item in self.enricher.enrich_batch(to_enrich, force_refresh=force_refresh)
|
| 665 |
+
if item.get('href')
|
| 666 |
+
}
|
| 667 |
+
temp_results = [enriched_map.get(r['href'], r) if r.get('href') else r for r in all_res]
|
| 668 |
+
all_res = temp_results
|
| 669 |
+
all_res.sort(key=lambda x: x.get('combined_score', 0), reverse=True)
|
| 670 |
+
final_results = all_res[:total_lim]
|
| 671 |
+
gaia_logger.info(f"[RAG Analyze] Done. {len(final_results)} results for '{q[:50]}'")
|
| 672 |
+
self.pipeline_cache.set(cache_key, final_results)
|
| 673 |
+
return final_results
|
| 674 |
|
| 675 |
class GaiaLevel1Agent:
|
| 676 |
def __init__(self, api_url: str = DEFAULT_API_URL):
|
|
|
|
| 684 |
model_name = 'gemini-1.0-pro'
|
| 685 |
self.llm_model = genai.GenerativeModel(model_name)
|
| 686 |
gaia_logger.info(f"Gemini LLM ('{model_name}') initialized.")
|
| 687 |
+
except Exception as e:
|
| 688 |
+
gaia_logger.error(f"Error initializing Gemini LLM: {e}", exc_info=True)
|
| 689 |
+
else:
|
| 690 |
+
gaia_logger.warning("Gemini LLM dependencies or API key missing.")
|
| 691 |
|
| 692 |
+
if not self.llm_model:
|
| 693 |
+
gaia_logger.warning("LLM (Gemini) unavailable. Limited capabilities.")
|
| 694 |
gaia_logger.info(f"GaiaLevel1Agent (RAG & FileProcessor) initialized. API: {self.api_url}")
|
| 695 |
|
| 696 |
@lru_cache(maxsize=32)
|
|
|
|
| 706 |
content_disposition = response.headers.get('Content-Disposition')
|
| 707 |
if content_disposition:
|
| 708 |
header_filename = FileProcessor._get_filename_from_url(content_disposition)
|
| 709 |
+
if header_filename != "unknown_file":
|
| 710 |
+
filename = header_filename
|
| 711 |
|
| 712 |
content_type = response.headers.get("Content-Type", "")
|
| 713 |
|
|
|
|
| 716 |
return processed_content
|
| 717 |
|
| 718 |
except requests.exceptions.HTTPError as e:
|
| 719 |
+
if e.response.status_code == 404:
|
| 720 |
+
gaia_logger.warning(f"File not found: {file_url}")
|
| 721 |
+
return None
|
| 722 |
gaia_logger.warning(f"HTTP error fetching file {task_id}: {e}")
|
| 723 |
except requests.exceptions.Timeout:
|
| 724 |
gaia_logger.warning(f"Timeout fetching file {task_id}")
|
| 725 |
+
if attempt < 1:
|
| 726 |
+
time.sleep(1)
|
| 727 |
except Exception as e:
|
| 728 |
gaia_logger.error(f"Error fetching/processing file {task_id} ({file_url}): {e}", exc_info=True)
|
| 729 |
+
if attempt < 1:
|
| 730 |
+
time.sleep(1)
|
| 731 |
return None
|
| 732 |
|
| 733 |
def _formulate_answer_with_llm(self, question: str, file_context: Optional[str], web_context: Optional[str]) -> str:
|
| 734 |
if not self.llm_model:
|
| 735 |
gaia_logger.warning("LLM model (Gemini) not available for answer formulation.")
|
| 736 |
+
if web_context:
|
| 737 |
+
return f"Based on web search (LLM unavailable): {web_context.splitlines()[0] if web_context.splitlines() else 'No specific snippet found.'}"
|
| 738 |
+
if file_context:
|
| 739 |
+
return f"Based on the provided document (LLM unavailable, first 100 chars of processed content): {file_context[:100]}..."
|
| 740 |
return "I am currently unable to process this request fully as the LLM is not available."
|
| 741 |
|
| 742 |
prompt_parts = [
|
|
|
|
| 757 |
truncated_web_context = web_context[:available_len_for_web] + "\n... (web context truncated)"
|
| 758 |
gaia_logger.info(f"Truncated web context from {len(web_context)} to {len(truncated_web_context)} chars.")
|
| 759 |
elif available_len_for_web <= 0 and web_context:
|
| 760 |
+
truncated_web_context = "\n...(web context omitted due to length constraints with file context)"
|
| 761 |
+
gaia_logger.warning("Web context completely omitted due to length constraints with file context.")
|
| 762 |
|
| 763 |
prompt_parts.extend(["\n\nContext from Web Search Results:\n---", truncated_web_context, "---"])
|
| 764 |
combined_context_len += len(truncated_web_context)
|
|
|
|
| 783 |
top_p=0.95,
|
| 784 |
max_output_tokens=2048
|
| 785 |
)
|
| 786 |
+
safety_set = [{"category": c, "threshold": "BLOCK_MEDIUM_AND_ABOVE"} for c in ["HARM_CATEGORY_HARASSMENT", "HARM_CATEGORY_HATE_SPEECH", "HARM_CATEGORY_SEXUALLY_EXPLICIT", "HARM_CATEGORY_DANGEROUS_CONTENT"]]
|
| 787 |
|
| 788 |
response = self.llm_model.generate_content(
|
| 789 |
final_prompt,
|
|
|
|
| 803 |
return llm_answer
|
| 804 |
except Exception as e:
|
| 805 |
gaia_logger.error(f"Error calling Gemini API: {e}", exc_info=True)
|
| 806 |
+
if "429" in str(e) or "ResourceExhausted" in str(type(e).__name__):
|
| 807 |
+
return "LLM temporarily unavailable (rate limit)."
|
| 808 |
return "Error generating LLM answer."
|
| 809 |
|
| 810 |
def __call__(self, question: str, task_id: Optional[str] = None) -> str:
|
|
|
|
| 829 |
if not any(kw in q_lower for kw in web_still_needed_kws):
|
| 830 |
needs_web = False
|
| 831 |
gaia_logger.info("Substantial file context present and question doesn't strongly imply web search. Skipping web search.")
|
| 832 |
+
if "don't search" in q_lower or "do not search" in q_lower:
|
| 833 |
+
needs_web = False
|
| 834 |
+
gaia_logger.info("Web search disabled by prompt.")
|
| 835 |
|
| 836 |
if needs_web:
|
| 837 |
search_q = question.replace("?", "").strip()
|
|
|
|
| 841 |
snippets = []
|
| 842 |
for i, res_item in enumerate(rag_res):
|
| 843 |
title, body, href = res_item.get('title','N/A'), res_item.get('body',''), res_item.get('href','#')
|
| 844 |
+
provider = res_item.get('query_tag','WebSearch')
|
| 845 |
prefix = "Enriched" if res_item.get('enriched') else "Snippet"
|
| 846 |
body_str = str(body) if body is not None else ""
|
| 847 |
body_prompt = body_str[:(MAX_CONTEXT_LENGTH_LLM // (len(rag_res) if rag_res else 1)) - 200] + "..." if len(body_str) > 2800 else body_str
|
| 848 |
snippets.append(f"Source [{i+1} - {provider}]: {title}\nURL: {href}\n{prefix}: {body_prompt}\n---")
|
| 849 |
web_ctx_str = "\n\n".join(snippets)
|
| 850 |
gaia_logger.info(f"RAG results: {len(web_ctx_str)} chars from {len(rag_res)} sources.")
|
| 851 |
+
else:
|
| 852 |
+
gaia_logger.warning("RAG pipeline yielded no web results for the query.")
|
| 853 |
|
| 854 |
answer = self._formulate_answer_with_llm(question, file_ctx_str, web_ctx_str)
|
| 855 |
gaia_logger.info(f"Final answer (first 70): {answer[:70]}...")
|
|
|
|
| 857 |
|
| 858 |
def run_and_submit_all(profile: gr.OAuthProfile | None):
|
| 859 |
space_id = os.getenv("SPACE_ID")
|
| 860 |
+
if profile:
|
| 861 |
+
username = f"{profile.username}"
|
| 862 |
+
gaia_logger.info(f"User logged in: {username}")
|
| 863 |
+
else:
|
| 864 |
+
gaia_logger.warning("User not logged in.")
|
| 865 |
+
return "Please Login to Hugging Face.", None
|
| 866 |
questions_url, submit_url = f"{DEFAULT_API_URL}/questions", f"{DEFAULT_API_URL}/submit"
|
| 867 |
try:
|
| 868 |
agent = GaiaLevel1Agent(api_url=DEFAULT_API_URL)
|
| 869 |
gaia_logger.info("GaiaLevel1Agent (RAG & FileProcessor) initialized for evaluation.")
|
| 870 |
+
except Exception as e:
|
| 871 |
+
gaia_logger.error(f"Error instantiating agent: {e}", exc_info=True)
|
| 872 |
+
return f"Error initializing agent: {e}", None
|
| 873 |
agent_code = f"https://huggingface.co/spaces/{space_id}/tree/main" if space_id else "Code link unavailable"
|
| 874 |
gaia_logger.info(f"Agent code link: {agent_code}")
|
| 875 |
try:
|
| 876 |
+
response = requests.get(questions_url, timeout=15)
|
| 877 |
+
response.raise_for_status()
|
| 878 |
+
questions_data = response.json()
|
| 879 |
+
if not questions_data or not isinstance(questions_data, list):
|
| 880 |
+
gaia_logger.error(f"Fetched questions list empty/invalid: {questions_data}")
|
| 881 |
+
return "Questions list empty/invalid.", None
|
| 882 |
gaia_logger.info(f"Fetched {len(questions_data)} questions.")
|
| 883 |
+
except Exception as e:
|
| 884 |
+
gaia_logger.error(f"Error fetching questions: {e}", exc_info=True)
|
| 885 |
+
return f"Error fetching questions: {e}", None
|
| 886 |
+
results_log, answers_payload = [], []
|
| 887 |
+
GEMINI_RPM_LIMIT = 60
|
| 888 |
+
sleep_llm = (60.0 / GEMINI_RPM_LIMIT) + 0.8 if GEMINI_RPM_LIMIT > 0 else 0.5
|
| 889 |
gaia_logger.info(f"LLM Rate: {GEMINI_RPM_LIMIT} RPM. Sleep ~{sleep_llm:.2f}s between LLM calls.")
|
| 890 |
gaia_logger.info(f"Running agent on {len(questions_data)} questions...")
|
| 891 |
+
for i, item in enumerate(questions_data):
|
| 892 |
+
task_id, q_text = item.get("task_id"), item.get("question")
|
| 893 |
+
if not task_id or q_text is None:
|
| 894 |
+
results_log.append({"Task ID": task_id, "Question": q_text, "Submitted Answer": "SKIPPED"})
|
| 895 |
+
continue
|
| 896 |
gaia_logger.info(f"Q {i+1}/{len(questions_data)} - Task: {task_id}")
|
| 897 |
try:
|
| 898 |
+
answer = agent(question=q_text, task_id=task_id)
|
| 899 |
+
answers_payload.append({"task_id": task_id, "submitted_answer": answer})
|
| 900 |
+
results_log.append({"Task ID": task_id, "Question": q_text, "Submitted Answer": answer})
|
| 901 |
except Exception as e:
|
| 902 |
+
gaia_logger.error(f"Error agent task {task_id}: {e}", exc_info=True)
|
| 903 |
+
results_log.append({"Task ID": task_id, "Question": q_text, "Submitted Answer": f"AGENT ERROR: {e}"})
|
| 904 |
+
if i < len(questions_data) - 1:
|
| 905 |
+
gaia_logger.info(f"Sleep {sleep_llm:.2f}s for LLM rate limit.")
|
| 906 |
+
time.sleep(sleep_llm)
|
| 907 |
+
if not answers_payload:
|
| 908 |
+
return "Agent produced no answers.", pd.DataFrame(results_log or [{"Info": "No questions processed"}])
|
| 909 |
+
submission = {"username": username.strip(), "agent_code": agent_code, "answers": answers_payload}
|
| 910 |
gaia_logger.info(f"Submitting {len(answers_payload)} answers for '{username}'...")
|
| 911 |
try:
|
| 912 |
+
response = requests.post(submit_url, json=submission, timeout=60)
|
| 913 |
+
response.raise_for_status()
|
| 914 |
+
result_data = response.json()
|
| 915 |
+
status = (f"Submission Successful!\nUser: {result_data.get('username')}\nScore: {result_data.get('score','N/A')}% "
|
| 916 |
+
f"({result_data.get('correct_count','?')}/{result_data.get('total_attempted','?')} correct)\n"
|
| 917 |
+
f"Msg: {result_data.get('message','No message.')}")
|
| 918 |
+
gaia_logger.info("Submission successful.")
|
| 919 |
+
return status, pd.DataFrame(results_log)
|
| 920 |
except requests.exceptions.HTTPError as e:
|
| 921 |
+
err_detail = f"Server: {e.response.status_code}. Detail: {e.response.text[:200]}"
|
| 922 |
+
gaia_logger.error(f"Submission Fail HTTP: {err_detail}", exc_info=False)
|
| 923 |
+
return f"Submission Failed: {err_detail}", pd.DataFrame(results_log)
|
| 924 |
+
except Exception as e:
|
| 925 |
+
gaia_logger.error(f"Submission Fail: {e}", exc_info=True)
|
| 926 |
+
return f"Submission Failed: {e}", pd.DataFrame(results_log)
|
| 927 |
|
| 928 |
with gr.Blocks(title="GAIA RAG Agent - Advanced") as demo:
|
| 929 |
gr.Markdown("# Gaia Level 1 Agent (RAG & FileProcessor) Evaluation Runner")
|
|
|
|
| 952 |
}
|
| 953 |
missing_keys = [key_name for key_name, key_val in required_env.items() if not key_val]
|
| 954 |
for key_name in required_env:
|
| 955 |
+
if required_env[key_name]:
|
| 956 |
+
print(f"✅ {key_name} found.")
|
| 957 |
+
else:
|
| 958 |
+
print(f"⚠️ WARNING: {key_name} not set.")
|
| 959 |
+
|
| 960 |
+
if not DDGS:
|
| 961 |
+
print("⚠️ WARNING: duckduckgo_search lib missing (for RAG DDG).")
|
| 962 |
+
else:
|
| 963 |
+
print("✅ duckduckgo_search lib found (for RAG DDG).")
|
| 964 |
+
if not BeautifulSoup:
|
| 965 |
+
print("⚠️ WARNING: BeautifulSoup lib missing (for RAG Enricher).")
|
| 966 |
+
else:
|
| 967 |
+
print("✅ BeautifulSoup lib found (for RAG Enricher).")
|
| 968 |
+
if not genai:
|
| 969 |
+
print("⚠️ WARNING: google-generativeai lib missing (for LLM).")
|
| 970 |
+
else:
|
| 971 |
+
print("✅ google-generativeai lib found (for LLM).")
|
| 972 |
|
| 973 |
if missing_keys:
|
| 974 |
print(f"\n--- PLEASE SET THE FOLLOWING MISSING ENVIRONMENT VARIABLES FOR FULL FUNCTIONALITY: {', '.join(missing_keys)} ---\n")
|