| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798 |
- import httpx
- from core_domain import ChatCompletionRequestContract, ChatCompletionResponseContract
- from core_shared import JSONValue
- from app.bootstrap.settings import ModelGatewayServiceSettings
- class ModelProviderClientError(Exception):
- pass
- class ModelProviderClient:
- def __init__(self, *, settings: ModelGatewayServiceSettings) -> None:
- self.settings = settings
- def create_chat_completion(
- self,
- payload: ChatCompletionRequestContract,
- ) -> ChatCompletionResponseContract:
- if payload.model is None:
- raise ModelProviderClientError("model is required for chat completion")
- request_payload = {
- "model": payload.model,
- "messages": [item.model_dump(mode="json") for item in payload.messages],
- }
- if payload.temperature is not None:
- request_payload["temperature"] = payload.temperature
- if payload.max_tokens is not None:
- request_payload["max_tokens"] = payload.max_tokens
- request_headers: dict[str, str] = {"content-type": "application/json"}
- if self.settings.provider_api_key:
- request_headers["authorization"] = f"Bearer {self.settings.provider_api_key}"
- try:
- with httpx.Client(timeout=60.0) as client:
- response = client.post(
- f"{self.settings.provider_base_url.rstrip('/')}/chat/completions",
- json=request_payload,
- headers=request_headers,
- )
- response.raise_for_status()
- except httpx.HTTPError as exc:
- raise ModelProviderClientError(f"model provider request failed: {exc}") from exc
- response_json = _coerce_json_dict(response.json())
- content = _extract_response_content(response_json)
- finish_reason = _extract_finish_reason(response_json)
- usage_json = _extract_usage_json(response_json)
- return ChatCompletionResponseContract(
- model=payload.model,
- content=content,
- finish_reason=finish_reason,
- usage_json=usage_json,
- raw_response_json=response_json,
- )
- def _coerce_json_dict(payload: JSONValue) -> dict[str, JSONValue]:
- if isinstance(payload, dict):
- return {str(key): value for key, value in payload.items()}
- return {}
- def _extract_response_content(payload: dict[str, JSONValue]) -> str:
- choices = payload.get("choices")
- if isinstance(choices, list) and choices:
- first_choice = choices[0]
- if isinstance(first_choice, dict):
- message = first_choice.get("message")
- if isinstance(message, dict):
- content = message.get("content")
- if isinstance(content, str):
- return content
- text = first_choice.get("text")
- if isinstance(text, str):
- return text
- return ""
- def _extract_finish_reason(payload: dict[str, JSONValue]) -> str | None:
- choices = payload.get("choices")
- if isinstance(choices, list) and choices:
- first_choice = choices[0]
- if isinstance(first_choice, dict):
- finish_reason = first_choice.get("finish_reason")
- if isinstance(finish_reason, str):
- return finish_reason
- return None
- def _extract_usage_json(payload: dict[str, JSONValue]) -> dict[str, JSONValue]:
- usage = payload.get("usage")
- if isinstance(usage, dict):
- return {str(key): value for key, value in usage.items()}
- return {}
|