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