11from __future__ import annotations as _annotations
22
33import base64
4+ import itertools
45import json
56import warnings
67from collections .abc import AsyncIterable , AsyncIterator , Sequence
6263 ChatCompletionContentPartParam ,
6364 ChatCompletionContentPartTextParam ,
6465 )
66+ from openai .types .chat .chat_completion_chunk import Choice
6567 from openai .types .chat .chat_completion_content_part_image_param import ImageURL
6668 from openai .types .chat .chat_completion_content_part_input_audio_param import InputAudio
6769 from openai .types .chat .chat_completion_content_part_param import File , FileFile
@@ -530,9 +532,17 @@ async def _completions_create(
530532 raise # pragma: lax no cover
531533
532534 def _validate_completion (self , response : chat .ChatCompletion ) -> chat .ChatCompletion :
535+ """Hook that validates chat completions before processing.
536+
537+ This method may be overridden by subclasses of `OpenAIChatModel` to apply custom completion validations.
538+ """
533539 return chat .ChatCompletion .model_validate (response .model_dump ())
534540
535541 def _process_reasoning (self , response : chat .ChatCompletion ) -> list [ThinkingPart ]:
542+ """Hook that maps reasoning tokens to thinking parts.
543+
544+ This method may be overridden by subclasses of `OpenAIChatModel` to apply custom mappings.
545+ """
536546 message = response .choices [0 ].message
537547 items : list [ThinkingPart ] = []
538548
@@ -550,6 +560,10 @@ def _process_reasoning(self, response: chat.ChatCompletion) -> list[ThinkingPart
550560 return items
551561
552562 def _process_provider_details (self , response : chat .ChatCompletion ) -> dict [str , Any ]:
563+ """Hook that response content to provider details.
564+
565+ This method may be overridden by subclasses of `OpenAIChatModel` to apply custom mappings.
566+ """
553567 choice = response .choices [0 ]
554568 provider_details : dict [str , Any ] = {}
555569
@@ -692,7 +706,7 @@ def _get_web_search_options(self, model_request_parameters: ModelRequestParamete
692706 def _map_model_response (self , message : ModelResponse ) -> chat .ChatCompletionMessageParam :
693707 """Hook that determines how `ModelResponse` is mapped into `ChatCompletionMessageParam` objects before sending.
694708
695- Subclasses of `OpenAIChatModel` should override this method to provide their own mapping logic.
709+ Subclasses of `OpenAIChatModel` may override this method to provide their own mapping logic.
696710 """
697711 texts : list [str ] = []
698712 tool_calls : list [ChatCompletionMessageFunctionToolCallParam ] = []
@@ -1740,19 +1754,28 @@ async def _get_event_iterator(self) -> AsyncIterator[ModelResponseStreamEvent]:
17401754 async def _validate_response (self ):
17411755 """Hook that validates incoming chunks.
17421756
1743- This method should be overridden by subclasses of `OpenAIStreamedResponse` to apply custom chunk validations.
1757+ This method may be overridden by subclasses of `OpenAIStreamedResponse` to apply custom chunk validations.
17441758
17451759 By default, this is a no-op since `ChatCompletionChunk` is already validated.
17461760 """
17471761 async for chunk in self ._response :
17481762 yield chunk
17491763
17501764 def _map_part_delta (self , chunk : ChatCompletionChunk ):
1751- """Hook that maps delta content to events.
1765+ """Hook that determines the sequence of mappings that will be called to produce events.
17521766
1753- This method should be overridden by subclasses of `OpenAIStreamResponse` to customize the mapping.
1767+ This method may be overridden by subclasses of `OpenAIStreamResponse` to customize the mapping.
17541768 """
17551769 choice = chunk .choices [0 ]
1770+ return itertools .chain (
1771+ self ._map_thinking_delta (choice ), self ._map_text_delta (choice ), self ._map_tool_call_delta (choice )
1772+ )
1773+
1774+ def _map_thinking_delta (self , choice : Choice ):
1775+ """Hook that maps thinking delta content to events.
1776+
1777+ This method may be overridden by subclasses of `OpenAIStreamResponse` to customize the mapping.
1778+ """
17561779 # The `reasoning_content` field is only present in DeepSeek models.
17571780 # https://api-docs.deepseek.com/guides/reasoning_model
17581781 if reasoning_content := getattr (choice .delta , 'reasoning_content' , None ):
@@ -1774,6 +1797,11 @@ def _map_part_delta(self, chunk: ChatCompletionChunk):
17741797 provider_name = self .provider_name ,
17751798 )
17761799
1800+ def _map_text_delta (self , choice : Choice ):
1801+ """Hook that maps text delta content to events.
1802+
1803+ This method may be overridden by subclasses of `OpenAIStreamResponse` to customize the mapping.
1804+ """
17771805 # Handle the text part of the response
17781806 content = choice .delta .content
17791807 if content :
@@ -1789,6 +1817,11 @@ def _map_part_delta(self, chunk: ChatCompletionChunk):
17891817 maybe_event .part .provider_name = self .provider_name
17901818 yield maybe_event
17911819
1820+ def _map_tool_call_delta (self , choice : Choice ):
1821+ """Hook that maps tool call delta content to events.
1822+
1823+ This method may be overridden by subclasses of `OpenAIStreamResponse` to customize the mapping.
1824+ """
17921825 for dtc in choice .delta .tool_calls or []:
17931826 maybe_event = self ._parts_manager .handle_tool_call_delta (
17941827 vendor_part_id = dtc .index ,
@@ -1802,7 +1835,7 @@ def _map_part_delta(self, chunk: ChatCompletionChunk):
18021835 def _map_provider_details (self , chunk : ChatCompletionChunk ) -> dict [str , str ] | None :
18031836 """Hook that generates the provider details from chunk content.
18041837
1805- This method should be overridden by subclasses of `OpenAIStreamResponse` to customize the provider details.
1838+ This method may be overridden by subclasses of `OpenAIStreamResponse` to customize the provider details.
18061839 """
18071840 choice = chunk .choices [0 ]
18081841 if raw_finish_reason := choice .finish_reason :
0 commit comments