44import itertools
55import json
66import warnings
7- from collections .abc import AsyncIterable , AsyncIterator , Sequence
7+ from collections .abc import AsyncIterable , AsyncIterator , Iterable , Sequence
88from contextlib import asynccontextmanager
99from dataclasses import dataclass , field , replace
1010from datetime import datetime
6262 ChatCompletionContentPartInputAudioParam ,
6363 ChatCompletionContentPartParam ,
6464 ChatCompletionContentPartTextParam ,
65+ chat_completion ,
66+ chat_completion_chunk ,
6567 )
66- from openai .types .chat .chat_completion_chunk import Choice
6768 from openai .types .chat .chat_completion_content_part_image_param import ImageURL
6869 from openai .types .chat .chat_completion_content_part_input_audio_param import InputAudio
6970 from openai .types .chat .chat_completion_content_part_param import File , FileFile
@@ -543,28 +544,7 @@ def _process_provider_details(self, response: chat.ChatCompletion) -> dict[str,
543544
544545 This method may be overridden by subclasses of `OpenAIChatModel` to apply custom mappings.
545546 """
546- choice = response .choices [0 ]
547- provider_details : dict [str , Any ] = {}
548-
549- # Add logprobs to vendor_details if available
550- if choice .logprobs is not None and choice .logprobs .content :
551- # Convert logprobs to a serializable format
552- provider_details ['logprobs' ] = [
553- {
554- 'token' : lp .token ,
555- 'bytes' : lp .bytes ,
556- 'logprob' : lp .logprob ,
557- 'top_logprobs' : [
558- {'token' : tlp .token , 'bytes' : tlp .bytes , 'logprob' : tlp .logprob } for tlp in lp .top_logprobs
559- ],
560- }
561- for lp in choice .logprobs .content
562- ]
563-
564- raw_finish_reason = choice .finish_reason
565- provider_details ['finish_reason' ] = raw_finish_reason
566-
567- return provider_details
547+ return _map_provider_details (response .choices [0 ])
568548
569549 def _process_response (self , response : chat .ChatCompletion | str ) -> ModelResponse :
570550 """Process a non-streamed response, and prepare a message to return."""
@@ -618,7 +598,7 @@ def _process_response(self, response: chat.ChatCompletion | str) -> ModelRespons
618598
619599 return ModelResponse (
620600 parts = items ,
621- usage = _map_usage (response , self . _provider . name , self . _provider . base_url , self . _model_name ),
601+ usage = self . _map_usage (response ),
622602 model_name = response .model ,
623603 timestamp = timestamp ,
624604 provider_details = self ._process_provider_details (response ),
@@ -680,6 +660,9 @@ def _streamed_response_cls(self) -> type[OpenAIStreamedResponse]:
680660 """
681661 return OpenAIStreamedResponse
682662
663+ def _map_usage (self , response : chat .ChatCompletion ) -> usage .RequestUsage :
664+ return _map_usage (response , self ._provider .name , self ._provider .base_url , self ._model_name )
665+
683666 def _get_tools (self , model_request_parameters : ModelRequestParameters ) -> list [chat .ChatCompletionToolParam ]:
684667 return [self ._map_tool_definition (r ) for r in model_request_parameters .tool_defs .values ()]
685668
@@ -1767,7 +1750,7 @@ async def _get_event_iterator(self) -> AsyncIterator[ModelResponseStreamEvent]:
17671750 for event in self ._map_part_delta (choice ):
17681751 yield event
17691752
1770- def _validate_response (self ):
1753+ def _validate_response (self ) -> AsyncIterable [ ChatCompletionChunk ] :
17711754 """Hook that validates incoming chunks.
17721755
17731756 This method may be overridden by subclasses of `OpenAIStreamedResponse` to apply custom chunk validations.
@@ -1776,7 +1759,7 @@ def _validate_response(self):
17761759 """
17771760 return self ._response
17781761
1779- def _map_part_delta (self , choice : Choice ):
1762+ def _map_part_delta (self , choice : chat_completion_chunk . Choice ) -> Iterable [ ModelResponseStreamEvent ] :
17801763 """Hook that determines the sequence of mappings that will be called to produce events.
17811764
17821765 This method may be overridden by subclasses of `OpenAIStreamResponse` to customize the mapping.
@@ -1785,7 +1768,7 @@ def _map_part_delta(self, choice: Choice):
17851768 self ._map_thinking_delta (choice ), self ._map_text_delta (choice ), self ._map_tool_call_delta (choice )
17861769 )
17871770
1788- def _map_thinking_delta (self , choice : Choice ):
1771+ def _map_thinking_delta (self , choice : chat_completion_chunk . Choice ) -> Iterable [ ModelResponseStreamEvent ] :
17891772 """Hook that maps thinking delta content to events.
17901773
17911774 This method may be overridden by subclasses of `OpenAIStreamResponse` to customize the mapping.
@@ -1811,7 +1794,7 @@ def _map_thinking_delta(self, choice: Choice):
18111794 provider_name = self .provider_name ,
18121795 )
18131796
1814- def _map_text_delta (self , choice : Choice ):
1797+ def _map_text_delta (self , choice : chat_completion_chunk . Choice ) -> Iterable [ ModelResponseStreamEvent ] :
18151798 """Hook that maps text delta content to events.
18161799
18171800 This method may be overridden by subclasses of `OpenAIStreamResponse` to customize the mapping.
@@ -1831,7 +1814,7 @@ def _map_text_delta(self, choice: Choice):
18311814 maybe_event .part .provider_name = self .provider_name
18321815 yield maybe_event
18331816
1834- def _map_tool_call_delta (self , choice : Choice ):
1817+ def _map_tool_call_delta (self , choice : chat_completion_chunk . Choice ) -> Iterable [ ModelResponseStreamEvent ] :
18351818 """Hook that maps tool call delta content to events.
18361819
18371820 This method may be overridden by subclasses of `OpenAIStreamResponse` to customize the mapping.
@@ -1851,11 +1834,9 @@ def _map_provider_details(self, chunk: ChatCompletionChunk) -> dict[str, str] |
18511834
18521835 This method may be overridden by subclasses of `OpenAIStreamResponse` to customize the provider details.
18531836 """
1854- choice = chunk .choices [0 ]
1855- if raw_finish_reason := choice .finish_reason :
1856- return {'finish_reason' : raw_finish_reason }
1837+ return _map_provider_details (chunk .choices [0 ])
18571838
1858- def _map_usage (self , response : ChatCompletionChunk ):
1839+ def _map_usage (self , response : ChatCompletionChunk ) -> usage . RequestUsage :
18591840 return _map_usage (response , self ._provider_name , self ._provider_url , self ._model_name )
18601841
18611842 def _map_finish_reason (
@@ -2177,7 +2158,7 @@ async def _get_event_iterator(self) -> AsyncIterator[ModelResponseStreamEvent]:
21772158 UserWarning ,
21782159 )
21792160
2180- def _map_usage (self , response : responses .Response ):
2161+ def _map_usage (self , response : responses .Response ) -> usage . RequestUsage :
21812162 return _map_usage (response , self ._provider_name , self ._provider_url , self ._model_name )
21822163
21832164 @property
@@ -2237,6 +2218,32 @@ def _map_usage(
22372218 )
22382219
22392220
2221+ def _map_provider_details (
2222+ choice : chat_completion_chunk .Choice | chat_completion .Choice ,
2223+ ) -> dict [str , Any ]:
2224+ provider_details : dict [str , Any ] = {}
2225+
2226+ # Add logprobs to vendor_details if available
2227+ if choice .logprobs is not None and choice .logprobs .content :
2228+ # Convert logprobs to a serializable format
2229+ provider_details ['logprobs' ] = [
2230+ {
2231+ 'token' : lp .token ,
2232+ 'bytes' : lp .bytes ,
2233+ 'logprob' : lp .logprob ,
2234+ 'top_logprobs' : [
2235+ {'token' : tlp .token , 'bytes' : tlp .bytes , 'logprob' : tlp .logprob } for tlp in lp .top_logprobs
2236+ ],
2237+ }
2238+ for lp in choice .logprobs .content
2239+ ]
2240+
2241+ if raw_finish_reason := choice .finish_reason :
2242+ provider_details ['finish_reason' ] = raw_finish_reason
2243+
2244+ return provider_details
2245+
2246+
22402247def _split_combined_tool_call_id (combined_id : str ) -> tuple [str , str | None ]:
22412248 # When reasoning, the Responses API requires the `ResponseFunctionToolCall` to be returned with both the `call_id` and `id` fields.
22422249 # Before our `ToolCallPart` gained the `id` field alongside `tool_call_id` field, we combined the two fields into a single string stored on `tool_call_id`.
0 commit comments