fix
This commit is contained in:
parent
f6f5137c9b
commit
38f961a4b4
3 changed files with 207 additions and 77 deletions
30
README.md
30
README.md
|
|
@ -2,11 +2,8 @@
|
||||||
|
|
||||||
**Use Claude Code with OpenAI Models** 🤝
|
**Use Claude Code with OpenAI Models** 🤝
|
||||||
|
|
||||||
A proxy server that lets you use Claude Code with OpenAI models like GPT-4o. 🌉
|
A proxy server that lets you use Claude Code with OpenAI models like GPT-4o / gpt-4.5 and o3-mini. 🌉
|
||||||
|
|
||||||
## Why Use This? 🤔
|
|
||||||
|
|
||||||
- Why not? ¯\\_(ツ)_/¯
|
|
||||||
|
|
||||||
## Quick Start ⚡
|
## Quick Start ⚡
|
||||||
|
|
||||||
|
|
@ -31,6 +28,9 @@ A proxy server that lets you use Claude Code with OpenAI models like GPT-4o.
|
||||||
Create a `.env` file with:
|
Create a `.env` file with:
|
||||||
```
|
```
|
||||||
OPENAI_API_KEY=your-openai-key
|
OPENAI_API_KEY=your-openai-key
|
||||||
|
# Optional: customize which models are used
|
||||||
|
# BIG_MODEL=gpt-4o
|
||||||
|
# SMALL_MODEL=gpt-4o-mini
|
||||||
```
|
```
|
||||||
|
|
||||||
4. **Start the proxy server**:
|
4. **Start the proxy server**:
|
||||||
|
|
@ -58,11 +58,27 @@ The proxy automatically maps Claude models to OpenAI models:
|
||||||
|
|
||||||
| Claude Model | OpenAI Model |
|
| Claude Model | OpenAI Model |
|
||||||
|--------------|--------------|
|
|--------------|--------------|
|
||||||
| haiku | gpt-4o-mini |
|
| haiku | gpt-4o-mini (default) |
|
||||||
| sonnet | gpt-4o |
|
| sonnet | gpt-4o (default) |
|
||||||
|
|
||||||
|
### Customizing Model Mapping
|
||||||
|
|
||||||
You can customize these mappings in `server.py` by editing the `validate_model` function. 🔧
|
You can customize which OpenAI models are used via environment variables:
|
||||||
|
|
||||||
|
- `BIG_MODEL`: The OpenAI model to use for Claude Sonnet models (default: "gpt-4o")
|
||||||
|
- `SMALL_MODEL`: The OpenAI model to use for Claude Haiku models (default: "gpt-4o-mini")
|
||||||
|
|
||||||
|
Add these to your `.env` file to customize:
|
||||||
|
```
|
||||||
|
OPENAI_API_KEY=your-openai-key
|
||||||
|
BIG_MODEL=gpt-4o
|
||||||
|
SMALL_MODEL=gpt-4o-mini
|
||||||
|
```
|
||||||
|
|
||||||
|
Or set them directly when running the server:
|
||||||
|
```bash
|
||||||
|
BIG_MODEL=gpt-4o SMALL_MODEL=gpt-4o-mini uv run uvicorn server:app --host 0.0.0.0 --port 8082
|
||||||
|
```
|
||||||
|
|
||||||
## How It Works 🧩
|
## How It Works 🧩
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,3 +12,4 @@ dependencies = [
|
||||||
"litellm>=1.40.14",
|
"litellm>=1.40.14",
|
||||||
"python-dotenv>=1.0.0",
|
"python-dotenv>=1.0.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
||||||
253
server.py
253
server.py
|
|
@ -11,6 +11,9 @@ import litellm
|
||||||
import uuid
|
import uuid
|
||||||
import time
|
import time
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
import re
|
||||||
|
from datetime import datetime
|
||||||
|
import sys
|
||||||
|
|
||||||
# Load environment variables from .env file
|
# Load environment variables from .env file
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
|
|
@ -22,7 +25,12 @@ logging.basicConfig(
|
||||||
)
|
)
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# Configure uvicorn to be quieter
|
||||||
|
import uvicorn
|
||||||
|
# Tell uvicorn's loggers to be quiet
|
||||||
|
logging.getLogger("uvicorn").setLevel(logging.WARNING)
|
||||||
|
logging.getLogger("uvicorn.access").setLevel(logging.WARNING)
|
||||||
|
logging.getLogger("uvicorn.error").setLevel(logging.WARNING)
|
||||||
|
|
||||||
# Create a filter to block any log messages containing specific strings
|
# Create a filter to block any log messages containing specific strings
|
||||||
class MessageFilter(logging.Filter):
|
class MessageFilter(logging.Filter):
|
||||||
|
|
@ -77,6 +85,10 @@ app = FastAPI()
|
||||||
ANTHROPIC_API_KEY = os.environ.get("ANTHROPIC_API_KEY")
|
ANTHROPIC_API_KEY = os.environ.get("ANTHROPIC_API_KEY")
|
||||||
OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY")
|
OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY")
|
||||||
|
|
||||||
|
# Get model mapping configuration from environment
|
||||||
|
BIG_MODEL = os.environ.get("BIG_MODEL", "gpt-4o")
|
||||||
|
SMALL_MODEL = os.environ.get("SMALL_MODEL", "gpt-4o-mini")
|
||||||
|
|
||||||
# Models for Anthropic API requests
|
# Models for Anthropic API requests
|
||||||
class ContentBlockText(BaseModel):
|
class ContentBlockText(BaseModel):
|
||||||
type: Literal["text"]
|
type: Literal["text"]
|
||||||
|
|
@ -127,33 +139,43 @@ class MessagesRequest(BaseModel):
|
||||||
tools: Optional[List[Tool]] = None
|
tools: Optional[List[Tool]] = None
|
||||||
tool_choice: Optional[Dict[str, Any]] = None
|
tool_choice: Optional[Dict[str, Any]] = None
|
||||||
thinking: Optional[ThinkingConfig] = None
|
thinking: Optional[ThinkingConfig] = None
|
||||||
|
original_model: Optional[str] = None # Will store the original model name
|
||||||
|
|
||||||
@field_validator('model')
|
@field_validator('model')
|
||||||
def validate_model(cls, v):
|
def validate_model(cls, v, info):
|
||||||
|
# Store the original model name
|
||||||
|
original_model = v
|
||||||
|
|
||||||
# Check if we're using OpenAI models and need to swap
|
# Check if we're using OpenAI models and need to swap
|
||||||
if USE_OPENAI_MODELS:
|
if USE_OPENAI_MODELS:
|
||||||
original_model = v
|
|
||||||
# Remove anthropic/ prefix if it exists
|
# Remove anthropic/ prefix if it exists
|
||||||
if v.startswith('anthropic/'):
|
if v.startswith('anthropic/'):
|
||||||
v = v[10:] # Remove 'anthropic/' prefix
|
v = v[10:] # Remove 'anthropic/' prefix
|
||||||
|
|
||||||
# Swap Haiku with 4o-mini
|
# Swap Haiku with small model (default: gpt-4o-mini)
|
||||||
if 'haiku' in v.lower():
|
if 'haiku' in v.lower():
|
||||||
new_model = "openai/gpt-4o-mini"
|
new_model = f"openai/{SMALL_MODEL}"
|
||||||
logger.debug(f"📌 MODEL MAPPING: {original_model} ➡️ {new_model}")
|
logger.debug(f"📌 MODEL MAPPING: {original_model} ➡️ {new_model}")
|
||||||
return new_model
|
v = new_model
|
||||||
|
|
||||||
# Swap any Sonnet model with 4o
|
# Swap any Sonnet model with big model (default: gpt-4o)
|
||||||
if 'sonnet' in v.lower():
|
elif 'sonnet' in v.lower():
|
||||||
new_model = "openai/gpt-4o"
|
new_model = f"openai/{BIG_MODEL}"
|
||||||
logger.debug(f"📌 MODEL MAPPING: {original_model} ➡️ {new_model}")
|
logger.debug(f"📌 MODEL MAPPING: {original_model} ➡️ {new_model}")
|
||||||
return new_model
|
v = new_model
|
||||||
|
|
||||||
# Keep the model as is but add openai/ prefix if not already present
|
# Keep the model as is but add openai/ prefix if not already present
|
||||||
if not v.startswith('openai/'):
|
elif not v.startswith('openai/'):
|
||||||
new_model = f"openai/{v}"
|
new_model = f"openai/{v}"
|
||||||
logger.debug(f"📌 MODEL MAPPING: {original_model} ➡️ {new_model}")
|
logger.debug(f"📌 MODEL MAPPING: {original_model} ➡️ {new_model}")
|
||||||
return new_model
|
v = new_model
|
||||||
|
|
||||||
|
# Store the original model in the values dictionary
|
||||||
|
# This will be accessible as request.original_model
|
||||||
|
values = info.data
|
||||||
|
if isinstance(values, dict):
|
||||||
|
values['original_model'] = original_model
|
||||||
|
|
||||||
return v
|
return v
|
||||||
else:
|
else:
|
||||||
# Original behavior - ensure anthropic/ prefix
|
# Original behavior - ensure anthropic/ prefix
|
||||||
|
|
@ -161,6 +183,12 @@ class MessagesRequest(BaseModel):
|
||||||
if not v.startswith('anthropic/'):
|
if not v.startswith('anthropic/'):
|
||||||
new_model = f"anthropic/{v}"
|
new_model = f"anthropic/{v}"
|
||||||
logger.debug(f"📌 MODEL MAPPING: {original_model} ➡️ {new_model}")
|
logger.debug(f"📌 MODEL MAPPING: {original_model} ➡️ {new_model}")
|
||||||
|
|
||||||
|
# Store original model
|
||||||
|
values = info.data
|
||||||
|
if isinstance(values, dict):
|
||||||
|
values['original_model'] = original_model
|
||||||
|
|
||||||
return new_model
|
return new_model
|
||||||
return v
|
return v
|
||||||
|
|
||||||
|
|
@ -171,32 +199,54 @@ class TokenCountRequest(BaseModel):
|
||||||
tools: Optional[List[Tool]] = None
|
tools: Optional[List[Tool]] = None
|
||||||
thinking: Optional[ThinkingConfig] = None
|
thinking: Optional[ThinkingConfig] = None
|
||||||
tool_choice: Optional[Dict[str, Any]] = None
|
tool_choice: Optional[Dict[str, Any]] = None
|
||||||
|
original_model: Optional[str] = None # Will store the original model name
|
||||||
|
|
||||||
@field_validator('model')
|
@field_validator('model')
|
||||||
def validate_model(cls, v):
|
def validate_model(cls, v, info):
|
||||||
|
# Store the original model name
|
||||||
|
original_model = v
|
||||||
|
|
||||||
# Same validation as MessagesRequest
|
# Same validation as MessagesRequest
|
||||||
if USE_OPENAI_MODELS:
|
if USE_OPENAI_MODELS:
|
||||||
original_model = v
|
# Remove anthropic/ prefix if it exists
|
||||||
if v.startswith('anthropic/'):
|
if v.startswith('anthropic/'):
|
||||||
v = v[10:]
|
v = v[10:]
|
||||||
|
|
||||||
|
# Swap Haiku with small model (default: gpt-4o-mini)
|
||||||
if 'haiku' in v.lower():
|
if 'haiku' in v.lower():
|
||||||
new_model = "openai/gpt-4o-mini"
|
new_model = f"openai/{SMALL_MODEL}"
|
||||||
logger.debug(f"📌 MODEL MAPPING: {original_model} ➡️ {new_model}")
|
logger.debug(f"📌 MODEL MAPPING: {original_model} ➡️ {new_model}")
|
||||||
return new_model
|
v = new_model
|
||||||
if 'sonnet' in v.lower():
|
|
||||||
new_model = "openai/gpt-4o"
|
# Swap any Sonnet model with big model (default: gpt-4o)
|
||||||
|
elif 'sonnet' in v.lower():
|
||||||
|
new_model = f"openai/{BIG_MODEL}"
|
||||||
logger.debug(f"📌 MODEL MAPPING: {original_model} ➡️ {new_model}")
|
logger.debug(f"📌 MODEL MAPPING: {original_model} ➡️ {new_model}")
|
||||||
return new_model
|
v = new_model
|
||||||
if not v.startswith('openai/'):
|
|
||||||
|
# Keep the model as is but add openai/ prefix if not already present
|
||||||
|
elif not v.startswith('openai/'):
|
||||||
new_model = f"openai/{v}"
|
new_model = f"openai/{v}"
|
||||||
logger.debug(f"📌 MODEL MAPPING: {original_model} ➡️ {new_model}")
|
logger.debug(f"📌 MODEL MAPPING: {original_model} ➡️ {new_model}")
|
||||||
return new_model
|
v = new_model
|
||||||
|
|
||||||
|
# Store the original model in the values dictionary
|
||||||
|
values = info.data
|
||||||
|
if isinstance(values, dict):
|
||||||
|
values['original_model'] = original_model
|
||||||
|
|
||||||
return v
|
return v
|
||||||
else:
|
else:
|
||||||
original_model = v
|
# Original behavior - ensure anthropic/ prefix
|
||||||
if not v.startswith('anthropic/'):
|
if not v.startswith('anthropic/'):
|
||||||
new_model = f"anthropic/{v}"
|
new_model = f"anthropic/{v}"
|
||||||
logger.debug(f"📌 MODEL MAPPING: {original_model} ➡️ {new_model}")
|
logger.debug(f"📌 MODEL MAPPING: {original_model} ➡️ {new_model}")
|
||||||
|
|
||||||
|
# Store original model
|
||||||
|
values = info.data
|
||||||
|
if isinstance(values, dict):
|
||||||
|
values['original_model'] = original_model
|
||||||
|
|
||||||
return new_model
|
return new_model
|
||||||
return v
|
return v
|
||||||
|
|
||||||
|
|
@ -942,11 +992,21 @@ async def handle_streaming(response_generator, original_request: MessagesRequest
|
||||||
|
|
||||||
@app.post("/v1/messages")
|
@app.post("/v1/messages")
|
||||||
async def create_message(
|
async def create_message(
|
||||||
request: MessagesRequest
|
request: MessagesRequest,
|
||||||
|
raw_request: Request
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
# Log the incoming request with the original model
|
# print the body here
|
||||||
original_model = request.model
|
body = await raw_request.body()
|
||||||
|
|
||||||
|
# Parse the raw body as JSON since it's bytes
|
||||||
|
body_json = json.loads(body.decode('utf-8'))
|
||||||
|
original_model = body_json.get("model", "unknown")
|
||||||
|
|
||||||
|
# Get the display name for logging, just the model name without provider prefix
|
||||||
|
display_model = original_model
|
||||||
|
if "/" in display_model:
|
||||||
|
display_model = display_model.split("/")[-1]
|
||||||
|
|
||||||
# Clean model name for capability check
|
# Clean model name for capability check
|
||||||
clean_model = request.model
|
clean_model = request.model
|
||||||
|
|
@ -957,43 +1017,6 @@ async def create_message(
|
||||||
|
|
||||||
logger.debug(f"📊 PROCESSING REQUEST: Model={request.model}, Stream={request.stream}")
|
logger.debug(f"📊 PROCESSING REQUEST: Model={request.model}, Stream={request.stream}")
|
||||||
|
|
||||||
# # Check if model supports function calling
|
|
||||||
# supports_tools = False
|
|
||||||
# try:
|
|
||||||
# if clean_model.startswith("gpt-") or clean_model.startswith("claude-"):
|
|
||||||
# import litellm
|
|
||||||
# supports_tools = litellm.supports_function_calling(model=clean_model)
|
|
||||||
# logger.debug(f"Model {clean_model} supports function calling: {supports_tools}")
|
|
||||||
# except Exception as e:
|
|
||||||
# logger.warning(f"Error checking if model supports function calling: {str(e)}")
|
|
||||||
# # Default to assuming Claude models support tool use and OpenAI supports function calling
|
|
||||||
# supports_tools = "claude" in clean_model or "gpt" in clean_model
|
|
||||||
|
|
||||||
# # Check if we're using tools but the model doesn't support them
|
|
||||||
# has_tools = request.tools is not None and len(request.tools) > 0
|
|
||||||
# if has_tools and not supports_tools:
|
|
||||||
# logger.warning(f"Model {clean_model} doesn't support tools but request has tools. Will convert to text-only format.")
|
|
||||||
|
|
||||||
# # If we're trying to use tools with an unsupported model, remove tools from the request
|
|
||||||
# # and add a warning message for the user
|
|
||||||
# original_messages = request.messages
|
|
||||||
# if len(original_messages) > 0 and original_messages[-1].role == "user":
|
|
||||||
# content = original_messages[-1].content
|
|
||||||
# if isinstance(content, str):
|
|
||||||
# # Add warning to string content
|
|
||||||
# content = content + "\n\n[Note: Tools were specified in this request, but the model doesn't support tools. I'll answer without using tools.]"
|
|
||||||
# original_messages[-1].content = content
|
|
||||||
# elif isinstance(content, list):
|
|
||||||
# # Find text blocks and add warning
|
|
||||||
# for i, block in enumerate(content):
|
|
||||||
# if hasattr(block, 'type') and block.type == 'text':
|
|
||||||
# block.text = block.text + "\n\n[Note: Tools were specified in this request, but the model doesn't support tools. I'll answer without using tools.]"
|
|
||||||
# break
|
|
||||||
|
|
||||||
# # Remove tools from request
|
|
||||||
# request.tools = None
|
|
||||||
# request.tool_choice = None
|
|
||||||
|
|
||||||
# Convert Anthropic request to LiteLLM format
|
# Convert Anthropic request to LiteLLM format
|
||||||
litellm_request = convert_anthropic_to_litellm(request)
|
litellm_request = convert_anthropic_to_litellm(request)
|
||||||
|
|
||||||
|
|
@ -1149,7 +1172,17 @@ async def create_message(
|
||||||
# Handle streaming mode
|
# Handle streaming mode
|
||||||
if request.stream:
|
if request.stream:
|
||||||
# Use LiteLLM for streaming
|
# Use LiteLLM for streaming
|
||||||
print(f"🚀 Sending {len(litellm_request['messages'])} messages to {litellm_request.get('model')}")
|
num_tools = len(request.tools) if request.tools else 0
|
||||||
|
|
||||||
|
log_request_beautifully(
|
||||||
|
"POST",
|
||||||
|
raw_request.url.path,
|
||||||
|
display_model,
|
||||||
|
litellm_request.get('model'),
|
||||||
|
len(litellm_request['messages']),
|
||||||
|
num_tools,
|
||||||
|
200 # Assuming success at this point
|
||||||
|
)
|
||||||
# Ensure we use the async version for streaming
|
# Ensure we use the async version for streaming
|
||||||
response_generator = await litellm.acompletion(**litellm_request)
|
response_generator = await litellm.acompletion(**litellm_request)
|
||||||
|
|
||||||
|
|
@ -1159,7 +1192,17 @@ async def create_message(
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# Use LiteLLM for regular completion
|
# Use LiteLLM for regular completion
|
||||||
logger.debug(f"🚀 SENDING REQUEST: Original={original_model}, Actual={litellm_request.get('model')}")
|
num_tools = len(request.tools) if request.tools else 0
|
||||||
|
|
||||||
|
log_request_beautifully(
|
||||||
|
"POST",
|
||||||
|
raw_request.url.path,
|
||||||
|
display_model,
|
||||||
|
litellm_request.get('model'),
|
||||||
|
len(litellm_request['messages']),
|
||||||
|
num_tools,
|
||||||
|
200 # Assuming success at this point
|
||||||
|
)
|
||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
litellm_response = litellm.completion(**litellm_request)
|
litellm_response = litellm.completion(**litellm_request)
|
||||||
logger.debug(f"✅ RESPONSE RECEIVED: Model={litellm_request.get('model')}, Time={time.time() - start_time:.2f}s")
|
logger.debug(f"✅ RESPONSE RECEIVED: Model={litellm_request.get('model')}, Time={time.time() - start_time:.2f}s")
|
||||||
|
|
@ -1207,12 +1250,24 @@ async def create_message(
|
||||||
|
|
||||||
@app.post("/v1/messages/count_tokens")
|
@app.post("/v1/messages/count_tokens")
|
||||||
async def count_tokens(
|
async def count_tokens(
|
||||||
request: TokenCountRequest
|
request: TokenCountRequest,
|
||||||
|
raw_request: Request
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
# Log the incoming token count request
|
# Log the incoming token count request
|
||||||
original_model = request.model
|
original_model = request.original_model or request.model
|
||||||
logger.debug(f"📊 TOKEN COUNT REQUEST: Model={request.model}")
|
|
||||||
|
# Get the display name for logging, just the model name without provider prefix
|
||||||
|
display_model = original_model
|
||||||
|
if "/" in display_model:
|
||||||
|
display_model = display_model.split("/")[-1]
|
||||||
|
|
||||||
|
# Clean model name for capability check
|
||||||
|
clean_model = request.model
|
||||||
|
if clean_model.startswith("anthropic/"):
|
||||||
|
clean_model = clean_model[len("anthropic/"):]
|
||||||
|
elif clean_model.startswith("openai/"):
|
||||||
|
clean_model = clean_model[len("openai/"):]
|
||||||
|
|
||||||
# Convert the messages to a format LiteLLM can understand
|
# Convert the messages to a format LiteLLM can understand
|
||||||
converted_request = convert_anthropic_to_litellm(
|
converted_request = convert_anthropic_to_litellm(
|
||||||
|
|
@ -1232,14 +1287,25 @@ async def count_tokens(
|
||||||
# Import token_counter function
|
# Import token_counter function
|
||||||
from litellm import token_counter
|
from litellm import token_counter
|
||||||
|
|
||||||
|
# Log the request beautifully
|
||||||
|
num_tools = len(request.tools) if request.tools else 0
|
||||||
|
|
||||||
|
log_request_beautifully(
|
||||||
|
"POST",
|
||||||
|
raw_request.url.path,
|
||||||
|
display_model,
|
||||||
|
converted_request.get('model'),
|
||||||
|
len(converted_request['messages']),
|
||||||
|
num_tools,
|
||||||
|
200 # Assuming success at this point
|
||||||
|
)
|
||||||
|
|
||||||
# Count tokens
|
# Count tokens
|
||||||
token_count = token_counter(
|
token_count = token_counter(
|
||||||
model=converted_request["model"],
|
model=converted_request["model"],
|
||||||
messages=converted_request["messages"],
|
messages=converted_request["messages"],
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.debug(f"🔢 TOKEN COUNT RESULT: Original={original_model}, Actual={converted_request['model']}, Count={token_count}")
|
|
||||||
|
|
||||||
# Return Anthropic-style response
|
# Return Anthropic-style response
|
||||||
return TokenCountResponse(input_tokens=token_count)
|
return TokenCountResponse(input_tokens=token_count)
|
||||||
|
|
||||||
|
|
@ -1258,9 +1324,56 @@ async def count_tokens(
|
||||||
async def root():
|
async def root():
|
||||||
return {"message": "Anthropic Proxy for LiteLLM"}
|
return {"message": "Anthropic Proxy for LiteLLM"}
|
||||||
|
|
||||||
|
# Define ANSI color codes for terminal output
|
||||||
|
class Colors:
|
||||||
|
CYAN = "\033[96m"
|
||||||
|
BLUE = "\033[94m"
|
||||||
|
GREEN = "\033[92m"
|
||||||
|
YELLOW = "\033[93m"
|
||||||
|
RED = "\033[91m"
|
||||||
|
MAGENTA = "\033[95m"
|
||||||
|
RESET = "\033[0m"
|
||||||
|
BOLD = "\033[1m"
|
||||||
|
UNDERLINE = "\033[4m"
|
||||||
|
DIM = "\033[2m"
|
||||||
|
def log_request_beautifully(method, path, claude_model, openai_model, num_messages, num_tools, status_code):
|
||||||
|
"""Log requests in a beautiful, twitter-friendly format showing Claude to OpenAI mapping."""
|
||||||
|
# Format the Claude model name nicely
|
||||||
|
claude_display = f"{Colors.CYAN}{claude_model}{Colors.RESET}"
|
||||||
|
|
||||||
|
# Extract endpoint name
|
||||||
|
endpoint = path
|
||||||
|
if "?" in endpoint:
|
||||||
|
endpoint = endpoint.split("?")[0]
|
||||||
|
|
||||||
|
# Extract just the OpenAI model name without provider prefix
|
||||||
|
openai_display = openai_model
|
||||||
|
if "/" in openai_display:
|
||||||
|
openai_display = openai_display.split("/")[-1]
|
||||||
|
openai_display = f"{Colors.GREEN}{openai_display}{Colors.RESET}"
|
||||||
|
|
||||||
|
# Format tools and messages
|
||||||
|
tools_str = f"{Colors.MAGENTA}{num_tools} tools{Colors.RESET}"
|
||||||
|
messages_str = f"{Colors.BLUE}{num_messages} messages{Colors.RESET}"
|
||||||
|
|
||||||
|
# Format status code
|
||||||
|
status_str = f"{Colors.GREEN}✓ {status_code} OK{Colors.RESET}" if status_code == 200 else f"{Colors.RED}✗ {status_code}{Colors.RESET}"
|
||||||
|
|
||||||
|
|
||||||
|
# Put it all together in a clear, beautiful format
|
||||||
|
log_line = f"{Colors.BOLD}{method} {endpoint}{Colors.RESET} {status_str}"
|
||||||
|
model_line = f"{claude_display} → {openai_display} {tools_str} {messages_str}"
|
||||||
|
|
||||||
|
# Print to console
|
||||||
|
print(log_line)
|
||||||
|
print(model_line)
|
||||||
|
sys.stdout.flush()
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
import sys
|
import sys
|
||||||
if len(sys.argv) > 1 and sys.argv[1] == "--help":
|
if len(sys.argv) > 1 and sys.argv[1] == "--help":
|
||||||
print("Run with: uvicorn server:app --reload --host 0.0.0.0 --port 8082")
|
print("Run with: uvicorn server:app --reload --host 0.0.0.0 --port 8082")
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
uvicorn.run(app, host="0.0.0.0", port=8082)
|
|
||||||
|
# Configure uvicorn to run with minimal logs
|
||||||
|
uvicorn.run(app, host="0.0.0.0", port=8082, log_level="error")
|
||||||
Loading…
Reference in a new issue