Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions litellm/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -277,8 +277,6 @@
custom_prometheus_metadata_labels: List[str] = []
#### REQUEST PRIORITIZATION ####
priority_reservation: Optional[Dict[str, float]] = None


force_ipv4: bool = (
False # when True, litellm will force ipv4 for all LLM requests. Some users have seen httpx ConnectionError when using ipv6.
)
Expand Down
2 changes: 2 additions & 0 deletions litellm/litellm_core_utils/get_litellm_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ def get_litellm_params(
prompt_variables: Optional[dict] = None,
async_call: Optional[bool] = None,
ssl_verify: Optional[bool] = None,
merge_reasoning_content_in_choices: Optional[bool] = None,
**kwargs,
) -> dict:
litellm_params = {
Expand Down Expand Up @@ -97,5 +98,6 @@ def get_litellm_params(
"prompt_variables": prompt_variables,
"async_call": async_call,
"ssl_verify": ssl_verify,
"merge_reasoning_content_in_choices": merge_reasoning_content_in_choices,
}
return litellm_params
65 changes: 59 additions & 6 deletions litellm/litellm_core_utils/streaming_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from litellm.litellm_core_utils.redact_messages import LiteLLMLoggingObject
from litellm.litellm_core_utils.thread_pool_executor import executor
from litellm.types.llms.openai import ChatCompletionChunk
from litellm.types.router import GenericLiteLLMParams
from litellm.types.utils import Delta
from litellm.types.utils import GenericStreamingChunk as GChunk
from litellm.types.utils import (
Expand Down Expand Up @@ -70,6 +71,17 @@ def __init__(
self.completion_stream = completion_stream
self.sent_first_chunk = False
self.sent_last_chunk = False

litellm_params: GenericLiteLLMParams = GenericLiteLLMParams(
**self.logging_obj.model_call_details.get("litellm_params", {})
)
self.merge_reasoning_content_in_choices: bool = (
litellm_params.merge_reasoning_content_in_choices or False
)
self.sent_first_thinking_block = False
self.sent_last_thinking_block = False
self.thinking_content = ""

self.system_fingerprint: Optional[str] = None
self.received_finish_reason: Optional[str] = None
self.intermittent_finish_reason: Optional[str] = (
Expand All @@ -87,12 +99,7 @@ def __init__(
self.holding_chunk = ""
self.complete_response = ""
self.response_uptil_now = ""
_model_info = (
self.logging_obj.model_call_details.get("litellm_params", {}).get(
"model_info", {}
)
or {}
)
_model_info: Dict = litellm_params.model_info or {}

_api_base = get_api_base(
model=model or "",
Expand Down Expand Up @@ -873,6 +880,10 @@ def return_processed_chunk_logic( # noqa
_index: Optional[int] = completion_obj.get("index")
if _index is not None:
model_response.choices[0].index = _index

self._optional_combine_thinking_block_in_choices(
model_response=model_response
)
print_verbose(f"returning model_response: {model_response}")
return model_response
else:
Expand Down Expand Up @@ -929,6 +940,48 @@ def return_processed_chunk_logic( # noqa
self.chunks.append(model_response)
return

def _optional_combine_thinking_block_in_choices(
self, model_response: ModelResponseStream
) -> None:
"""
UI's Like OpenWebUI expect to get 1 chunk with <think>...</think> tags in the chunk content

In place updates the model_response object with reasoning_content in content with <think>...</think> tags

Enabled when `merge_reasoning_content_in_choices=True` passed in request params


"""
if self.merge_reasoning_content_in_choices is True:
reasoning_content = getattr(
model_response.choices[0].delta, "reasoning_content", None
)
if reasoning_content:
if self.sent_first_thinking_block is False:
model_response.choices[0].delta.content += (
"<think>" + reasoning_content
)
self.sent_first_thinking_block = True
elif (
self.sent_first_thinking_block is True
and hasattr(model_response.choices[0].delta, "reasoning_content")
and model_response.choices[0].delta.reasoning_content
):
model_response.choices[0].delta.content = reasoning_content
elif (
self.sent_first_thinking_block is True
and not self.sent_last_thinking_block
and model_response.choices[0].delta.content
):
model_response.choices[0].delta.content = (
"</think>" + model_response.choices[0].delta.content
)
self.sent_last_thinking_block = True

if hasattr(model_response.choices[0].delta, "reasoning_content"):
del model_response.choices[0].delta.reasoning_content
return

def chunk_creator(self, chunk: Any): # type: ignore # noqa: PLR0915
model_response = self.model_response_creator()
response_obj: Dict[str, Any] = {}
Expand Down
3 changes: 3 additions & 0 deletions litellm/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -1159,6 +1159,9 @@ def completion( # type: ignore # noqa: PLR0915
prompt_id=prompt_id,
prompt_variables=prompt_variables,
ssl_verify=ssl_verify,
merge_reasoning_content_in_choices=kwargs.get(
"merge_reasoning_content_in_choices", None
),
)
logging.update_environment_variables(
model=model,
Expand Down
28 changes: 7 additions & 21 deletions litellm/proxy/proxy_config.yaml
Original file line number Diff line number Diff line change
@@ -1,27 +1,13 @@
model_list:
- model_name: fake-openai-endpoint
- model_name: bedrock/us.anthropic.claude-3-7-sonnet-20250219-v1:0
litellm_params:
model: openai/my-fake-model
api_key: my-fake-key
api_base: https://exampleopenaiendpoint-production.up.railway.app/
- model_name: claude-special-alias
litellm_params:
model: anthropic/claude-3-haiku-20240307
api_key: os.environ/ANTHROPIC_API_KEY
- model_name: claude-3-5-sonnet-20241022
litellm_params:
model: anthropic/claude-3-5-sonnet-20241022
api_key: os.environ/ANTHROPIC_API_KEY
- model_name: claude-3-7-sonnet-20250219
litellm_params:
model: anthropic/claude-3-7-sonnet-20250219
api_key: os.environ/ANTHROPIC_API_KEY
- model_name: anthropic/*
litellm_params:
model: anthropic/*
api_key: os.environ/ANTHROPIC_API_KEY

model: bedrock/us.anthropic.claude-3-7-sonnet-20250219-v1:0
thinking: {"type": "enabled", "budget_tokens": 1024}
max_tokens: 1080
merge_reasoning_content_in_choices: true


general_settings:
store_model_in_db: true
store_prompts_in_spend_logs: true

5 changes: 5 additions & 0 deletions litellm/types/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,8 @@ class GenericLiteLLMParams(BaseModel):
budget_duration: Optional[str] = None
use_in_pass_through: Optional[bool] = False
model_config = ConfigDict(extra="allow", arbitrary_types_allowed=True)
merge_reasoning_content_in_choices: Optional[bool] = False
model_info: Optional[Dict] = None

def __init__(
self,
Expand Down Expand Up @@ -231,6 +233,9 @@ def __init__(
budget_duration: Optional[str] = None,
# Pass through params
use_in_pass_through: Optional[bool] = False,
# This will merge the reasoning content in the choices
merge_reasoning_content_in_choices: Optional[bool] = False,
model_info: Optional[Dict] = None,
**params,
):
args = locals()
Expand Down
3 changes: 3 additions & 0 deletions litellm/types/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
from pydantic import BaseModel, ConfigDict, Field, PrivateAttr
from typing_extensions import Callable, Dict, Required, TypedDict, override

import litellm

from ..litellm_core_utils.core_helpers import map_finish_reason
from .guardrails import GuardrailEventHooks
from .llms.openai import (
Expand Down Expand Up @@ -1803,6 +1805,7 @@ class StandardCallbackDynamicParams(TypedDict, total=False):
"max_budget",
"budget_duration",
"use_in_pass_through",
"merge_reasoning_content_in_choices",
] + list(StandardCallbackDynamicParams.__annotations__.keys())


Expand Down
Loading