File size: 3,601 Bytes
194e844 c597e9f 194e844 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 | """Strict request and response contracts for the LibBee agent."""
import time
from typing import Literal, Optional
from pydantic import BaseModel, ConfigDict, Field, field_validator
class ChatMessage(BaseModel):
model_config = ConfigDict(extra="forbid")
role: Literal["user", "assistant"]
content: str = Field(min_length=1, max_length=4000)
class SearchContextPayload(BaseModel):
model_config = ConfigDict(extra="ignore")
context_id: str = Field(min_length=1, max_length=80)
topic: str = Field(min_length=1, max_length=500)
display_topic: Optional[str] = Field(default=None, max_length=500)
intent: Literal["search_academic", "search_medical"] = "search_academic"
source: Literal["primo", "pubmed"] = "primo"
resource_type: Literal["articles", "books", "both"] = "articles"
query_field: Literal["any", "title", "creator", "sub"] = "any"
sort: Literal["rank", "date_d", "date_a"] = "rank"
peer_reviewed: bool = False
open_access: bool = False
year_from: Optional[str] = Field(default=None, pattern=r"^\d{4}$")
year_to: Optional[str] = Field(default=None, pattern=r"^\d{4}$")
ai_tool_query: Optional[str] = Field(default=None, max_length=1000)
primo_boolean_query: Optional[str] = Field(default=None, max_length=1500)
created_at: float = Field(default_factory=time.time)
clarification_needed: bool = False
clarification_message: Optional[str] = Field(default=None, max_length=500)
@field_validator("year_to")
@classmethod
def years_in_order(cls, value: Optional[str], info):
start = info.data.get("year_from")
if value and start and int(value) < int(start):
raise ValueError("year_to must not be earlier than year_from")
return value
class ClientStatePayload(BaseModel):
model_config = ConfigDict(extra="ignore")
session_id: Optional[str] = Field(default=None, pattern=r"^[A-Za-z0-9_-]{8,80}$")
active_search_context_id: Optional[str] = Field(default=None, max_length=80)
recent_search_contexts: list[SearchContextPayload] = Field(default_factory=list, max_length=5)
follow_up_action: Optional[str] = Field(default=None, max_length=80)
follow_up_context_id: Optional[str] = Field(default=None, max_length=80)
class AgentRequest(BaseModel):
model_config = ConfigDict(extra="forbid")
question: str = Field(min_length=1, max_length=2000)
model: Literal["gpt", "claude"] = "gpt"
history: list[ChatMessage] = Field(default_factory=list, max_length=12)
client_state: Optional[ClientStatePayload] = None
class AgentResponse(BaseModel):
model_config = ConfigDict(extra="ignore")
request_id: str = ""
answer: str
intent: str = "general"
tools_used: list[str] = Field(default_factory=list)
search_results: list[dict] = Field(default_factory=list)
sources: list[dict] = Field(default_factory=list)
model_used: str = "gpt"
response_time: float = 0
corrected_query: Optional[str] = None
natural_query: Optional[str] = None
database_query: Optional[str] = None
ask_librarian: Optional[bool] = None
is_follow_up: bool = False
follow_up_question: Optional[str] = None
follow_up_suggestions: list[dict] = Field(default_factory=list)
search_context: Optional[dict] = None
ai_tool_query: Optional[str] = None
primo_boolean_query: Optional[str] = None
primo_search_url: Optional[str] = None
primo_ai_url: Optional[str] = None
pubmed_search_url: Optional[str] = None
|