96 lines
2.6 KiB
Python
96 lines
2.6 KiB
Python
"""Chunk-level lexical and hybrid contracts; the item-level baseline stays distinct."""
|
|
|
|
from datetime import datetime
|
|
from typing import Literal
|
|
from uuid import UUID
|
|
|
|
from pydantic import BaseModel, Field, model_validator
|
|
|
|
from app.schemas.catalog import Category, Input
|
|
from app.schemas.indexing import IndexConfig
|
|
from app.schemas.retrieval import RetrievalConfig
|
|
from app.schemas.vectors import SpaceRead, VectorHit
|
|
|
|
|
|
class ChunkKeywordRequest(Input):
|
|
query: str = Field(min_length=1, max_length=500)
|
|
mode: Literal["keyword_chunks"] = "keyword_chunks"
|
|
top_k: int = Field(5, ge=1, le=20)
|
|
category: Category | None = None
|
|
|
|
|
|
class HybridRequest(ChunkKeywordRequest):
|
|
mode: Literal["hybrid"] = "hybrid"
|
|
candidate_k: int = Field(50, ge=1, le=200)
|
|
|
|
@model_validator(mode="after")
|
|
def sufficient_candidates(self):
|
|
if self.candidate_k < self.top_k:
|
|
raise ValueError("candidate_k 不得小于 top_k")
|
|
return self
|
|
|
|
|
|
class ChunkRetrievalConfig(BaseModel):
|
|
ranking_unit: Literal["chunk"] = "chunk"
|
|
lexical: RetrievalConfig
|
|
chunking: IndexConfig
|
|
tie_break: Literal["knowledge_id,chunk_ordinal,chunk_id"] = (
|
|
"knowledge_id,chunk_ordinal,chunk_id"
|
|
)
|
|
|
|
|
|
class HybridConfig(ChunkRetrievalConfig):
|
|
scorer: Literal["rrf-v1"] = "rrf-v1"
|
|
rrf_k: Literal[60] = 60
|
|
weights: tuple[Literal[1], Literal[1]] = (1, 1)
|
|
candidate_k: int
|
|
vector_scorer: Literal["pgvector-cosine-exact-v1"] = "pgvector-cosine-exact-v1"
|
|
space: SpaceRead
|
|
|
|
|
|
class RankedCandidate(BaseModel):
|
|
chunk_id: UUID
|
|
rank: int
|
|
score: float
|
|
|
|
|
|
class HybridHit(VectorHit):
|
|
lexical_rank: int | None
|
|
lexical_score: float | None
|
|
vector_rank: int | None
|
|
vector_score: float | None
|
|
lexical_contribution: float
|
|
vector_contribution: float
|
|
|
|
|
|
class ChunkKeywordResponse(BaseModel):
|
|
run_id: UUID
|
|
query: str
|
|
mode: Literal["keyword_chunks"] = "keyword_chunks"
|
|
status: Literal["ok", "no_evidence"]
|
|
top_k: int
|
|
category: str | None
|
|
query_terms: list[str]
|
|
text_index_version_id: UUID
|
|
text_generation: int
|
|
catalog_revision: int
|
|
corpus_fingerprint: str
|
|
corpus_size: int
|
|
total_hits: int
|
|
results: list[VectorHit]
|
|
elapsed_ms: float
|
|
created_at: datetime
|
|
config: ChunkRetrievalConfig
|
|
|
|
|
|
class HybridResponse(ChunkKeywordResponse):
|
|
mode: Literal["hybrid"] = "hybrid"
|
|
vector_version_id: UUID
|
|
generation: int
|
|
lexical_total_hits: int
|
|
vector_total_hits: int
|
|
lexical_candidates: list[RankedCandidate]
|
|
vector_candidates: list[RankedCandidate]
|
|
results: list[HybridHit]
|
|
config: HybridConfig
|