Coverage for src/lilbee/server/chat_completions_api/models.py: 100%
124 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-08-14 11:46 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-08-14 11:46 +0000
1"""Pydantic models for the OpenAI Chat Completions wire shapes."""
3from __future__ import annotations
5from enum import StrEnum
6from typing import Annotated, Any, Literal
8from pydantic import BaseModel, ConfigDict, Field
10# Wire layer reuses the provider-layer enum so the two can never drift.
11from lilbee.providers.base import FinishReason as FinishReason
14class ToolChoiceMode(StrEnum):
15 AUTO = "auto"
16 NONE = "none"
17 REQUIRED = "required"
20class CompletionsTextContent(BaseModel):
21 """Text part of a multi-part message content."""
23 type: Literal["text"]
24 text: str
27class CompletionsImageUrl(BaseModel):
28 url: str
29 detail: Literal["auto", "low", "high"] | None = None
32class CompletionsImageContent(BaseModel):
33 """Image part of a multi-part message content."""
35 type: Literal["image_url"]
36 image_url: CompletionsImageUrl
39CompletionsMessageContentPart = Annotated[
40 CompletionsTextContent | CompletionsImageContent,
41 Field(discriminator="type"),
42]
45class CompletionsToolCallFunction(BaseModel):
46 name: str
47 arguments: str = "{}"
50class CompletionsToolCall(BaseModel):
51 """Assistant-side tool_call entry inside a request message."""
53 id: str
54 type: Literal["function"] = "function"
55 function: CompletionsToolCallFunction
58class CompletionsMessage(BaseModel):
59 """One entry in the request ``messages`` list."""
61 role: Literal["system", "user", "assistant", "tool"]
62 content: str | list[CompletionsMessageContentPart] | None = None
63 name: str | None = None
64 tool_calls: list[CompletionsToolCall] | None = None
65 tool_call_id: str | None = None
68class CompletionsFunctionDef(BaseModel):
69 name: str
70 description: str | None = None
71 parameters: dict[str, Any] = Field(default_factory=dict)
74class CompletionsTool(BaseModel):
75 type: Literal["function"] = "function"
76 function: CompletionsFunctionDef
79class CompletionsToolChoiceFunction(BaseModel):
80 name: str
83class CompletionsNamedToolChoice(BaseModel):
84 """Explicit ``{type: "function", function: {name: ...}}`` tool_choice."""
86 type: Literal["function"]
87 function: CompletionsToolChoiceFunction
90class StreamOptions(BaseModel):
91 """OpenAI ``stream_options``. ``include_usage`` adds a final usage-only chunk."""
93 include_usage: bool = False
96class CompletionsRequest(BaseModel):
97 """Top-level ``POST /v1/chat/completions`` request body.
99 OpenAI parameters fall into three groups on this surface:
101 - Honoured: ``model``, ``messages``, ``tools``, ``tool_choice``,
102 ``temperature``, ``top_p``, ``top_k``, ``max_tokens``, ``stop``, ``seed``,
103 ``frequency_penalty``, ``presence_penalty``, ``stream``, ``stream_options``.
104 - Rejected with a 400: ``n`` greater than 1 (lilbee serves one choice).
105 - Accepted but ignored (``extra="allow"`` keeps them off the parsed model and
106 the route logs their keys at debug): ``n == 1``, ``response_format``,
107 ``logprobs``, ``top_logprobs``, and any other unrecognised field.
108 """
110 model_config = ConfigDict(extra="allow")
112 model: str = Field(min_length=1)
113 messages: list[CompletionsMessage] = Field(min_length=1)
114 tools: list[CompletionsTool] | None = None
115 tool_choice: ToolChoiceMode | CompletionsNamedToolChoice | None = None
116 temperature: float | None = Field(default=None, ge=0.0, le=2.0)
117 top_p: float | None = Field(default=None, ge=0.0, le=1.0)
118 top_k: int | None = Field(default=None, ge=1)
119 max_tokens: int | None = Field(default=None, ge=1)
120 seed: int | None = None
121 frequency_penalty: float | None = Field(default=None, ge=-2.0, le=2.0)
122 presence_penalty: float | None = Field(default=None, ge=-2.0, le=2.0)
123 n: int | None = Field(default=None, ge=1)
124 stop: str | list[str] | None = None
125 stream: bool = False
126 stream_options: StreamOptions | None = None
129class CompletionsResponseToolCallFunction(BaseModel):
130 name: str
131 arguments: str
134class CompletionsResponseToolCall(BaseModel):
135 id: str
136 type: Literal["function"] = "function"
137 function: CompletionsResponseToolCallFunction
140class CompletionsResponseMessage(BaseModel):
141 role: Literal["assistant"] = "assistant"
142 content: str | None = None
143 # A reasoning model's thinking, reported separately so ``content`` stays clean.
144 reasoning_content: str | None = None
145 tool_calls: list[CompletionsResponseToolCall] | None = None
148class CompletionsResponseChoice(BaseModel):
149 index: int = 0
150 message: CompletionsResponseMessage
151 finish_reason: FinishReason
154class CompletionsUsage(BaseModel):
155 prompt_tokens: int = 0
156 completion_tokens: int = 0
157 total_tokens: int = 0
160class CompletionsResponse(BaseModel):
161 """Non-streaming ``/v1/chat/completions`` response body."""
163 id: str
164 object: Literal["chat.completion"] = "chat.completion"
165 created: int
166 model: str
167 choices: list[CompletionsResponseChoice]
168 usage: CompletionsUsage
171class CompletionsStreamToolCallFunction(BaseModel):
172 name: str | None = None
173 arguments: str | None = None
176class CompletionsStreamToolCall(BaseModel):
177 index: int
178 id: str | None = None
179 type: Literal["function"] | None = None
180 function: CompletionsStreamToolCallFunction | None = None
183class CompletionsStreamDelta(BaseModel):
184 role: Literal["assistant"] | None = None
185 content: str | None = None
186 reasoning_content: str | None = None
187 tool_calls: list[CompletionsStreamToolCall] | None = None
190class CompletionsStreamChoice(BaseModel):
191 index: int = 0
192 delta: CompletionsStreamDelta
193 finish_reason: FinishReason | None = None
196class CompletionsStreamChunk(BaseModel):
197 """Single SSE frame from streaming ``/v1/chat/completions``.
199 The final frame (when usage is known) carries an empty ``choices`` list and a
200 populated ``usage`` block, matching OpenAI's ``stream_options.include_usage``.
201 """
203 id: str
204 object: Literal["chat.completion.chunk"] = "chat.completion.chunk"
205 created: int
206 model: str
207 choices: list[CompletionsStreamChoice]
208 usage: CompletionsUsage | None = None
211class ModelEntry(BaseModel):
212 id: str
213 object: Literal["model"] = "model"
214 owned_by: str = "lilbee"
215 created: int
216 context_window: int | None = None
217 """The context the active chat engine serves, so a client can trim history to
218 fit. None when the engine is not up yet or the window is unknown."""
221class ModelsListResponse(BaseModel):
222 """``GET /v1/models`` response envelope."""
224 object: Literal["list"] = "list"
225 data: list[ModelEntry]