"""Glue between the pipeline and the LLM layer.""" from __future__ import annotations import logging from us2kr.config import Settings from us2kr.llm.base import LLMError, LLMProvider from us2kr.llm.minimax import MiniMaxProvider from us2kr.models import MarketPayload, Report, Verification log = logging.getLogger(__name__) def build_provider(settings: Settings) -> LLMProvider: """Construct the configured LLM provider (MiniMax only for now).""" return MiniMaxProvider(settings) def _merge_gaps(payload: MarketPayload, report: Report) -> Report: """Guarantee `report.data_gaps` includes every payload gap (deduplicated, payload first).""" merged: list[str] = [] for gap in [*payload.data_gaps, *report.data_gaps]: if gap and gap not in merged: merged.append(gap) if merged != list(report.data_gaps): report = report.model_copy(update={"data_gaps": merged}) return report def summarize_payload(payload: MarketPayload, provider: LLMProvider) -> tuple[Report, str]: """Summarise via the provider and guarantee `report.data_gaps` includes every payload gap.""" report, model_used = provider.summarize(payload) report = _merge_gaps(payload, report) log.info("summary produced by %s (%d data gaps)", model_used, len(report.data_gaps)) return report, model_used def summarize_weekly_payload( payload: MarketPayload, week_sessions: list, provider: LLMProvider ) -> tuple[Report, str]: """Weekly-recap variant of summarize_payload.""" report, model_used = provider.summarize_weekly(payload, week_sessions) report = _merge_gaps(payload, report) log.info("weekly summary produced by %s (%d data gaps)", model_used, len(report.data_gaps)) return report, model_used def _consistent(verification: Verification) -> Verification: """Force `ok` to mean "no major issues" - verifier models are inconsistent about minor-only cases.""" ok = not verification.has_major return verification if verification.ok == ok else verification.model_copy(update={"ok": ok}) def verify_and_revise( payload: MarketPayload, report: Report, provider: LLMProvider, *, data_message: str | None = None, system_prompt: str | None = None, ) -> tuple[Report, Verification]: """Fact-check the report; on major issues ask for one revision and re-check it. `data_message`/`system_prompt` override the default daily-briefing serialisation and author persona (used by the weekly recap). Returns (possibly revised report, verification of the returned report). Raises LLMError only when the *initial* verification cannot be produced; a failed revision keeps the original report. """ raw_verification, model = provider.verify(payload, report, data_message) verification = _consistent(raw_verification) log.info( "verification by %s: ok=%s issues=%d (major=%s)", model, verification.ok, len(verification.issues), verification.has_major, ) if not verification.has_major: return report, verification for issue in verification.issues: log.warning("verification issue [%s] %s -> %s", issue.severity, issue.claim, issue.problem) try: revised, model = provider.revise( payload, report, verification.issues, system_prompt=system_prompt, data_message=data_message ) log.info("report revised by %s after verification issues", model) re_verification, model = provider.verify(payload, revised, data_message) re_verification = _consistent(re_verification) log.info( "re-verification by %s: ok=%s issues=%d", model, re_verification.ok, len(re_verification.issues) ) return revised, re_verification except LLMError as exc: log.warning("revision failed; keeping the original report and verdict: %s", exc) return report, verification