mirror of
https://github.com/open-webui/open-webui.git
synced 2026-08-13 01:02:25 -06:00
refac
This commit is contained in:
@@ -252,7 +252,7 @@ from open_webui.utils.oauth import (
|
||||
from open_webui.utils.plugin import install_tool_and_function_dependencies
|
||||
from open_webui.utils.redis import get_redis_client
|
||||
from open_webui.utils.security_headers import SecurityHeadersMiddleware
|
||||
from open_webui.utils.session_pool import get_session
|
||||
from open_webui.utils.session_pool import cleanup_response, get_session, stream_wrapper
|
||||
from open_webui.utils.tools import set_terminal_servers, set_tool_servers
|
||||
|
||||
if SAFE_MODE:
|
||||
@@ -1779,6 +1779,7 @@ app.state.CHAT_COMPLETION_HANDLER = chat_completion
|
||||
from open_webui.utils.anthropic import (
|
||||
convert_anthropic_to_openai_payload,
|
||||
convert_openai_to_anthropic_response,
|
||||
is_anthropic_messages_passthrough,
|
||||
openai_stream_to_anthropic_stream,
|
||||
)
|
||||
|
||||
@@ -1793,6 +1794,65 @@ async def count_message_tokens(
|
||||
return {'input_tokens': await openai.count_anthropic_tokens(request, form_data, user)}
|
||||
|
||||
|
||||
async def passthrough_anthropic_messages(request: Request, form_data: dict, user) -> Response | dict:
|
||||
requested_model, payload, url, key, headers, cookies = await openai.get_anthropic_token_count_target(
|
||||
request, form_data, user
|
||||
)
|
||||
request_url = f'{url.rstrip("/")}/messages'
|
||||
response = None
|
||||
streaming = False
|
||||
|
||||
try:
|
||||
session = await get_session()
|
||||
response = await session.request(
|
||||
method='POST',
|
||||
url=request_url,
|
||||
data=json.dumps(payload),
|
||||
headers=headers,
|
||||
cookies=cookies,
|
||||
ssl=AIOHTTP_CLIENT_SESSION_SSL,
|
||||
timeout=aiohttp.ClientTimeout(total=openai.AIOHTTP_CLIENT_TIMEOUT),
|
||||
)
|
||||
|
||||
if 'text/event-stream' in response.headers.get('Content-Type', ''):
|
||||
streaming = True
|
||||
return StreamingResponse(
|
||||
stream_wrapper(response),
|
||||
status_code=response.status,
|
||||
headers=openai._clean_proxy_headers(response.headers),
|
||||
)
|
||||
|
||||
try:
|
||||
response_data = await response.json()
|
||||
except Exception:
|
||||
response_data = await response.text()
|
||||
|
||||
if response.status >= 400:
|
||||
await openai.publish_model_provider_request_failed(
|
||||
request,
|
||||
actor=user,
|
||||
provider='openai-compatible',
|
||||
base_url=url,
|
||||
api_key=key,
|
||||
status=response.status,
|
||||
requested_model=requested_model,
|
||||
upstream_error=response_data,
|
||||
)
|
||||
if isinstance(response_data, (dict, list)):
|
||||
return JSONResponse(status_code=response.status, content=response_data)
|
||||
return Response(status_code=response.status, content=response_data)
|
||||
|
||||
return response_data
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception:
|
||||
log.exception('Failed to passthrough Anthropic Messages request for model %s', requested_model)
|
||||
raise HTTPException(status_code=502, detail=ERROR_MESSAGES.SERVER_CONNECTION_ERROR)
|
||||
finally:
|
||||
if not streaming:
|
||||
await cleanup_response(response)
|
||||
|
||||
|
||||
@app.post('/api/message')
|
||||
@app.post('/api/v1/messages') # Anthropic Messages API compatible endpoint
|
||||
async def generate_messages(
|
||||
@@ -1833,7 +1893,9 @@ async def generate_messages(
|
||||
models = request.app.state.OPENAI_MODELS
|
||||
model = models.get(model_id)
|
||||
if model:
|
||||
_, _, api_config = await openai.get_openai_connection(model['urlIdx'])
|
||||
url, _, api_config = await openai.get_openai_connection(model['urlIdx'])
|
||||
if is_anthropic_messages_passthrough(url, api_config):
|
||||
return await passthrough_anthropic_messages(request, form_data, user)
|
||||
passthrough_params = api_config.get('passthrough_params') or []
|
||||
|
||||
# Convert Anthropic payload to OpenAI format
|
||||
|
||||
@@ -46,6 +46,7 @@ API_CONFIG_FIELDS = (
|
||||
'auth_type',
|
||||
'headers',
|
||||
'azure',
|
||||
'api_type',
|
||||
'api_version',
|
||||
'extra_params',
|
||||
'passthrough_params',
|
||||
|
||||
@@ -126,6 +126,13 @@ def _finalize_openai_content(blocks: list) -> str | list:
|
||||
return blocks
|
||||
|
||||
|
||||
def is_anthropic_messages_passthrough(url: str, api_config: dict | None = None) -> bool:
|
||||
api_config = api_config or {}
|
||||
provider = str(api_config.get('provider', '')).lower()
|
||||
|
||||
return is_anthropic_url(url or '') or provider == 'litellm'
|
||||
|
||||
|
||||
def convert_anthropic_to_openai_payload(
|
||||
anthropic_payload: dict, passthrough_params: list[str] | str | None = None
|
||||
) -> dict:
|
||||
@@ -192,6 +199,8 @@ def convert_anthropic_to_openai_payload(
|
||||
},
|
||||
)
|
||||
)
|
||||
elif block_type in ('thinking', 'redacted_thinking'):
|
||||
openai_content.append(_copy_cache_control(block, dict(block)))
|
||||
elif block_type == 'image':
|
||||
source = block.get('source', {})
|
||||
if source.get('type') == 'base64':
|
||||
@@ -492,6 +501,11 @@ def convert_openai_to_anthropic_response(
|
||||
if not isinstance(block, dict):
|
||||
continue
|
||||
|
||||
if block.get('type') == 'redacted_thinking':
|
||||
content.append({k: v for k, v in block.items() if k in {'type', 'data'}})
|
||||
has_thinking = True
|
||||
continue
|
||||
|
||||
thinking = block.get('thinking') or block.get('content') or block.get('text')
|
||||
if not thinking:
|
||||
continue
|
||||
|
||||
@@ -597,6 +597,7 @@
|
||||
<option value="">{$i18n.t('Default')}</option>
|
||||
<option value="azure">{$i18n.t('Azure OpenAI')}</option>
|
||||
<option value="llama.cpp">{$i18n.t('llama.cpp')}</option>
|
||||
<option value="litellm">{$i18n.t('LiteLLM')}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user