Skip to content

Commit 5cb387e

Browse files
authored
✨ Update the /v1/responses endpoint (#75)
* Remove the unused auto-refresh functionality and related imports. They are no longer needed since the underlying library issue has been resolved. * Enhance error handling in client initialization and message sending * Refactor link handling to extract file paths and simplify Google search links * Fix regex pattern for Google search link matching * Fix regex patterns for Markdown escaping, code fence and Google search link matching * Increase timeout value in configuration files from 60 to 120 seconds to better handle heavy tasks * Fix Image generation * Refactor tool handling to support standard and image generation tools separately * Fix: use "ascii" decoding for base64-encoded image data consistency * Fix: replace `running` with `_running` for internal client status checks * Refactor: replace direct `_running` access with `running()` method in client status checks * Extend models with new fields for annotations, reasoning, audio, log probabilities, and token details; adjust response handling accordingly. * Extend models with new fields (annotations, error), add `normalize_output_text` validator, rename `created` to `created_at`, and update response handling accordingly. * Extend response models to support tool choices, image output, and improved streaming of response items. Refactor image generation handling for consistency and add compatibility with output content. * Set default `text` value to an empty string for `ResponseOutputContent` and ensure consistent initialization in image output handling. * feat: Add /images endpoint with dedicated router and improved image management Add dedicated router for /images endpoint and refactor image handling logic for better modularity. Enhance temporary image management with secure naming, token verification, and cleanup functionality. * feat: Add token-based verification for image access * Refactor: rename image store directory to `ai_generated_images` for clarity * fix: Update create_response to use FastAPI Request object for base_url and refactor variable handling * fix: Correct attribute access in request_data handling within `chat.py` for tools, tool_choice, and streaming settings * fix: Save generated images to persistent storage * fix: Remove unused `output_image` type from `ResponseOutputContent` and update response handling for consistency * fix: Update image URL generation in chat response to use Markdown format for compatibility
1 parent ff54322 commit 5cb387e

File tree

7 files changed

+227
-70
lines changed

7 files changed

+227
-70
lines changed

app/main.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,12 @@
66

77
from .server.chat import router as chat_router
88
from .server.health import router as health_router
9-
from .server.middleware import add_cors_middleware, add_exception_handler
9+
from .server.images import router as images_router
10+
from .server.middleware import (
11+
add_cors_middleware,
12+
add_exception_handler,
13+
cleanup_expired_images,
14+
)
1015
from .services import GeminiClientPool, LMDBConversationStore
1116

1217
RETENTION_CLEANUP_INTERVAL_SECONDS = 6 * 60 * 60 # 6 hours
@@ -28,6 +33,7 @@ async def _run_retention_cleanup(stop_event: asyncio.Event) -> None:
2833
while not stop_event.is_set():
2934
try:
3035
store.cleanup_expired()
36+
cleanup_expired_images(store.retention_days)
3137
except Exception:
3238
logger.exception("LMDB retention cleanup task failed.")
3339

@@ -93,5 +99,6 @@ def create_app() -> FastAPI:
9399

94100
app.include_router(health_router, tags=["Health"])
95101
app.include_router(chat_router, tags=["Chat"])
102+
app.include_router(images_router, tags=["Images"])
96103

97104
return app

app/models/models.py

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from datetime import datetime
44
from typing import Any, Dict, List, Literal, Optional, Union
55

6-
from pydantic import BaseModel, Field
6+
from pydantic import BaseModel, Field, model_validator
77

88

99
class ContentItem(BaseModel):
@@ -12,7 +12,9 @@ class ContentItem(BaseModel):
1212
type: Literal["text", "image_url", "file", "input_audio"]
1313
text: Optional[str] = None
1414
image_url: Optional[Dict[str, str]] = None
15+
input_audio: Optional[Dict[str, Any]] = None
1516
file: Optional[Dict[str, str]] = None
17+
annotations: List[Dict[str, Any]] = Field(default_factory=list)
1618

1719

1820
class Message(BaseModel):
@@ -22,6 +24,10 @@ class Message(BaseModel):
2224
content: Union[str, List[ContentItem], None] = None
2325
name: Optional[str] = None
2426
tool_calls: Optional[List["ToolCall"]] = None
27+
refusal: Optional[str] = None
28+
reasoning_content: Optional[str] = None
29+
audio: Optional[Dict[str, Any]] = None
30+
annotations: List[Dict[str, Any]] = Field(default_factory=list)
2531

2632

2733
class Choice(BaseModel):
@@ -30,6 +36,7 @@ class Choice(BaseModel):
3036
index: int
3137
message: Message
3238
finish_reason: str
39+
logprobs: Optional[Dict[str, Any]] = None
3340

3441

3542
class FunctionCall(BaseModel):
@@ -81,6 +88,8 @@ class Usage(BaseModel):
8188
prompt_tokens: int
8289
completion_tokens: int
8390
total_tokens: int
91+
prompt_tokens_details: Optional[Dict[str, int]] = None
92+
completion_tokens_details: Optional[Dict[str, int]] = None
8493

8594

8695
class ModelData(BaseModel):
@@ -161,6 +170,15 @@ class ResponseInputContent(BaseModel):
161170
file_url: Optional[str] = None
162171
file_data: Optional[str] = None
163172
filename: Optional[str] = None
173+
annotations: List[Dict[str, Any]] = Field(default_factory=list)
174+
175+
@model_validator(mode="before")
176+
@classmethod
177+
def normalize_output_text(cls, data: Any) -> Any:
178+
"""Allow output_text (from previous turns) to be treated as input_text."""
179+
if isinstance(data, dict) and data.get("type") == "output_text":
180+
data["type"] = "input_text"
181+
return data
164182

165183

166184
class ResponseInputItem(BaseModel):
@@ -216,7 +234,8 @@ class ResponseOutputContent(BaseModel):
216234
"""Content item for Responses API output."""
217235

218236
type: Literal["output_text"]
219-
text: Optional[str] = None
237+
text: Optional[str] = ""
238+
annotations: List[Dict[str, Any]] = Field(default_factory=list)
220239

221240

222241
class ResponseOutputMessage(BaseModel):
@@ -254,20 +273,22 @@ class ResponseCreateResponse(BaseModel):
254273

255274
id: str
256275
object: Literal["response"] = "response"
257-
created: int
276+
created_at: int
258277
model: str
259278
output: List[Union[ResponseOutputMessage, ResponseImageGenerationCall, ResponseToolCall]]
260-
output_text: Optional[str] = None
261279
status: Literal[
262280
"in_progress",
263281
"completed",
264282
"failed",
265283
"incomplete",
284+
"cancelled",
266285
"requires_action",
267286
] = "completed"
287+
tool_choice: Optional[Union[str, ResponseToolChoice]] = None
288+
tools: Optional[List[Union[Tool, ResponseImageTool]]] = None
268289
usage: ResponseUsage
290+
error: Optional[Dict[str, Any]] = None
269291
metadata: Optional[Dict[str, Any]] = None
270-
system_fingerprint: Optional[str] = None
271292
input: Optional[Union[str, List[ResponseInputItem]]] = None
272293

273294

0 commit comments

Comments
 (0)