Files

118 lines
4.8 KiB
Python

"""
Responses API 路由 (OpenAI compatible).
"""
from typing import Any, Dict, List, Optional, Union
from fastapi import APIRouter
from fastapi.responses import JSONResponse, StreamingResponse
from pydantic import BaseModel, Field
from app.core.exceptions import ValidationException
from app.services.grok.services.responses import ResponsesService
router = APIRouter(tags=["Responses"])
class ResponseCreateRequest(BaseModel):
model: str = Field(..., description="Model name")
input: Optional[Any] = Field(None, description="Input content")
instructions: Optional[str] = Field(None, description="System instructions")
stream: Optional[bool] = Field(False, description="Stream response")
max_output_tokens: Optional[int] = Field(None, description="Max output tokens")
temperature: Optional[float] = Field(None, description="Sampling temperature")
top_p: Optional[float] = Field(None, description="Nucleus sampling")
tools: Optional[List[Dict[str, Any]]] = Field(None, description="Tool definitions")
tool_choice: Optional[Union[str, Dict[str, Any]]] = Field(None, description="Tool choice")
parallel_tool_calls: Optional[bool] = Field(True, description="Allow parallel tool calls")
reasoning: Optional[Dict[str, Any]] = Field(None, description="Reasoning options")
metadata: Optional[Dict[str, Any]] = Field(None, description="Metadata")
user: Optional[str] = Field(None, description="User identifier")
store: Optional[bool] = Field(None, description="Store response")
previous_response_id: Optional[str] = Field(None, description="Previous response id")
truncation: Optional[str] = Field(None, description="Truncation behavior")
provider_options: Optional[Dict[str, Any]] = Field(None, description="Provider-specific options")
providerOptions: Optional[Dict[str, Any]] = Field(None, description="Provider-specific options (camelCase)")
class Config:
extra = "allow"
@router.post("/responses")
async def create_response(request: ResponseCreateRequest):
if not request.model:
raise ValidationException(message="model is required", param="model", code="invalid_request_error")
if request.input is None:
raise ValidationException(message="input is required", param="input", code="invalid_request_error")
reasoning_effort = None
if isinstance(request.reasoning, dict):
reasoning_effort = request.reasoning.get("effort") or request.reasoning.get("reasoning_effort")
if not reasoning_effort:
provider_options = request.provider_options or request.providerOptions
if isinstance(provider_options, dict):
for key in ("reasoning_effort", "reasoningEffort"):
val = provider_options.get(key)
if isinstance(val, str) and val.strip():
reasoning_effort = val.strip().lower()
break
if not reasoning_effort:
for _, conf in provider_options.items():
if not isinstance(conf, dict):
continue
for key in ("reasoning_effort", "reasoningEffort"):
val = conf.get(key)
if isinstance(val, str) and val.strip():
reasoning_effort = val.strip().lower()
break
if reasoning_effort:
break
try:
result = await ResponsesService.create(
model=request.model,
input_value=request.input,
instructions=request.instructions,
stream=bool(request.stream),
temperature=request.temperature,
top_p=request.top_p,
tools=request.tools,
tool_choice=request.tool_choice,
parallel_tool_calls=request.parallel_tool_calls,
reasoning_effort=reasoning_effort,
max_output_tokens=request.max_output_tokens,
metadata=request.metadata,
user=request.user,
store=request.store,
previous_response_id=request.previous_response_id,
truncation=request.truncation,
)
if request.stream:
return StreamingResponse(
result,
media_type="text/event-stream",
headers={"Cache-Control": "no-cache", "Connection": "keep-alive"},
)
return JSONResponse(content=result)
except ValidationException:
raise
except Exception as e:
return JSONResponse(
status_code=500,
content={
"error": {
"message": str(e) or "responses request failed",
"type": "server_error",
"param": None,
"code": "responses_internal_error",
}
},
)
__all__ = ["router"]