Source code for aeat.adapters.outbound.llm._providers.openai
"""OpenAI provider adapter for the LLM outbound subpackage.Speaks the OpenAI ``/v1/chat/completions`` HTTP API and adapts its responseinto the:class:`~adapters.outbound.llm._providers.base.ProviderCompletion`contract."""from__future__importannotationsfromtypingimportoverrideimporthttpxfrompydanticimportBaseModel,ConfigDict,Fieldfrom.....core.configimportSettingsfrom.....core.loggingimportget_loggerfrom.._errorsimportLLMConfigErrorfrom.._modelsimportLLMProviderfrom.baseimportProviderCompletion,ProviderRequest,_ProviderAdapter,check_http_error_OPENAI_CHAT_URL=Settings().aeat_llm_openai_chat_completions_url_logger=get_logger(__name__)class_OpenAIUsage(BaseModel):"""Token accounting reported by the OpenAI Chat Completions API. Attributes: prompt_tokens: Tokens charged for the prompt. completion_tokens: Tokens charged for the completion. """model_config=ConfigDict(strict=True,frozen=True)prompt_tokens:int=Field(ge=0)completion_tokens:int=Field(ge=0)class_OpenAIMessage(BaseModel):"""Single assistant message inside a Chat Completions choice."""model_config=ConfigDict(strict=True,frozen=True)content:str|None=Noneclass_OpenAIChoice(BaseModel):"""One choice within an OpenAI Chat Completions response."""model_config=ConfigDict(strict=True,frozen=True)message:_OpenAIMessageclass_OpenAIResponse(BaseModel):"""Top-level OpenAI Chat Completions response envelope. Attributes: id: Vendor-native response identifier (forwarded as :attr:`~adapters.outbound.llm._providers.base.ProviderCompletion.provider_request_id`). model: Model that served the request. choices: Returned chat completion choices. usage: Token accounting metadata. """model_config=ConfigDict(strict=True,frozen=True)id:strmodel:strchoices:tuple[_OpenAIChoice,...]usage:_OpenAIUsage
[docs]classOpenAIAdapter(_ProviderAdapter):"""Provider adapter that invokes the OpenAI Chat Completions API. A non-empty ``api_key`` is mandatory; the adapter refuses to construct without one because the underlying API rejects unauthenticated calls. """provider=LLMProvider.OPENAIdef__init__(self,api_key:str,timeout_s:int)->None:"""Initialize the adapter. Args: api_key: OpenAI API key. timeout_s: Per-request HTTP timeout in seconds. Raises: LLMConfigError: When ``api_key`` is empty. """ifnotapi_key:msg="AEAT_LLM_OPENAI_API_KEY must be set for the OpenAI provider."raiseLLMConfigError(msg)self._api_key=api_keyself._timeout_s=timeout_s
[docs]@overrideasyncdefcomplete(self,request:ProviderRequest)->ProviderCompletion:"""Execute a Chat Completions request against the OpenAI API. Args: request: Normalized provider request. Returns: A :class:`ProviderCompletion` with the first choice's trimmed text and reported token counts. Raises: LLMProviderError: When the API returns a 4xx or 5xx HTTP error status. """messages:list[dict[str,str]]=[]ifrequest.systemisnotNone:messages.append({"role":"system","content":request.system})messages.append({"role":"user","content":request.prompt})asyncwithhttpx.AsyncClient(timeout=self._timeout_s)asclient:response=awaitclient.post(_OPENAI_CHAT_URL,headers={"Authorization":f"Bearer {self._api_key}"},json={"model":request.model,"messages":messages,"max_tokens":request.max_tokens,"temperature":request.temperature,},)check_http_error(response,provider_name="OpenAI",model=request.model,logger=_logger)parsed=_OpenAIResponse.model_validate_json(response.text)text=parsed.choices[0].message.contentor""returnProviderCompletion(text=text.strip(),model=parsed.model,input_tokens=parsed.usage.prompt_tokens,output_tokens=parsed.usage.completion_tokens,provider_request_id=parsed.id,)