litellm-mirror/litellm/llms/base_llm/base_utils.py
Krish Dholakia 142b195784
Add anthropic thinking + reasoning content support (#8778)
* feat(anthropic/chat/transformation.py): add anthropic thinking param support

* feat(anthropic/chat/transformation.py): support returning thinking content for anthropic on streaming responses

* feat(anthropic/chat/transformation.py): return list of thinking blocks (include block signature)

allows usage in tool call responses

* fix(types/utils.py): extract and map reasoning_content from anthropic as content str

* test: add testing to ensure thinking_blocks are returned at the root

* fix(anthropic/chat/handler.py): return thinking blocks on streaming - include signature

* feat(factory.py): handle anthropic thinking blocks translation if in assistant response

* test: handle openai internal instability

* test: handle openai audio instability

* ci: pin anthropic dep

* test: handle openai audio instability

* fix: fix linting error

* refactor(anthropic/chat/transformation.py): refactor function to remain <50 LOC

* fix: fix linting error

* fix: fix linting error

* fix: fix linting error

* fix: fix linting error
2025-02-24 21:54:30 -08:00

142 lines
4.4 KiB
Python

"""
Utility functions for base LLM classes.
"""
import copy
from abc import ABC, abstractmethod
from typing import List, Optional, Type, Union
from openai.lib import _parsing, _pydantic
from pydantic import BaseModel
from litellm._logging import verbose_logger
from litellm.types.llms.openai import AllMessageValues
from litellm.types.utils import ProviderSpecificModelInfo
class BaseLLMModelInfo(ABC):
def get_provider_info(
self,
model: str,
) -> Optional[ProviderSpecificModelInfo]:
return None
@abstractmethod
def get_models(self) -> List[str]:
pass
@staticmethod
@abstractmethod
def get_api_key(api_key: Optional[str] = None) -> Optional[str]:
pass
@staticmethod
@abstractmethod
def get_api_base(api_base: Optional[str] = None) -> Optional[str]:
pass
@staticmethod
@abstractmethod
def get_base_model(model: str) -> Optional[str]:
"""
Returns the base model name from the given model name.
Some providers like bedrock - can receive model=`invoke/anthropic.claude-3-opus-20240229-v1:0` or `converse/anthropic.claude-3-opus-20240229-v1:0`
This function will return `anthropic.claude-3-opus-20240229-v1:0`
"""
pass
def _dict_to_response_format_helper(
response_format: dict, ref_template: Optional[str] = None
) -> dict:
if ref_template is not None and response_format.get("type") == "json_schema":
# Deep copy to avoid modifying original
modified_format = copy.deepcopy(response_format)
schema = modified_format["json_schema"]["schema"]
# Update all $ref values in the schema
def update_refs(schema):
stack = [(schema, [])]
visited = set()
while stack:
obj, path = stack.pop()
obj_id = id(obj)
if obj_id in visited:
continue
visited.add(obj_id)
if isinstance(obj, dict):
if "$ref" in obj:
ref_path = obj["$ref"]
model_name = ref_path.split("/")[-1]
obj["$ref"] = ref_template.format(model=model_name)
for k, v in obj.items():
if isinstance(v, (dict, list)):
stack.append((v, path + [k]))
elif isinstance(obj, list):
for i, item in enumerate(obj):
if isinstance(item, (dict, list)):
stack.append((item, path + [i]))
update_refs(schema)
return modified_format
return response_format
def type_to_response_format_param(
response_format: Optional[Union[Type[BaseModel], dict]],
ref_template: Optional[str] = None,
) -> Optional[dict]:
"""
Re-implementation of openai's 'type_to_response_format_param' function
Used for converting pydantic object to api schema.
"""
if response_format is None:
return None
if isinstance(response_format, dict):
return _dict_to_response_format_helper(response_format, ref_template)
# type checkers don't narrow the negation of a `TypeGuard` as it isn't
# a safe default behaviour but we know that at this point the `response_format`
# can only be a `type`
if not _parsing._completions.is_basemodel_type(response_format):
raise TypeError(f"Unsupported response_format type - {response_format}")
if ref_template is not None:
schema = response_format.model_json_schema(ref_template=ref_template)
else:
schema = _pydantic.to_strict_json_schema(response_format)
return {
"type": "json_schema",
"json_schema": {
"schema": schema,
"name": response_format.__name__,
"strict": True,
},
}
def map_developer_role_to_system_role(
messages: List[AllMessageValues],
) -> List[AllMessageValues]:
"""
Translate `developer` role to `system` role for non-OpenAI providers.
"""
new_messages: List[AllMessageValues] = []
for m in messages:
if m["role"] == "developer":
verbose_logger.debug(
"Translating developer role to system role for non-OpenAI providers."
) # ensure user knows what's happening with their input.
new_messages.append({"role": "system", "content": m["content"]})
else:
new_messages.append(m)
return new_messages