mirror of
https://github.com/BerriAI/litellm.git
synced 2025-04-27 11:43:54 +00:00
Litellm dev bedrock anthropic 3 7 v2 (#8843)
* feat(bedrock/converse/transformation.py): support claude-3-7-sonnet reasoning_Content transformation Closes https://github.com/BerriAI/litellm/issues/8777 * fix(bedrock/): support returning `reasoning_content` on streaming for claude-3-7 Resolves https://github.com/BerriAI/litellm/issues/8777 * feat(bedrock/): unify converse reasoning content blocks for consistency across anthropic and bedrock * fix(anthropic/chat/transformation.py): handle deepseek-style 'reasoning_content' extraction within transformation.py simpler logic * feat(bedrock/): fix streaming to return blocks in consistent format * fix: fix linting error * test: fix test * feat(factory.py): fix bedrock thinking block translation on tool calling allows passing the thinking blocks back to bedrock for tool calling * fix(types/utils.py): don't exclude provider_specific_fields on model dump ensures consistent responses * fix: fix linting errors * fix(convert_dict_to_response.py): pass reasoning_content on root * fix: test * fix(streaming_handler.py): add helper util for setting model id * fix(streaming_handler.py): fix setting model id on model response stream chunk * fix(streaming_handler.py): fix linting error * fix(streaming_handler.py): fix linting error * fix(types/utils.py): add provider_specific_fields to model stream response * fix(streaming_handler.py): copy provider specific fields and add them to the root of the streaming response * fix(streaming_handler.py): fix check * fix: fix test * fix(types/utils.py): ensure messages content is always openai compatible * fix(types/utils.py): fix delta object to always be openai compatible only introduce new params if variable exists * test: fix bedrock nova tests * test: skip flaky test * test: skip flaky test in ci/cd
This commit is contained in:
parent
f3ef6c92a3
commit
05a973bf19
20 changed files with 447 additions and 149 deletions
|
@ -66,6 +66,22 @@ class ToolUseBlock(TypedDict):
|
|||
toolUseId: str
|
||||
|
||||
|
||||
class BedrockConverseReasoningTextBlock(TypedDict, total=False):
|
||||
text: Required[str]
|
||||
signature: str
|
||||
|
||||
|
||||
class BedrockConverseReasoningContentBlock(TypedDict, total=False):
|
||||
reasoningText: BedrockConverseReasoningTextBlock
|
||||
redactedContent: str
|
||||
|
||||
|
||||
class BedrockConverseReasoningContentBlockDelta(TypedDict, total=False):
|
||||
signature: str
|
||||
redactedContent: str
|
||||
text: str
|
||||
|
||||
|
||||
class ContentBlock(TypedDict, total=False):
|
||||
text: str
|
||||
image: ImageBlock
|
||||
|
@ -73,6 +89,7 @@ class ContentBlock(TypedDict, total=False):
|
|||
toolResult: ToolResultBlock
|
||||
toolUse: ToolUseBlock
|
||||
cachePoint: CachePointBlock
|
||||
reasoningContent: BedrockConverseReasoningContentBlock
|
||||
|
||||
|
||||
class MessageBlock(TypedDict):
|
||||
|
@ -167,6 +184,7 @@ class ContentBlockDeltaEvent(TypedDict, total=False):
|
|||
|
||||
text: str
|
||||
toolUse: ToolBlockDeltaEvent
|
||||
reasoningContent: BedrockConverseReasoningContentBlockDelta
|
||||
|
||||
|
||||
class CommonRequestObject(
|
||||
|
|
|
@ -596,6 +596,9 @@ class ChatCompletionResponseMessage(TypedDict, total=False):
|
|||
tool_calls: Optional[List[ChatCompletionToolCallChunk]]
|
||||
role: Literal["assistant"]
|
||||
function_call: Optional[ChatCompletionToolCallFunctionChunk]
|
||||
provider_specific_fields: Optional[dict]
|
||||
reasoning_content: Optional[str]
|
||||
thinking_blocks: Optional[List[ChatCompletionThinkingBlock]]
|
||||
|
||||
|
||||
class ChatCompletionUsageBlock(TypedDict):
|
||||
|
|
|
@ -24,6 +24,7 @@ from typing_extensions import Callable, Dict, Required, TypedDict, override
|
|||
from ..litellm_core_utils.core_helpers import map_finish_reason
|
||||
from .guardrails import GuardrailEventHooks
|
||||
from .llms.openai import (
|
||||
ChatCompletionThinkingBlock,
|
||||
ChatCompletionToolCallChunk,
|
||||
ChatCompletionUsageBlock,
|
||||
OpenAIChatCompletionChunk,
|
||||
|
@ -457,29 +458,6 @@ Reference:
|
|||
ChatCompletionMessage(content='This is a test', role='assistant', function_call=None, tool_calls=None))
|
||||
"""
|
||||
|
||||
REASONING_CONTENT_COMPATIBLE_PARAMS = [
|
||||
"thinking_blocks",
|
||||
"reasoning_content",
|
||||
]
|
||||
|
||||
|
||||
def map_reasoning_content(provider_specific_fields: Dict[str, Any]) -> str:
|
||||
"""
|
||||
Extract reasoning_content from provider_specific_fields
|
||||
"""
|
||||
|
||||
reasoning_content: str = ""
|
||||
for k, v in provider_specific_fields.items():
|
||||
if k == "thinking_blocks" and isinstance(v, list):
|
||||
_reasoning_content = ""
|
||||
for block in v:
|
||||
if block.get("type") == "thinking":
|
||||
_reasoning_content += block.get("thinking", "")
|
||||
reasoning_content = _reasoning_content
|
||||
elif k == "reasoning_content":
|
||||
reasoning_content = v
|
||||
return reasoning_content
|
||||
|
||||
|
||||
def add_provider_specific_fields(
|
||||
object: BaseModel, provider_specific_fields: Optional[Dict[str, Any]]
|
||||
|
@ -487,12 +465,6 @@ def add_provider_specific_fields(
|
|||
if not provider_specific_fields: # set if provider_specific_fields is not empty
|
||||
return
|
||||
setattr(object, "provider_specific_fields", provider_specific_fields)
|
||||
for k, v in provider_specific_fields.items():
|
||||
if v is not None:
|
||||
setattr(object, k, v)
|
||||
if k in REASONING_CONTENT_COMPATIBLE_PARAMS and k != "reasoning_content":
|
||||
reasoning_content = map_reasoning_content({k: v})
|
||||
setattr(object, "reasoning_content", reasoning_content)
|
||||
|
||||
|
||||
class Message(OpenAIObject):
|
||||
|
@ -501,6 +473,8 @@ class Message(OpenAIObject):
|
|||
tool_calls: Optional[List[ChatCompletionMessageToolCall]]
|
||||
function_call: Optional[FunctionCall]
|
||||
audio: Optional[ChatCompletionAudioResponse] = None
|
||||
reasoning_content: Optional[str] = None
|
||||
thinking_blocks: Optional[List[ChatCompletionThinkingBlock]] = None
|
||||
provider_specific_fields: Optional[Dict[str, Any]] = Field(
|
||||
default=None, exclude=True
|
||||
)
|
||||
|
@ -513,6 +487,8 @@ class Message(OpenAIObject):
|
|||
tool_calls: Optional[list] = None,
|
||||
audio: Optional[ChatCompletionAudioResponse] = None,
|
||||
provider_specific_fields: Optional[Dict[str, Any]] = None,
|
||||
reasoning_content: Optional[str] = None,
|
||||
thinking_blocks: Optional[List[ChatCompletionThinkingBlock]] = None,
|
||||
**params,
|
||||
):
|
||||
init_values: Dict[str, Any] = {
|
||||
|
@ -538,6 +514,12 @@ class Message(OpenAIObject):
|
|||
if audio is not None:
|
||||
init_values["audio"] = audio
|
||||
|
||||
if thinking_blocks is not None:
|
||||
init_values["thinking_blocks"] = thinking_blocks
|
||||
|
||||
if reasoning_content is not None:
|
||||
init_values["reasoning_content"] = reasoning_content
|
||||
|
||||
super(Message, self).__init__(
|
||||
**init_values, # type: ignore
|
||||
**params,
|
||||
|
@ -548,6 +530,14 @@ class Message(OpenAIObject):
|
|||
# OpenAI compatible APIs like mistral API will raise an error if audio is passed in
|
||||
del self.audio
|
||||
|
||||
if reasoning_content is None:
|
||||
# ensure default response matches OpenAI spec
|
||||
del self.reasoning_content
|
||||
|
||||
if thinking_blocks is None:
|
||||
# ensure default response matches OpenAI spec
|
||||
del self.thinking_blocks
|
||||
|
||||
add_provider_specific_fields(self, provider_specific_fields)
|
||||
|
||||
def get(self, key, default=None):
|
||||
|
@ -571,9 +561,9 @@ class Message(OpenAIObject):
|
|||
|
||||
|
||||
class Delta(OpenAIObject):
|
||||
provider_specific_fields: Optional[Dict[str, Any]] = Field(
|
||||
default=None, exclude=True
|
||||
)
|
||||
reasoning_content: Optional[str] = None
|
||||
thinking_blocks: Optional[List[ChatCompletionThinkingBlock]] = None
|
||||
provider_specific_fields: Optional[Dict[str, Any]] = Field(default=None)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
|
@ -582,6 +572,8 @@ class Delta(OpenAIObject):
|
|||
function_call=None,
|
||||
tool_calls=None,
|
||||
audio: Optional[ChatCompletionAudioResponse] = None,
|
||||
reasoning_content: Optional[str] = None,
|
||||
thinking_blocks: Optional[List[ChatCompletionThinkingBlock]] = None,
|
||||
**params,
|
||||
):
|
||||
super(Delta, self).__init__(**params)
|
||||
|
@ -593,6 +585,18 @@ class Delta(OpenAIObject):
|
|||
self.tool_calls: Optional[List[Union[ChatCompletionDeltaToolCall, Any]]] = None
|
||||
self.audio: Optional[ChatCompletionAudioResponse] = None
|
||||
|
||||
if reasoning_content is not None:
|
||||
self.reasoning_content = reasoning_content
|
||||
else:
|
||||
# ensure default response matches OpenAI spec
|
||||
del self.reasoning_content
|
||||
|
||||
if thinking_blocks is not None:
|
||||
self.thinking_blocks = thinking_blocks
|
||||
else:
|
||||
# ensure default response matches OpenAI spec
|
||||
del self.thinking_blocks
|
||||
|
||||
if function_call is not None and isinstance(function_call, dict):
|
||||
self.function_call = FunctionCall(**function_call)
|
||||
else:
|
||||
|
@ -894,12 +898,14 @@ class ModelResponseBase(OpenAIObject):
|
|||
|
||||
class ModelResponseStream(ModelResponseBase):
|
||||
choices: List[StreamingChoices]
|
||||
provider_specific_fields: Optional[Dict[str, Any]] = Field(default=None)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
choices: Optional[List[Union[StreamingChoices, dict, BaseModel]]] = None,
|
||||
id: Optional[str] = None,
|
||||
created: Optional[int] = None,
|
||||
provider_specific_fields: Optional[Dict[str, Any]] = None,
|
||||
**kwargs,
|
||||
):
|
||||
if choices is not None and isinstance(choices, list):
|
||||
|
@ -936,6 +942,7 @@ class ModelResponseStream(ModelResponseBase):
|
|||
kwargs["id"] = id
|
||||
kwargs["created"] = created
|
||||
kwargs["object"] = "chat.completion.chunk"
|
||||
kwargs["provider_specific_fields"] = provider_specific_fields
|
||||
|
||||
super().__init__(**kwargs)
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue