Spaces:
Sleeping
Sleeping
claude's playground
feat(agent verf): Add multi-provider LLM infrastructure with dual-mode support
0a6602d | # ============================================================================ | |
| # agent verf - LangGraph Verification Workflow | |
| # Version: 0.1.0 | |
| # Last Updated: 2026-01-13 | |
| # | |
| # This is the main workflow definition using LangGraph. | |
| # | |
| # Workflow: | |
| # START → extraction → triage → evidence → synthesizer → END | |
| # | |
| # The workflow is a linear pipeline in the MVP. Future versions may add: | |
| # - Conditional routing based on triage decision | |
| # - Parallel evidence gathering | |
| # - Human-in-the-loop checkpoints | |
| # - Retry/fallback branches | |
| # | |
| # Usage: | |
| # receipt = await verify_content(url="https://twitter.com/...") | |
| # ============================================================================ | |
| import structlog | |
| from typing import Optional, Any | |
| from uuid import UUID | |
| from datetime import datetime | |
| from langgraph.graph import StateGraph, END | |
| from src.graph.state import VerificationState, create_initial_state | |
| from src.utils.llm import VerificationMode | |
| from src.graph.nodes import ( | |
| triage_node, | |
| extraction_node, | |
| evidence_node, | |
| synthesizer_node, | |
| ) | |
| from src.models.receipt import VerificationReceipt, AgentSignature | |
| logger = structlog.get_logger() | |
| # ============================================================================ | |
| # Graph Definition | |
| # ============================================================================ | |
| def create_verification_graph() -> StateGraph: | |
| """ | |
| Create the verification workflow graph. | |
| Node order: | |
| 1. extraction - Fetch content from URL | |
| 2. triage - Analyze and route to lens | |
| 3. evidence - Gather sources | |
| 4. synthesizer - Generate verdict | |
| Returns: | |
| Compiled StateGraph ready for execution | |
| """ | |
| # Create the graph with our state schema | |
| workflow = StateGraph(VerificationState) | |
| # Add nodes | |
| workflow.add_node("extraction", extraction_node) | |
| workflow.add_node("triage", triage_node) | |
| workflow.add_node("evidence", evidence_node) | |
| workflow.add_node("synthesizer", synthesizer_node) | |
| # Define the flow (linear for MVP) | |
| workflow.set_entry_point("extraction") | |
| workflow.add_edge("extraction", "triage") | |
| workflow.add_edge("triage", "evidence") | |
| workflow.add_edge("evidence", "synthesizer") | |
| workflow.add_edge("synthesizer", END) | |
| # Compile the graph | |
| return workflow.compile() | |
| # ============================================================================ | |
| # Cached Graph Instance | |
| # | |
| # The graph is expensive to compile, so we cache it. | |
| # ============================================================================ | |
| _compiled_graph = None | |
| def get_compiled_graph(): | |
| """Get or create the compiled verification graph.""" | |
| global _compiled_graph | |
| if _compiled_graph is None: | |
| logger.info("Compiling verification graph") | |
| _compiled_graph = create_verification_graph() | |
| return _compiled_graph | |
| # ============================================================================ | |
| # Main Entry Point | |
| # ============================================================================ | |
| async def verify_content( | |
| url: Optional[str] = None, | |
| text: Optional[str] = None, | |
| user_id: Optional[UUID] = None, | |
| platform: str = "web", | |
| scan_mode: str = "quick_scan", | |
| mode: VerificationMode = VerificationMode.FREE, | |
| ) -> VerificationReceipt: | |
| """ | |
| Verify content and return a VerificationReceipt. | |
| This is the main entry point for the verification system. | |
| Args: | |
| url: URL to verify (e.g., tweet, video, article) | |
| text: Raw text to verify (if no URL) | |
| user_id: User who requested verification | |
| platform: Request source (web, telegram, discord) | |
| scan_mode: "quick_scan" (fast) or "deep_dive" (thorough) | |
| mode: LLM provider mode (FREE or VENICE) | |
| Modes: | |
| - FREE: Uses Groq, Google, Cloudflare for triage; Cerebras, OpenRouter for synthesis | |
| - VENICE: Uses Venice.ai first, falls back to free providers | |
| Returns: | |
| VerificationReceipt with verdict, evidence, and metadata | |
| Raises: | |
| ValueError: If neither url nor text is provided | |
| Example: | |
| receipt = await verify_content( | |
| url="https://twitter.com/someone/status/123456", | |
| user_id=user.id, | |
| platform="telegram", | |
| mode=VerificationMode.VENICE, | |
| ) | |
| print(receipt.verdict.status) # "FALSE" | |
| print(receipt.dm_response) # Formatted response for DM | |
| """ | |
| # Validate input | |
| if not url and not text: | |
| raise ValueError("Either url or text must be provided") | |
| logger.info( | |
| "Starting verification", | |
| url=url, | |
| has_text=text is not None, | |
| user_id=str(user_id) if user_id else None, | |
| platform=platform, | |
| scan_mode=scan_mode, | |
| mode=mode.value, | |
| ) | |
| # Create initial state with mode | |
| initial_state = create_initial_state( | |
| url=url, | |
| text=text, | |
| user_id=user_id, | |
| platform=platform, | |
| scan_mode=scan_mode, | |
| mode=mode, | |
| ) | |
| # Get compiled graph | |
| graph = get_compiled_graph() | |
| # Run the workflow | |
| try: | |
| # Execute the graph | |
| final_state = await graph.ainvoke(initial_state) | |
| # Convert to VerificationReceipt | |
| receipt = _state_to_receipt(final_state) | |
| logger.info( | |
| "Verification complete", | |
| request_id=str(receipt.id), | |
| verdict=receipt.verdict.status, | |
| confidence=receipt.verdict.confidence, | |
| sources_checked=receipt.sources_checked, | |
| cost_usd=receipt.signature.estimated_cost_usd, | |
| ) | |
| return receipt | |
| except Exception as e: | |
| logger.error( | |
| "Verification workflow failed", | |
| error=str(e), | |
| url=url, | |
| ) | |
| raise | |
| def _state_to_receipt(state: VerificationState) -> VerificationReceipt: | |
| """ | |
| Convert final VerificationState to a VerificationReceipt. | |
| The state contains all the workflow data. | |
| The receipt is the user-facing output. | |
| """ | |
| from src.models.content import Content, ContentType, Platform | |
| # Handle case where content extraction failed | |
| if state.content: | |
| content = state.content | |
| else: | |
| # Create minimal content | |
| content = Content( | |
| source_url=state.request_url, | |
| platform=Platform.WEB, | |
| content_type=ContentType.TEXT_POST, | |
| text_content=state.request_text or "", | |
| extracted_at=datetime.utcnow(), | |
| extraction_method="none", | |
| ) | |
| # Handle case where verdict wasn't generated | |
| if state.verdict: | |
| verdict = state.verdict | |
| else: | |
| from src.models.verdict import Verdict, VerdictStatus | |
| verdict = Verdict( | |
| status=VerdictStatus.UNVERIFIED, | |
| confidence=0.0, | |
| summary="Verification did not complete", | |
| detailed_reasoning="The workflow did not produce a verdict", | |
| key_findings=[], | |
| primary_lens="general", | |
| ) | |
| # Build agent signature | |
| signature = AgentSignature( | |
| model_name=state.meta_models_used[0] if state.meta_models_used else "unknown", | |
| model_version=None, | |
| tools_used=state.meta_tools_used, | |
| processing_time_ms=state.processing_time_ms, | |
| estimated_cost_usd=state.meta_total_cost_usd, | |
| system_version="0.1.0", | |
| ) | |
| # Create receipt | |
| receipt = VerificationReceipt( | |
| id=state.request_id, | |
| created_at=state.meta_completed_at or datetime.utcnow(), | |
| user_id=state.request_user_id, | |
| request_platform=state.request_platform, | |
| scan_mode=state.request_scan_mode, | |
| content=content, | |
| verdict=verdict, | |
| evidence=state.evidence, | |
| sources_checked=state.sources_checked, | |
| ai_detection=None, # Not implemented in MVP | |
| ipfs_hash=None, # Will be set after IPFS upload | |
| signature=signature, | |
| ) | |
| return receipt | |
| # ============================================================================ | |
| # Quick Verification (Simplified API) | |
| # ============================================================================ | |
| async def quick_verify( | |
| url: str, | |
| mode: VerificationMode = VerificationMode.FREE, | |
| ) -> dict[str, Any]: | |
| """ | |
| Quick verification returning just the essential info. | |
| This is a convenience wrapper for simple use cases. | |
| Args: | |
| url: URL to verify | |
| mode: LLM provider mode (FREE or VENICE) | |
| Returns: | |
| Dict with verdict, confidence, and summary | |
| Example: | |
| result = await quick_verify("https://twitter.com/...") | |
| print(result["verdict"]) # "FALSE" | |
| print(result["confidence"]) # 0.92 | |
| print(result["summary"]) # "This claim is false because..." | |
| """ | |
| receipt = await verify_content(url=url, mode=mode) | |
| return { | |
| "verdict": receipt.verdict.status, | |
| "confidence": receipt.verdict.confidence, | |
| "summary": receipt.verdict.summary, | |
| "report_url": receipt.report_url, | |
| "sources_checked": receipt.sources_checked, | |
| "mode": mode.value, | |
| } | |