This commit is contained in:
Timothy Jaeryang Baek
2026-02-19 16:29:19 -06:00
parent 6ac593209c
commit 91a0301c9e
3 changed files with 447 additions and 0 deletions
+73
View File
@@ -1442,6 +1442,16 @@ async def check_url(request: Request, call_next):
scheme="Bearer", credentials=request.cookies.get("token")
)
# Fallback to x-api-key header for Anthropic Messages API routes
if request.state.token is None and request.headers.get("x-api-key"):
request_path = request.url.path
if request_path in ("/api/message", "/api/v1/messages"):
from fastapi.security import HTTPAuthorizationCredentials
request.state.token = HTTPAuthorizationCredentials(
scheme="Bearer", credentials=request.headers.get("x-api-key")
)
request.state.enable_api_keys = app.state.config.ENABLE_API_KEYS
response = await call_next(request)
process_time = int(time.time()) - start_time
@@ -1893,6 +1903,69 @@ generate_chat_completions = chat_completion
generate_chat_completion = chat_completion
##################################
#
# Anthropic Messages API Compatible Endpoint
#
##################################
from open_webui.utils.anthropic import (
convert_anthropic_to_openai_payload,
convert_openai_to_anthropic_response,
openai_stream_to_anthropic_stream,
)
@app.post("/api/message")
@app.post("/api/v1/messages") # Anthropic Messages API compatible endpoint
async def generate_messages(
request: Request,
form_data: dict,
user=Depends(get_verified_user),
):
"""
Anthropic Messages API compatible endpoint.
Accepts the Anthropic Messages API format, converts internally to OpenAI
Chat Completions format, routes through the existing chat completion
pipeline, then converts the response back to Anthropic Messages format.
Supports both streaming and non-streaming requests.
All models configured in Open WebUI are accessible via this endpoint.
Authentication: Supports both standard Authorization header and
Anthropic's x-api-key header (via middleware translation).
"""
# Convert Anthropic payload to OpenAI format
requested_model = form_data.get("model", "")
openai_payload = convert_anthropic_to_openai_payload(form_data)
# Route through the existing chat_completion handler
response = await chat_completion(request, openai_payload, user)
# Convert response back to Anthropic format
if isinstance(response, StreamingResponse):
# Streaming response: wrap the generator to convert SSE format
return StreamingResponse(
openai_stream_to_anthropic_stream(
response.body_iterator, model=requested_model
),
media_type="text/event-stream",
headers={
"Cache-Control": "no-cache",
"Connection": "keep-alive",
},
)
elif isinstance(response, dict):
return convert_openai_to_anthropic_response(response, model=requested_model)
else:
# Passthrough for error responses (JSONResponse, PlainTextResponse, etc.)
return response
@app.post("/api/chat/completed")
async def chat_completed(
request: Request, form_data: dict, user=Depends(get_verified_user)