diff --git a/Jujutsu_Quants/app/adk/README.md b/Jujutsu_Quants/app/adk/README.md index 53c0c36..d4277fe 100644 --- a/Jujutsu_Quants/app/adk/README.md +++ b/Jujutsu_Quants/app/adk/README.md @@ -9,20 +9,4 @@ This directory contains the core agent framework for advanced news and market da - BiasDetector: Detects potential bias in news coverage. - NewsQAAgent: Answers questions from the news corpus. -## Hybrid RAG Summarizer (optional) - -- `HybridRAGSummarizer`: Fuses retrieval-ranked passages, market move regimes, and bias flags into a cited summary available under `rag_summary` in the `/api/v2/report` response. -- Enable/disable via config flag `ENABLE_HYBRID_RAG` (env var). Default: enabled. -- Minimal usage: - -```bash -uvicorn Jujutsu-Quants.app.adk.main:app --reload -curl -s -X POST http://localhost:8000/api/v2/report -H "Content-Type: application/json" -d '{ - "symbols": ["AAPL"], - "news_urls": ["https://example.com/news1"], - "question": "What are key drivers today?" -}' | jq -``` - - To add a new agent, create a new Python file in `agents/` and integrate it in `orchestrator.py`. diff --git a/Jujutsu_Quants/app/adk/agents/__init__.py b/Jujutsu_Quants/app/adk/agents/__init__.py index 5079a4a..991d58e 100644 --- a/Jujutsu_Quants/app/adk/agents/__init__.py +++ b/Jujutsu_Quants/app/adk/agents/__init__.py @@ -5,4 +5,3 @@ from .bias_detector import create_bias_detector from .news_qa_agent import create_news_qa_agent from .sentiment_agent import create_sentiment_agent -from .hybrid_rag_summarizer import create_hybrid_rag_summarizer diff --git a/Jujutsu_Quants/app/adk/agents/hybrid_rag_summarizer.py b/Jujutsu_Quants/app/adk/agents/hybrid_rag_summarizer.py deleted file mode 100644 index 8417a8b..0000000 --- a/Jujutsu_Quants/app/adk/agents/hybrid_rag_summarizer.py +++ /dev/null @@ -1,148 +0,0 @@ -from typing import List, Dict, Any, Tuple - -from app.config.adk_config import AGENT_CONFIGS -from .bias_detector import create_bias_detector - - -def _classify_regime(change_fraction: float) -> str: - """Classify price move regime from fractional change. Deterministic thresholds. - flat: |Δ| < 0.002, up: Δ >= 0.002, down: Δ <= -0.002 - """ - if change_fraction is None: - return "flat" - if change_fraction >= 0.002: - return "up" - if change_fraction <= -0.002: - return "down" - return "flat" - - -def _chunk_passages(text: str, window: int = 220, stride: int = 180) -> List[Tuple[int, int, str]]: - """Create overlapping fixed-size character windows to keep implementation tiny and deterministic. - Returns list of (start, end, passage_text). - """ - if not text: - return [] - passages: List[Tuple[int, int, str]] = [] - n = len(text) - i = 0 - while i < n: - end = min(i + window, n) - passages.append((i, end, text[i:end])) - if end == n: - break - i += stride - return passages - - -def _score_passage(passage: str, query_terms: List[str]) -> int: - passage_lower = passage.lower() - score = 0 - for t in query_terms: - if t and t in passage_lower: - score += 1 - return score - - -def create_hybrid_rag_summarizer(): - config = AGENT_CONFIGS.get("summarizer", {"name": "hybrid_rag_summarizer"}) - - class HybridRAGSummarizer: - def __init__(self): - self.name = config.get("name", "hybrid_rag_summarizer") - self.bias = create_bias_detector() - - def _compute_regimes(self, market_data: List[Dict[str, Any]]) -> List[Dict[str, Any]]: - regimes = [] - for item in market_data or []: - sym = item.get("symbol") - chg = item.get("price_change") - regimes.append({ - "symbol": sym, - "price_change": chg, - "regime": _classify_regime(chg if isinstance(chg, (int, float)) else 0.0), - }) - return regimes - - def _rank_passages(self, articles: List[Dict[str, Any]], question: str = None) -> List[Dict[str, Any]]: - query_terms: List[str] = [] - if question: - query_terms = [w for w in question.lower().split() if len(w) > 2] - else: - tokens: List[str] = [] - for a in articles or []: - tokens.extend([w.lower() for w in (a.get("title") or "").split() if len(w) > 3]) - seen = set() - for t in tokens: - if t not in seen: - query_terms.append(t) - seen.add(t) - if len(query_terms) >= 10: - break - - ranked: List[Dict[str, Any]] = [] - for art in articles or []: - content = art.get("content", "") - url = art.get("url") or art.get("source") or "" - for start, end, chunk in _chunk_passages(content): - score = _score_passage(chunk, query_terms) - if score > 0: - ranked.append({ - "source": url, - "start": start, - "end": end, - "text": chunk, - "score": score, - }) - ranked.sort(key=lambda x: x["score"], reverse=True) - return ranked[:3] - - def summarize(self, market_data: List[Dict[str, Any]], news_articles: List[Dict[str, Any]], question: str = None) -> Dict[str, Any]: - regimes = self._compute_regimes(market_data) - top_passages = self._rank_passages(news_articles, question) - bias_flags = self.bias.detect(news_articles) - key_points: List[str] = [] - for p in top_passages: - snippet = p["text"].strip().split(". ")[0].strip() - if snippet: - key_points.append(snippet[:120]) - if not key_points: - for a in news_articles or []: - t = (a.get("title") or "").strip() - if t: - key_points.append(t) - if len(key_points) >= 3: - break - moves = [] - for r in regimes: - sym = r.get("symbol") - reg = r.get("regime") - if sym: - moves.append(f"{sym}:{reg}") - move_sentence = ", ".join(moves) if moves else "market flat" - uncertainty: List[str] = [] - for b in bias_flags: - reason = b.get("reason") - ent = b.get("entity_focus") - if reason: - uncertainty.append(reason) - elif ent: - uncertainty.append(f"bias around {ent}") - if len(uncertainty) >= 4: - break - - citations = [{"source": p.get("source", ""), "start": p["start"], "end": p["end"]} for p in top_passages] - - headline = "; ".join(key_points[:2]) if key_points else "Key drivers mixed" - summary = f"{headline}. Moves: {move_sentence}." - - return { - "summary": summary, - "key_points": key_points[:5], - "uncertainty_factors": uncertainty, - "citations": citations, - } - - return HybridRAGSummarizer() - - diff --git a/Jujutsu_Quants/app/adk/orchestrator.py b/Jujutsu_Quants/app/adk/orchestrator.py index e0824c1..c64d255 100644 --- a/Jujutsu_Quants/app/adk/orchestrator.py +++ b/Jujutsu_Quants/app/adk/orchestrator.py @@ -50,7 +50,7 @@ def filter(self, record): from google.adk.agents import Agent from app.config.adk_config import ADK_CONFIG from app.adk.agents import ( - create_anomaly_detector, create_summarizer, create_diversity_analyzer, create_breaking_news_alert, create_bias_detector, create_news_qa_agent, create_sentiment_agent, create_hybrid_rag_summarizer + create_anomaly_detector, create_summarizer, create_diversity_analyzer, create_breaking_news_alert, create_bias_detector, create_news_qa_agent, create_sentiment_agent ) from app.adk.adk_agents import ( create_adk_anomaly_agent, create_adk_summarizer_agent, create_adk_diversity_agent, create_adk_breaking_agent, create_adk_bias_agent, create_adk_sentiment_agent, create_adk_qa_agent @@ -79,9 +79,6 @@ def _init_lightweight(self): self.bias_detector = create_bias_detector() self.news_qa_agent = create_news_qa_agent() self.sentiment_agent = create_sentiment_agent() - self.enable_hybrid_rag = ADK_CONFIG.get("enable_hybrid_rag_summarizer", False) - if self.enable_hybrid_rag: - self.hybrid_rag_summarizer = create_hybrid_rag_summarizer() def _init_adk(self): self.adk = { @@ -109,8 +106,6 @@ async def _process_lightweight(self, market_data, news_articles, question=None): results['sentiment'] = self.sentiment_agent.analyze(news_articles) if question: results['qa'] = self.news_qa_agent.answer(news_articles, question) - if getattr(self, 'enable_hybrid_rag', False): - results['rag_summary'] = self.hybrid_rag_summarizer.summarize(market_data, news_articles, question) return results async def _process_with_adk(self, market_data, news_articles, question=None): diff --git a/Jujutsu_Quants/app/config/adk_config.py b/Jujutsu_Quants/app/config/adk_config.py index fa1dafb..bb80680 100644 --- a/Jujutsu_Quants/app/config/adk_config.py +++ b/Jujutsu_Quants/app/config/adk_config.py @@ -9,8 +9,6 @@ "model": "gemini-2.0-flash", "use_vertex_ai": True, "adk_mode": os.getenv("ADK_MODE", "0") == "1", - # Optional hybrid RAG summarizer stage toggle - "enable_hybrid_rag_summarizer": os.getenv("ENABLE_HYBRID_RAG", "1") == "1", } # Agent Configuration diff --git a/Jujutsu_Quants/tests/test_hybrid_rag_summarizer.py b/Jujutsu_Quants/tests/test_hybrid_rag_summarizer.py deleted file mode 100644 index 1bb6450..0000000 --- a/Jujutsu_Quants/tests/test_hybrid_rag_summarizer.py +++ /dev/null @@ -1,36 +0,0 @@ -import pytest - -from app.adk.agents.hybrid_rag_summarizer import create_hybrid_rag_summarizer, _classify_regime - - -def test_regime_classification(): - assert _classify_regime(0.01) == "up" - assert _classify_regime(-0.02) == "down" - assert _classify_regime(0.0005) == "flat" - assert _classify_regime(None) == "flat" - - -def test_passage_fusion_and_citations(): - agent = create_hybrid_rag_summarizer() - market = [{"symbol": "AAPL", "price_change": 0.012}] - articles = [ - { - "title": "Apple iPhone demand improves as supply chain eases", - "url": "https://example.com/news1", - "content": ( - "Apple reported better iPhone demand this quarter. " - "Analysts noted supply chain easing in China factories. " - "Investors reacted positively to the update." - ), - } - ] - out = agent.summarize(market, articles, question="What are key drivers today?") - assert isinstance(out, dict) - assert "summary" in out and isinstance(out["summary"], str) - assert "key_points" in out and len(out["key_points"]) >= 1 - assert "uncertainty_factors" in out - assert "citations" in out and isinstance(out["citations"], list) - if out["citations"]: - c = out["citations"][0] - assert set(c.keys()) == {"source", "start", "end"} -