141 lines
4.5 KiB
Python
141 lines
4.5 KiB
Python
"""Responses preparation contracts, retaining the original historical wire format."""
|
|
|
|
from datetime import datetime
|
|
from typing import Annotated, Literal
|
|
from uuid import UUID
|
|
|
|
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
|
|
|
|
from app.schemas.catalog import Input
|
|
from app.schemas.indexing import ChunkProvenance
|
|
|
|
|
|
class CompletionOptions(Input):
|
|
"""Read-only options for Chat Completions snapshots created before Responses."""
|
|
|
|
model: str = Field(pattern=r"^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$")
|
|
output_tokens: int = Field(512, ge=1, le=4096)
|
|
token_parameter: Literal["max_completion_tokens", "max_tokens"] = "max_completion_tokens"
|
|
instruction_role: Literal["system", "developer"] = "system"
|
|
temperature: float | None = Field(None, ge=0, le=2)
|
|
|
|
|
|
class ResponsesOptions(Input):
|
|
protocol: Literal["responses"]
|
|
provider_profile: Literal["compatible", "openai", "deepseek", "bailian"] = "compatible"
|
|
model: str = Field(pattern=r"^[A-Za-z0-9][A-Za-z0-9._:/-]{0,127}$")
|
|
output_tokens: int = Field(512, ge=16, le=4096)
|
|
temperature: float | None = Field(None, ge=0, le=2)
|
|
|
|
@model_validator(mode="after")
|
|
def supported_temperature(self):
|
|
if self.provider_profile == "bailian" and self.temperature == 2:
|
|
raise ValueError("bailian temperature must be less than 2")
|
|
return self
|
|
|
|
|
|
class PrepareContext(Input):
|
|
retrieval_run_id: UUID
|
|
completion: ResponsesOptions
|
|
max_input_characters: int = Field(10000, ge=1024, le=24000)
|
|
max_evidence: int = Field(5, ge=1, le=10)
|
|
|
|
@field_validator("completion", mode="before")
|
|
@classmethod
|
|
def default_new_protocol(cls, value):
|
|
if isinstance(value, dict):
|
|
return {"protocol": "responses", **value}
|
|
return value
|
|
|
|
|
|
class Evidence(ChunkProvenance):
|
|
citation_id: str
|
|
chunk_id: UUID
|
|
chunk_ordinal: int
|
|
retrieval_rank: int
|
|
text: str
|
|
text_sha256: str
|
|
start_offset: int
|
|
end_offset: int
|
|
|
|
|
|
class ExcludedEvidence(BaseModel):
|
|
chunk_id: UUID
|
|
reason: Literal["overlap", "budget", "evidence_limit"]
|
|
|
|
|
|
class ContextRead(BaseModel):
|
|
id: UUID
|
|
kind: Literal["prepared_request"] = "prepared_request"
|
|
model_called: Literal[False] = False
|
|
status: Literal["prepared", "insufficient_evidence"]
|
|
reason: Literal["candidates_available", "no_candidates", "context_budget"]
|
|
retrieval_run_id: UUID
|
|
retrieval_mode: Literal["keyword_chunks", "vector", "hybrid"]
|
|
query: str
|
|
text_index_version_id: UUID
|
|
text_generation: int
|
|
catalog_revision: int
|
|
vector_version_id: UUID | None
|
|
vector_generation: int | None
|
|
corpus_fingerprint: str
|
|
prompt_version: Literal["nalati-evidence-v1"] = "nalati-evidence-v1"
|
|
completion: ResponsesOptions | CompletionOptions
|
|
max_input_characters: int
|
|
input_characters: int
|
|
max_evidence: int
|
|
evidence: list[Evidence]
|
|
excluded: list[ExcludedEvidence]
|
|
request_body: dict | None
|
|
request_sha256: str | None
|
|
created_at: datetime
|
|
|
|
|
|
class CompletionUsage(BaseModel):
|
|
prompt_tokens: int = Field(ge=0, strict=True)
|
|
completion_tokens: int = Field(ge=0, strict=True)
|
|
total_tokens: int = Field(ge=0, strict=True)
|
|
|
|
|
|
class ParsedCompletion(BaseModel):
|
|
status: Literal["answer_candidate", "insufficient_evidence", "incomplete", "refused"]
|
|
completion_id: str
|
|
model: str
|
|
answer: str | None
|
|
citations: list[str]
|
|
usage: CompletionUsage | None
|
|
|
|
|
|
TokenCount = Annotated[int, Field(ge=0, strict=True)]
|
|
|
|
|
|
class ResponseUsage(BaseModel):
|
|
model_config = ConfigDict(extra="allow")
|
|
|
|
input_tokens: TokenCount
|
|
output_tokens: TokenCount
|
|
total_tokens: TokenCount
|
|
input_tokens_details: dict[str, TokenCount] | None = None
|
|
output_tokens_details: dict[str, TokenCount] | None = None
|
|
|
|
@model_validator(mode="after")
|
|
def consistent_counts(self):
|
|
if self.total_tokens != self.input_tokens + self.output_tokens:
|
|
raise ValueError("inconsistent usage")
|
|
for details, count in [
|
|
(self.input_tokens_details, self.input_tokens),
|
|
(self.output_tokens_details, self.output_tokens),
|
|
]:
|
|
if details and any(value > count for value in details.values()):
|
|
raise ValueError("invalid usage details")
|
|
return self
|
|
|
|
|
|
class ParsedResponse(BaseModel):
|
|
status: Literal["answer_candidate", "insufficient_evidence", "incomplete", "refused", "failed"]
|
|
response_id: str
|
|
model: str
|
|
answer: str | None
|
|
citations: list[str]
|
|
usage: ResponseUsage | None
|