FastAPI + Celery + Next.js + Postgres/Redis app with company monitoring, source collection, LLM-based change analysis, enrichment, and account security (Turnstile, escalating lockout, email verification).
55 lines
2.0 KiB
Python
55 lines
2.0 KiB
Python
"""Task C: cross-source synthesis. Combines related signals from multiple
|
|
documents/changes into a conclusion (e.g. "hiring battery engineers" +
|
|
"filed a battery patent" + "announced a facility expansion" = "possible
|
|
battery manufacturing initiative"), always with alternative explanations and
|
|
an explicit accounting of what's still unknown.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from pydantic import BaseModel, Field
|
|
|
|
from app.analysis.llm.base import LLMProvider
|
|
from app.prompts.base import build_user_prompt
|
|
|
|
SYSTEM_PROMPT = (
|
|
"You are a competitive intelligence analyst. Given a set of independently-extracted "
|
|
"signals about a company, identify conclusions that multiple signals point toward "
|
|
"together. Only draw a conclusion when the evidence actually supports it - state your "
|
|
"confidence honestly, list plausible alternative explanations, and note what evidence "
|
|
"would be needed to be more certain. Do not synthesize a conclusion from a single signal."
|
|
)
|
|
|
|
|
|
class SynthesizedConclusion(BaseModel):
|
|
conclusion: str
|
|
evidence_summary: list[str] = Field(default_factory=list)
|
|
source_count: int = Field(ge=0)
|
|
confidence: float = Field(ge=0.0, le=1.0)
|
|
alternative_explanations: list[str] = Field(default_factory=list)
|
|
missing_information: list[str] = Field(default_factory=list)
|
|
|
|
|
|
class SynthesisResult(BaseModel):
|
|
conclusions: list[SynthesizedConclusion] = Field(default_factory=list)
|
|
|
|
|
|
async def synthesize_signals(
|
|
llm: LLMProvider,
|
|
*,
|
|
company_name: str,
|
|
monitoring_focus: str | None,
|
|
signals: list[dict],
|
|
) -> SynthesisResult:
|
|
evidence = {
|
|
"company_name": company_name,
|
|
"monitoring_focus": monitoring_focus,
|
|
"signals": signals,
|
|
}
|
|
user_prompt = build_user_prompt(
|
|
"Identify conclusions supported by two or more of these signals together. Do not "
|
|
"synthesize from a single signal alone.",
|
|
evidence,
|
|
)
|
|
return await llm.generate_structured(SYSTEM_PROMPT, user_prompt, SynthesisResult)
|