Files
PTSRaiAgent/app/llm/tasks.py
T
ElebobaandClaude Opus 5.5 924a51c483
CI / ruff · pytest (pull_request) Successful in 23s
feat(bot): LLM gateway with GigaChat and offline mock providers
Single entry point app/llm/gateway.py: extract_item, extract_checkin,
risk_classify, phrase, summarize_for_doctor. Two-level design:
LLMProvider (transport, GigaChat) and LLMTasks (PromptedTasks over a
provider, or rule-based MockTasks). Shared policy in the gateway:
assert_scrubbed backstop, confidence threshold 0.5, risk failure -> NONE,
forbidden-output filter with config/texts/phrases.yaml fallbacks.

Refs #7

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 15:00:41 +03:00

38 lines
1.8 KiB
Python

"""`LLMTasks` on top of any `LLMProvider`: builds prompts, delegates transport and validation."""
from app.core.types import ScrubbedText
from app.llm import prompts
from app.llm.base import LLMProvider
from app.llm.schemas import CheckinFacts, ItemScore, RiskVerdict
class PromptedTasks:
def __init__(self, provider: LLMProvider, name: str = "gigachat") -> None:
self._provider = provider
self.name = name
async def extract_item(
self, scale_item_id: str, item_text: str, max_score: int, answer: ScrubbedText
) -> ItemScore | None:
system = prompts.extract_item_system(scale_item_id, item_text, max_score)
result = await self._provider.complete_json(
system, answer, ItemScore, validation_context={"max_score": max_score}
)
if result is not None and result.item != scale_item_id:
# The model sometimes echoes the question instead of the id; the id is ours, not its.
result = result.model_copy(update={"item": scale_item_id})
return result
async def extract_checkin(self, text: ScrubbedText) -> CheckinFacts | None:
return await self._provider.complete_json(prompts.extract_checkin_system(), text, CheckinFacts)
async def risk_classify(self, text: ScrubbedText) -> RiskVerdict | None:
return await self._provider.complete_json(prompts.risk_system(), text, RiskVerdict)
async def phrase(self, intent: str, payload: ScrubbedText, base_text: str) -> str | None:
# `payload` already carries `base_text` (built by the gateway), so the model sees the meaning to keep.
return await self._provider.complete_text(prompts.phrase_system(intent), payload)
async def summarize_for_doctor(self, structured: ScrubbedText, base_text: str) -> str | None:
return await self._provider.complete_text(prompts.summary_system(), structured)