All checks were successful
Hugo Publish CI / build-and-deploy (push) Successful in 15s
187 lines
7.2 KiB
Python
187 lines
7.2 KiB
Python
import sys
|
|
import os
|
|
import shutil
|
|
|
|
# 1. Prepare /tmp/patch
|
|
PATCH_DIR = "/tmp/patch"
|
|
LITELLM_DIR = os.path.join(PATCH_DIR, "litellm")
|
|
|
|
if os.path.exists(PATCH_DIR):
|
|
shutil.rmtree(PATCH_DIR)
|
|
os.makedirs(PATCH_DIR)
|
|
|
|
shutil.copytree("/app/litellm", LITELLM_DIR)
|
|
|
|
# 2. Patch openai.py
|
|
openai_file = os.path.join(LITELLM_DIR, "types/llms/openai.py")
|
|
with open(openai_file, "r") as f:
|
|
content = f.read()
|
|
|
|
tool_call_chunk_original = (
|
|
'class ChatCompletionToolCallChunk(TypedDict): # result of /chat/completions call\n'
|
|
' id: Optional[str]\n'
|
|
' type: Literal["function"]'
|
|
)
|
|
tool_call_chunk_patch = tool_call_chunk_original.replace(
|
|
'Literal["function"]', 'Literal["function", "web_search"]'
|
|
)
|
|
delta_chunk_original = (
|
|
"class ChatCompletionDeltaToolCallChunk(TypedDict, total=False):\n"
|
|
" id: str\n"
|
|
' type: Literal["function"]'
|
|
)
|
|
delta_chunk_patch = delta_chunk_original.replace(
|
|
'Literal["function"]', 'Literal["function", "web_search"]'
|
|
)
|
|
|
|
for original, patched, label in [
|
|
(tool_call_chunk_original, tool_call_chunk_patch, "ChatCompletionToolCallChunk"),
|
|
(delta_chunk_original, delta_chunk_patch, "ChatCompletionDeltaToolCallChunk"),
|
|
]:
|
|
if original in content:
|
|
content = content.replace(original, patched, 1)
|
|
else:
|
|
print(f"Hotfix warning: {label} pattern not found, skipping update")
|
|
|
|
with open(openai_file, "w") as f:
|
|
f.write(content)
|
|
|
|
# 3. Patch transformation.py
|
|
trans_file = os.path.join(LITELLM_DIR, "completion_extras/litellm_responses_transformation/transformation.py")
|
|
with open(trans_file, "r") as f:
|
|
content = f.read()
|
|
|
|
import_block_original = """ from litellm.types.utils import (
|
|
ChatCompletionToolCallChunk,
|
|
GenericStreamingChunk,
|
|
)
|
|
"""
|
|
|
|
import_block_updated = """ from litellm.types.utils import (
|
|
ChatCompletionToolCallChunk,
|
|
Delta,
|
|
GenericStreamingChunk,
|
|
ModelResponseStream,
|
|
StreamingChoices,
|
|
)
|
|
"""
|
|
|
|
if import_block_original in content:
|
|
content = content.replace(import_block_original, import_block_updated, 1)
|
|
elif import_block_updated in content:
|
|
pass
|
|
else:
|
|
print("Hotfix warning: unexpected chunk_parser import layout, skipping Delta/ModelResponseStream import patch")
|
|
|
|
added_block = """ elif output_item.get("type") == "web_search_call":
|
|
# handle web search call - mask tool call by emitting empty content delta
|
|
# This prevents Open WebUI from seeing tool_calls and trying to execute them
|
|
action_payload = output_item.get("action")
|
|
verbose_logger.debug(
|
|
"Chat provider: masking web_search_call (added) call_id=%s action=%s",
|
|
output_item.get("call_id"),
|
|
action_payload,
|
|
)
|
|
# Emit empty content delta instead of tool_call to mask the tool usage
|
|
return ModelResponseStream(
|
|
choices=[
|
|
StreamingChoices(
|
|
index=0,
|
|
delta=Delta(content=""),
|
|
finish_reason=None,
|
|
)
|
|
]
|
|
)
|
|
"""
|
|
|
|
done_block = """ elif output_item.get("type") == "web_search_call":
|
|
# handle web search done - mask tool call by emitting empty content delta
|
|
# This prevents Open WebUI from seeing tool_calls and trying to execute them
|
|
action_payload = output_item.get("action")
|
|
verbose_logger.debug(
|
|
"Chat provider: masking web_search_call (done) call_id=%s action=%s",
|
|
output_item.get("call_id"),
|
|
action_payload,
|
|
)
|
|
# Emit empty content delta instead of tool_call to mask the tool usage
|
|
# Do NOT set finish_reason="tool_calls" as that would signal Open WebUI to handle the tool
|
|
return ModelResponseStream(
|
|
choices=[
|
|
StreamingChoices(
|
|
index=0,
|
|
delta=Delta(content=""),
|
|
finish_reason=None,
|
|
)
|
|
]
|
|
)
|
|
"""
|
|
|
|
added_target = ' elif output_item.get("type") == "message":'
|
|
|
|
def insert_block(source: str, block: str, occurrence_index: int) -> str:
|
|
"""Insert block before the nth occurrence (0-based) of added_target."""
|
|
start = -1
|
|
search_from = 0
|
|
for _ in range(occurrence_index + 1):
|
|
start = source.find(added_target, search_from)
|
|
if start == -1:
|
|
return source
|
|
search_from = start + len(added_target)
|
|
return source[:start] + block + source[start:]
|
|
|
|
if 'masking web_search_call (added)' not in content:
|
|
new_content = insert_block(content, added_block, 0)
|
|
if new_content == content:
|
|
print("Hotfix warning: unable to find insertion point for web_search_call (added)")
|
|
else:
|
|
content = new_content
|
|
|
|
if 'masking web_search_call (done)' not in content:
|
|
new_content = insert_block(content, done_block, 1)
|
|
if new_content == content:
|
|
print("Hotfix warning: unable to find insertion point for web_search_call (done)")
|
|
else:
|
|
content = new_content
|
|
|
|
# 4. Ensure streaming tool call chunks fall back to output_item IDs
|
|
call_id_pattern = 'id=output_item.get("call_id"),'
|
|
call_id_patch = 'id=output_item.get("call_id") or output_item.get("id"),'
|
|
if call_id_pattern in content:
|
|
content = content.replace(call_id_pattern, call_id_patch)
|
|
|
|
# 5. Guard assistant tool_call conversions when id is missing
|
|
tool_call_block_original = """ if function:
|
|
input_tool_call = {
|
|
"type": "function_call",
|
|
"call_id": tool_call["id"],
|
|
}
|
|
if "name" in function:
|
|
input_tool_call["name"] = function["name"]
|
|
if "arguments" in function:
|
|
input_tool_call["arguments"] = function["arguments"]
|
|
input_items.append(input_tool_call)
|
|
"""
|
|
tool_call_block_patch = """ if function:
|
|
call_id = tool_call.get("id") or tool_call.get("call_id")
|
|
if not call_id:
|
|
call_id = f"auto_tool_call_{len(input_items)}"
|
|
input_tool_call = {
|
|
"type": "function_call",
|
|
"call_id": call_id,
|
|
}
|
|
if "name" in function:
|
|
input_tool_call["name"] = function["name"]
|
|
if "arguments" in function:
|
|
input_tool_call["arguments"] = function["arguments"]
|
|
input_items.append(input_tool_call)
|
|
"""
|
|
if tool_call_block_original in content:
|
|
content = content.replace(tool_call_block_original, tool_call_block_patch, 1)
|
|
elif "auto_tool_call_" not in content:
|
|
print("Hotfix warning: assistant tool_call block not found; missing id guard not applied")
|
|
|
|
with open(trans_file, "w") as f:
|
|
f.write(content)
|
|
|
|
print("Successfully applied hotfixes to /tmp/patch/litellm")
|