Some checks failed
Deploy Nexus / deploy (push) Has been cancelled
Phase 3 (Synthesis): - deepdive_synthesis.py: LLM-powered briefing generation - Supports OpenAI (gpt-4o-mini) and Anthropic (claude-3-haiku) - Fallback to keyword summary if LLM unavailable - Intelligence briefing format: Headlines, Deep Dive, Bottom Line Phase 4 (TTS): - TTS integration in orchestrator - Converts markdown to speech-friendly text - Configurable provider (openai/elevenlabs/piper) Phase 5 (Delivery): - Enhanced delivery.py with --text and --chat-id/--bot-token overrides - Supports text-only and audio+text delivery - Full Telegram Bot API integration Orchestrator: - Complete 5-phase pipeline - --dry-run mode for testing - State management in ~/the-nexus/deepdive_state/ - Error handling with fallbacks Progresses #830 to implementation-ready status
171 lines
6.0 KiB
Python
171 lines
6.0 KiB
Python
#!/usr/bin/env python3
|
|
"""deepdive_synthesis.py — Phase 3: LLM-powered intelligence briefing synthesis. Issue #830."""
|
|
|
|
import argparse
|
|
import json
|
|
import os
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
from typing import List, Dict
|
|
|
|
|
|
BRIEFING_PROMPT = """You are Deep Dive, an AI intelligence analyst for the Timmy Foundation fleet.
|
|
|
|
Your task: Synthesize the following research papers into a tight, actionable intelligence briefing for Alexander Whitestone, founder of Timmy.
|
|
|
|
CONTEXT:
|
|
- Timmy Foundation builds autonomous AI agents using the Hermes framework
|
|
- Focus areas: LLM architecture, tool use, RL training, agent systems
|
|
- Alexander prefers: Plain speech, evidence over vibes, concrete implications
|
|
|
|
SOURCES:
|
|
{sources}
|
|
|
|
OUTPUT FORMAT:
|
|
# Deep Dive Intelligence Brief — {date}
|
|
|
|
## Headlines (3 items)
|
|
For each top paper:
|
|
- **Title**: Paper name
|
|
- **Why It Matters**: One sentence on relevance to Hermes/Timmy
|
|
- **Key Insight**: The actionable takeaway
|
|
|
|
## Deep Dive (1 item)
|
|
Expand on the most relevant paper:
|
|
- Problem it solves
|
|
- Method/approach
|
|
- Implications for our agent work
|
|
- Suggested follow-up (if any)
|
|
|
|
## Bottom Line
|
|
3 bullets on what to know/do this week
|
|
|
|
Write in tight, professional intelligence style. No fluff."""
|
|
|
|
|
|
class SynthesisEngine:
|
|
def __init__(self, provider: str = None):
|
|
self.provider = provider or os.environ.get("DEEPDIVE_LLM_PROVIDER", "openai")
|
|
self.api_key = os.environ.get("OPENAI_API_KEY") or os.environ.get("ANTHROPIC_API_KEY")
|
|
|
|
def synthesize(self, items: List[Dict], date: str) -> str:
|
|
"""Generate briefing from ranked items."""
|
|
sources_text = self._format_sources(items)
|
|
prompt = BRIEFING_PROMPT.format(sources=sources_text, date=date)
|
|
|
|
if self.provider == "openai":
|
|
return self._call_openai(prompt)
|
|
elif self.provider == "anthropic":
|
|
return self._call_anthropic(prompt)
|
|
else:
|
|
return self._fallback_synthesis(items, date)
|
|
|
|
def _format_sources(self, items: List[Dict]) -> str:
|
|
lines = []
|
|
for i, item in enumerate(items[:10], 1):
|
|
lines.append(f"\n{i}. {item.get('title', 'Untitled')}")
|
|
lines.append(f" URL: {item.get('url', 'N/A')}")
|
|
lines.append(f" Abstract: {item.get('content', 'No abstract')[:500]}...")
|
|
lines.append(f" Relevance Score: {item.get('score', 0)}")
|
|
return "\n".join(lines)
|
|
|
|
def _call_openai(self, prompt: str) -> str:
|
|
"""Call OpenAI API for synthesis."""
|
|
try:
|
|
import openai
|
|
client = openai.OpenAI(api_key=self.api_key)
|
|
|
|
response = client.chat.completions.create(
|
|
model="gpt-4o-mini", # Cost-effective for daily briefings
|
|
messages=[
|
|
{"role": "system", "content": "You are an expert AI research analyst. Be concise and actionable."},
|
|
{"role": "user", "content": prompt}
|
|
],
|
|
temperature=0.3,
|
|
max_tokens=2000
|
|
)
|
|
return response.choices[0].message.content
|
|
except Exception as e:
|
|
print(f"[WARN] OpenAI synthesis failed: {e}")
|
|
return self._fallback_synthesis_from_prompt(prompt)
|
|
|
|
def _call_anthropic(self, prompt: str) -> str:
|
|
"""Call Anthropic API for synthesis."""
|
|
try:
|
|
import anthropic
|
|
client = anthropic.Anthropic(api_key=self.api_key)
|
|
|
|
response = client.messages.create(
|
|
model="claude-3-haiku-20240307", # Cost-effective
|
|
max_tokens=2000,
|
|
temperature=0.3,
|
|
system="You are an expert AI research analyst. Be concise and actionable.",
|
|
messages=[{"role": "user", "content": prompt}]
|
|
)
|
|
return response.content[0].text
|
|
except Exception as e:
|
|
print(f"[WARN] Anthropic synthesis failed: {e}")
|
|
return self._fallback_synthesis_from_prompt(prompt)
|
|
|
|
def _fallback_synthesis(self, items: List[Dict], date: str) -> str:
|
|
"""Generate basic briefing without LLM."""
|
|
lines = [
|
|
f"# Deep Dive Intelligence Brief — {date}",
|
|
"",
|
|
"## Headlines",
|
|
""
|
|
]
|
|
for i, item in enumerate(items[:3], 1):
|
|
lines.append(f"{i}. [{item.get('title', 'Untitled')}]({item.get('url', '')})")
|
|
lines.append(f" Relevance Score: {item.get('score', 0)}")
|
|
lines.append("")
|
|
|
|
lines.extend([
|
|
"## Bottom Line",
|
|
"",
|
|
f"- Reviewed {len(items)} papers from arXiv",
|
|
"- Run with LLM API key for full synthesis"
|
|
])
|
|
|
|
return "\n".join(lines)
|
|
|
|
def _fallback_synthesis_from_prompt(self, prompt: str) -> str:
|
|
"""Extract items from prompt and do basic synthesis."""
|
|
# Simple extraction for fallback
|
|
return "# Deep Dive\n\n[LLM synthesis unavailable - check API key]\n\n" + prompt[:1000]
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("--input", required=True, help="Path to ranked.json")
|
|
parser.add_argument("--output", required=True, help="Path to write briefing.md")
|
|
parser.add_argument("--date", default=None)
|
|
parser.add_argument("--provider", default=None)
|
|
args = parser.parse_args()
|
|
|
|
date = args.date or datetime.now().strftime("%Y-%m-%d")
|
|
|
|
# Load ranked items
|
|
ranked_data = json.loads(Path(args.input).read_text())
|
|
items = ranked_data.get("items", [])
|
|
|
|
if not items:
|
|
print("[ERROR] No items to synthesize")
|
|
return 1
|
|
|
|
print(f"[INFO] Synthesizing {len(items)} items...")
|
|
|
|
# Generate briefing
|
|
engine = SynthesisEngine(provider=args.provider)
|
|
briefing = engine.synthesize(items, date)
|
|
|
|
# Write output
|
|
Path(args.output).write_text(briefing)
|
|
print(f"[INFO] Briefing written to {args.output}")
|
|
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
exit(main())
|