from __future__ import annotations from typing import Any, Literal from pydantic import BaseModel, ConfigDict, Field, model_validator ExternalQueryKind = Literal["weather", "news", "company", "model", "market", "generic_web", "unknown"] ExternalAnswerContract = Literal["direct_fact", "summary_with_sources", "compare_with_sources", "clarify"] ExternalRetrievalStatus = Literal["evidence_found", "verifier_blocked", "no_relevant_results", "retrieval_failed"] ExternalImpactStatus = Literal["direct_match", "indirect_only", "no_direct_impact"] RecommendationEvidenceMode = Literal["advisory", "verified", "needs_clarify"] class ExternalQueryPlan(BaseModel): model_config = ConfigDict(extra="ignore") plan_version: str = "v1" query_kind: ExternalQueryKind = "unknown" interaction_level: Literal["status", "recommendation", "draft", "execute", "clarify"] | None = None answer_contract: ExternalAnswerContract = "summary_with_sources" freshness_required: bool = False topic: str = "" subject_entities: list[str] = Field(default_factory=list) location_hint: str | None = None time_range_hint: str | None = None preferred_domains: list[str] = Field(default_factory=list) search_queries: list[str] = Field(default_factory=list) result_limit: int = Field(default=3, ge=1, le=10) requires_multi_source: bool = False clarify_on_ambiguity: bool = False fit_brief_summary: str = "" project_binding: int | None = None project_title: str | None = None project_scope_hint: str | None = None recommendation_goal: str | None = None project_object_anchors: list[str] = Field(default_factory=list) signal_source_objectives: list[str] = Field(default_factory=list) class ExternalEvidenceItem(BaseModel): model_config = ConfigDict(extra="ignore") source_type: str = "url" source_label: str = "" title: str = "" target_url: str = "" snippet: str = "" retrieved_at: str | None = None freshness_score: float = 0.0 authority_score: float = 0.0 grounded_fields: list[str] = Field(default_factory=list) raw_metadata: dict[str, Any] = Field(default_factory=dict) class ExternalAdapterResult(BaseModel): model_config = ConfigDict(extra="ignore") adapter_name: str status: str = "empty" evidence: list[ExternalEvidenceItem] = Field(default_factory=list) meta: dict[str, Any] = Field(default_factory=dict) class ExternalQueryExecution(BaseModel): model_config = ConfigDict(extra="ignore") plan: ExternalQueryPlan adapter_name: str evidence: list[ExternalEvidenceItem] = Field(default_factory=list) verifier: dict[str, Any] = Field(default_factory=dict) planner_meta: dict[str, Any] = Field(default_factory=dict) meta: dict[str, Any] = Field(default_factory=dict) def derive_retrieval_status(self) -> ExternalRetrievalStatus: verifier = dict(self.verifier or {}) attempts = list((self.meta or {}).get("adapter_attempts") or []) if self.evidence and bool(verifier.get("ok")): return "evidence_found" if self.evidence and not bool(verifier.get("ok")): return "verifier_blocked" if any( int(item.get("evidence_count") or 0) > 0 and not bool(item.get("verifier_ok")) for item in attempts if isinstance(item, dict) ): return "verifier_blocked" statuses = { str(item.get("status") or "").strip().lower() for item in attempts if isinstance(item, dict) and str(item.get("status") or "").strip() } if any(status in {"success", "empty"} for status in statuses): return "no_relevant_results" return "retrieval_failed" def to_context_packet(self, *, question: str) -> "ExternalContextPacket": verifier = dict(self.verifier or {}) uncertainties: list[str] = [] reason_code = str(verifier.get("reason_code") or "").strip() if reason_code: uncertainties.append(reason_code) for item in list(verifier.get("missing_fields") or []): normalized = str(item or "").strip() if normalized and normalized not in uncertainties: uncertainties.append(normalized) return ExternalContextPacket( question=str(question or "").strip(), plan=self.plan, adapter_attempts=list((self.meta or {}).get("adapter_attempts") or []), verified_evidence=list(self.evidence or []) if bool(verifier.get("ok")) else [], verifier=verifier, uncertainties=uncertainties, retrieval_status=self.derive_retrieval_status(), impact_status="no_direct_impact", affected_signal_sources=[], ) class ExternalContextPacket(BaseModel): model_config = ConfigDict(extra="ignore") question: str = "" plan: ExternalQueryPlan adapter_attempts: list[dict[str, Any]] = Field(default_factory=list) verified_evidence: list[ExternalEvidenceItem] = Field(default_factory=list) verifier: dict[str, Any] = Field(default_factory=dict) uncertainties: list[str] = Field(default_factory=list) retrieval_status: ExternalRetrievalStatus = "retrieval_failed" impact_status: ExternalImpactStatus = "no_direct_impact" affected_signal_sources: list[str] = Field(default_factory=list) class SignalSourceRecommendationCandidate(BaseModel): model_config = ConfigDict(extra="ignore") candidate_ref: str | None = None name: str = "" display_name: str = "" reason: str = "" business_value: str = "" why_now: str = "" source_origin: str = "recommendation" materialization_payload: dict[str, Any] = Field(default_factory=dict) @model_validator(mode="after") def _normalize_fields(self) -> "SignalSourceRecommendationCandidate": display_name = str(self.display_name or self.name).strip() name = str(self.name or display_name).strip() reason = str(self.reason or self.business_value or self.why_now).strip() business_value = str(self.business_value or reason or self.why_now).strip() why_now = str(self.why_now or reason or business_value).strip() payload = dict(self.materialization_payload or {}) if display_name and not payload.get("display_name"): payload["display_name"] = display_name if str(self.source_origin or "").strip() and not payload.get("source_origin"): payload["source_origin"] = str(self.source_origin or "").strip() if self.candidate_ref and not payload.get("candidate_ref"): payload["candidate_ref"] = str(self.candidate_ref) self.display_name = display_name self.name = name or display_name self.reason = reason self.business_value = business_value self.why_now = why_now self.materialization_payload = payload return self class SignalSourceRecommendationPacket(BaseModel): model_config = ConfigDict(extra="ignore") candidate_set_id: str = "" project_binding: int | None = None resource_role: Literal["signal_source"] = "signal_source" source_kind: Literal["external", "hybrid"] = "external" evidence_mode: RecommendationEvidenceMode = "advisory" candidates: list[SignalSourceRecommendationCandidate] = Field(default_factory=list) caveat: str = "" def normalize_signal_source_recommendation_payload(value: Any) -> dict[str, Any]: if not isinstance(value, dict) or not value: return {} try: return SignalSourceRecommendationPacket.model_validate(value).model_dump() except Exception: return {}