Source code for cadrumo.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:`~llm.providers.base.ProviderCompletion`contract."""from__future__importannotationsfromtypingimportoverridefrompydanticimportBaseModel,Fieldfrom.....core.configimportload_settingsfrom.....core.config_supportimportLLMProviderfrom.....core.loggingimportget_loggerfrom.....core.modelsimportSTRICT_FROZEN_CONFIGfrom.baseimport(ProviderAdapter,ProviderCompletion,ProviderRequest,require_provider_response_item,)def_openai_chat_url()->str:"""Read the chat-completions URL at call time, never at import time. A module-scope ``Settings()`` resolves the storage root while the module imports, so an import-time refusal would kill any entrypoint whose import chain reaches this adapter instead of the one request that needs the URL. """returnload_settings().cadrumo_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=STRICT_FROZEN_CONFIGprompt_tokens:int=Field(ge=0)completion_tokens:int=Field(ge=0)class_OpenAIMessage(BaseModel):"""Single assistant message inside a Chat Completions choice."""model_config=STRICT_FROZEN_CONFIGcontent:str|None=Noneclass_OpenAIChoice(BaseModel):"""One choice within an OpenAI Chat Completions response."""model_config=STRICT_FROZEN_CONFIGmessage:_OpenAIMessageclass_OpenAIResponse(BaseModel):"""Top-level OpenAI Chat Completions response envelope. Attributes: id: Vendor-native response identifier (forwarded as :attr:`~llm.providers.base.ProviderCompletion.provider_request_id`). model: Model that served the request. choices: Returned chat completion choices. usage: Token accounting metadata. """model_config=STRICT_FROZEN_CONFIGid: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. """self._configure_api_key(api_key,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})parsed=awaitself._request_completion(request,endpoint=_openai_chat_url(),headers={"Authorization":f"Bearer {self._api_key}"},json={"model":request.model,"messages":messages,"max_tokens":request.max_tokens,"temperature":request.temperature,},response_model=_OpenAIResponse,logger=_logger,)choice=require_provider_response_item(parsed.choices,provider_name=LLMProvider.OPENAI.value,item_name="choices",)text=choice.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,)