refac
This commit is contained in:
@@ -89,6 +89,26 @@ async def get_anthropic_models(url: str, key: str, user: UserModel = None) -> di
|
|||||||
##############################
|
##############################
|
||||||
|
|
||||||
|
|
||||||
|
def _copy_cache_control(source: dict, target: dict) -> dict:
|
||||||
|
if isinstance(source, dict) and 'cache_control' in source:
|
||||||
|
target['cache_control'] = source['cache_control']
|
||||||
|
return target
|
||||||
|
|
||||||
|
|
||||||
|
def _has_cache_control(blocks: list) -> bool:
|
||||||
|
return any(isinstance(block, dict) and 'cache_control' in block for block in blocks)
|
||||||
|
|
||||||
|
|
||||||
|
def _finalize_openai_content(blocks: list) -> str | list:
|
||||||
|
if not blocks:
|
||||||
|
return ''
|
||||||
|
|
||||||
|
if len(blocks) == 1 and blocks[0].get('type') == 'text' and not _has_cache_control(blocks):
|
||||||
|
return blocks[0].get('text', '')
|
||||||
|
|
||||||
|
return blocks
|
||||||
|
|
||||||
|
|
||||||
def convert_anthropic_to_openai_payload(anthropic_payload: dict) -> dict:
|
def convert_anthropic_to_openai_payload(anthropic_payload: dict) -> dict:
|
||||||
"""
|
"""
|
||||||
Convert an Anthropic Messages API request to OpenAI Chat Completions format.
|
Convert an Anthropic Messages API request to OpenAI Chat Completions format.
|
||||||
@@ -112,14 +132,21 @@ def convert_anthropic_to_openai_payload(anthropic_payload: dict) -> dict:
|
|||||||
if isinstance(system, str):
|
if isinstance(system, str):
|
||||||
messages.append({'role': 'system', 'content': system})
|
messages.append({'role': 'system', 'content': system})
|
||||||
elif isinstance(system, list):
|
elif isinstance(system, list):
|
||||||
# Anthropic supports system as list of content blocks
|
openai_content = []
|
||||||
text_parts = []
|
|
||||||
for block in system:
|
for block in system:
|
||||||
if isinstance(block, dict) and block.get('type') == 'text':
|
if isinstance(block, dict) and block.get('type') == 'text':
|
||||||
text_parts.append(block.get('text', ''))
|
openai_content.append(
|
||||||
|
_copy_cache_control(
|
||||||
|
block,
|
||||||
|
{
|
||||||
|
'type': 'text',
|
||||||
|
'text': block.get('text', ''),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
)
|
||||||
elif isinstance(block, str):
|
elif isinstance(block, str):
|
||||||
text_parts.append(block)
|
openai_content.append({'type': 'text', 'text': block})
|
||||||
messages.append({'role': 'system', 'content': '\n'.join(text_parts)})
|
messages.append({'role': 'system', 'content': _finalize_openai_content(openai_content)})
|
||||||
|
|
||||||
# Convert messages
|
# Convert messages
|
||||||
for msg in anthropic_payload.get('messages', []):
|
for msg in anthropic_payload.get('messages', []):
|
||||||
@@ -138,10 +165,13 @@ def convert_anthropic_to_openai_payload(anthropic_payload: dict) -> dict:
|
|||||||
|
|
||||||
if block_type == 'text':
|
if block_type == 'text':
|
||||||
openai_content.append(
|
openai_content.append(
|
||||||
{
|
_copy_cache_control(
|
||||||
'type': 'text',
|
block,
|
||||||
'text': block.get('text', ''),
|
{
|
||||||
}
|
'type': 'text',
|
||||||
|
'text': block.get('text', ''),
|
||||||
|
},
|
||||||
|
)
|
||||||
)
|
)
|
||||||
elif block_type == 'image':
|
elif block_type == 'image':
|
||||||
source = block.get('source', {})
|
source = block.get('source', {})
|
||||||
@@ -149,19 +179,25 @@ def convert_anthropic_to_openai_payload(anthropic_payload: dict) -> dict:
|
|||||||
media_type = source.get('media_type', 'image/png')
|
media_type = source.get('media_type', 'image/png')
|
||||||
data = source.get('data', '')
|
data = source.get('data', '')
|
||||||
openai_content.append(
|
openai_content.append(
|
||||||
{
|
_copy_cache_control(
|
||||||
'type': 'image_url',
|
block,
|
||||||
'image_url': {
|
{
|
||||||
'url': f'data:{media_type};base64,{data}',
|
'type': 'image_url',
|
||||||
|
'image_url': {
|
||||||
|
'url': f'data:{media_type};base64,{data}',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
)
|
||||||
)
|
)
|
||||||
elif source.get('type') == 'url':
|
elif source.get('type') == 'url':
|
||||||
openai_content.append(
|
openai_content.append(
|
||||||
{
|
_copy_cache_control(
|
||||||
'type': 'image_url',
|
block,
|
||||||
'image_url': {'url': source.get('url', '')},
|
{
|
||||||
}
|
'type': 'image_url',
|
||||||
|
'image_url': {'url': source.get('url', '')},
|
||||||
|
},
|
||||||
|
)
|
||||||
)
|
)
|
||||||
elif block_type == 'tool_use':
|
elif block_type == 'tool_use':
|
||||||
tool_calls.append(
|
tool_calls.append(
|
||||||
@@ -196,10 +232,13 @@ def convert_anthropic_to_openai_payload(anthropic_payload: dict) -> dict:
|
|||||||
|
|
||||||
if content_type == 'text':
|
if content_type == 'text':
|
||||||
converted_parts.append(
|
converted_parts.append(
|
||||||
{
|
_copy_cache_control(
|
||||||
'type': 'text',
|
content_block,
|
||||||
'text': content_block.get('text', ''),
|
{
|
||||||
}
|
'type': 'text',
|
||||||
|
'text': content_block.get('text', ''),
|
||||||
|
},
|
||||||
|
)
|
||||||
)
|
)
|
||||||
elif content_type == 'image':
|
elif content_type == 'image':
|
||||||
source = content_block.get('source', {})
|
source = content_block.get('source', {})
|
||||||
@@ -207,21 +246,27 @@ def convert_anthropic_to_openai_payload(anthropic_payload: dict) -> dict:
|
|||||||
media_type = source.get('media_type', 'image/png')
|
media_type = source.get('media_type', 'image/png')
|
||||||
data = source.get('data', '')
|
data = source.get('data', '')
|
||||||
converted_parts.append(
|
converted_parts.append(
|
||||||
{
|
_copy_cache_control(
|
||||||
'type': 'image_url',
|
content_block,
|
||||||
'image_url': {
|
{
|
||||||
'url': f'data:{media_type};base64,{data}',
|
'type': 'image_url',
|
||||||
|
'image_url': {
|
||||||
|
'url': f'data:{media_type};base64,{data}',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
)
|
||||||
)
|
)
|
||||||
elif source.get('type') == 'url':
|
elif source.get('type') == 'url':
|
||||||
converted_parts.append(
|
converted_parts.append(
|
||||||
{
|
_copy_cache_control(
|
||||||
'type': 'image_url',
|
content_block,
|
||||||
'image_url': {
|
{
|
||||||
'url': source.get('url', ''),
|
'type': 'image_url',
|
||||||
|
'image_url': {
|
||||||
|
'url': source.get('url', ''),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
)
|
||||||
)
|
)
|
||||||
elif content_type == 'document':
|
elif content_type == 'document':
|
||||||
# Documents have no direct OpenAI equivalent;
|
# Documents have no direct OpenAI equivalent;
|
||||||
@@ -254,7 +299,9 @@ def convert_anthropic_to_openai_payload(anthropic_payload: dict) -> dict:
|
|||||||
converted_parts.append({'type': 'text', 'text': search_text})
|
converted_parts.append({'type': 'text', 'text': search_text})
|
||||||
|
|
||||||
# Flatten to string when only text parts are present
|
# Flatten to string when only text parts are present
|
||||||
if all(part.get('type') == 'text' for part in converted_parts):
|
if all(part.get('type') == 'text' for part in converted_parts) and not _has_cache_control(
|
||||||
|
converted_parts
|
||||||
|
):
|
||||||
tool_content = '\n'.join(part.get('text', '') for part in converted_parts)
|
tool_content = '\n'.join(part.get('text', '') for part in converted_parts)
|
||||||
elif converted_parts:
|
elif converted_parts:
|
||||||
tool_content = converted_parts
|
tool_content = converted_parts
|
||||||
@@ -287,21 +334,13 @@ def convert_anthropic_to_openai_payload(anthropic_payload: dict) -> dict:
|
|||||||
# Assistant message with tool calls
|
# Assistant message with tool calls
|
||||||
msg_dict = {'role': role}
|
msg_dict = {'role': role}
|
||||||
if openai_content:
|
if openai_content:
|
||||||
# If there's only text, flatten it
|
msg_dict['content'] = _finalize_openai_content(openai_content)
|
||||||
if len(openai_content) == 1 and openai_content[0]['type'] == 'text':
|
|
||||||
msg_dict['content'] = openai_content[0]['text']
|
|
||||||
else:
|
|
||||||
msg_dict['content'] = openai_content
|
|
||||||
else:
|
else:
|
||||||
msg_dict['content'] = ''
|
msg_dict['content'] = ''
|
||||||
msg_dict['tool_calls'] = tool_calls
|
msg_dict['tool_calls'] = tool_calls
|
||||||
messages.append(msg_dict)
|
messages.append(msg_dict)
|
||||||
elif openai_content:
|
elif openai_content:
|
||||||
# If there's only a single text block, flatten it to a string
|
messages.append({'role': role, 'content': _finalize_openai_content(openai_content)})
|
||||||
if len(openai_content) == 1 and openai_content[0]['type'] == 'text':
|
|
||||||
messages.append({'role': role, 'content': openai_content[0]['text']})
|
|
||||||
else:
|
|
||||||
messages.append({'role': role, 'content': openai_content})
|
|
||||||
else:
|
else:
|
||||||
messages.append({'role': role, 'content': str(content) if content else ''})
|
messages.append({'role': role, 'content': str(content) if content else ''})
|
||||||
|
|
||||||
@@ -312,7 +351,7 @@ def convert_anthropic_to_openai_payload(anthropic_payload: dict) -> dict:
|
|||||||
openai_payload['max_tokens'] = anthropic_payload['max_tokens']
|
openai_payload['max_tokens'] = anthropic_payload['max_tokens']
|
||||||
|
|
||||||
# Common parameters
|
# Common parameters
|
||||||
for param in ('temperature', 'top_p', 'stop_sequences', 'stream'):
|
for param in ('temperature', 'top_p', 'top_k', 'stop_sequences', 'stream', 'metadata', 'service_tier'):
|
||||||
if param in anthropic_payload:
|
if param in anthropic_payload:
|
||||||
if param == 'stop_sequences':
|
if param == 'stop_sequences':
|
||||||
openai_payload['stop'] = anthropic_payload[param]
|
openai_payload['stop'] = anthropic_payload[param]
|
||||||
@@ -324,14 +363,17 @@ def convert_anthropic_to_openai_payload(anthropic_payload: dict) -> dict:
|
|||||||
openai_tools = []
|
openai_tools = []
|
||||||
for tool in anthropic_payload['tools']:
|
for tool in anthropic_payload['tools']:
|
||||||
openai_tools.append(
|
openai_tools.append(
|
||||||
{
|
_copy_cache_control(
|
||||||
'type': 'function',
|
tool,
|
||||||
'function': {
|
{
|
||||||
'name': tool.get('name', ''),
|
'type': 'function',
|
||||||
'description': tool.get('description', ''),
|
'function': {
|
||||||
'parameters': tool.get('input_schema', {}),
|
'name': tool.get('name', ''),
|
||||||
|
'description': tool.get('description', ''),
|
||||||
|
'parameters': tool.get('input_schema', {}),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
)
|
||||||
)
|
)
|
||||||
openai_payload['tools'] = openai_tools
|
openai_payload['tools'] = openai_tools
|
||||||
|
|
||||||
@@ -382,7 +424,7 @@ def convert_openai_to_anthropic_response(openai_response: dict, model: str = '')
|
|||||||
content.append({'type': 'text', 'text': message_content})
|
content.append({'type': 'text', 'text': message_content})
|
||||||
|
|
||||||
# Tool calls -> tool_use blocks
|
# Tool calls -> tool_use blocks
|
||||||
tool_calls = message.get('tool_calls', [])
|
tool_calls = message.get('tool_calls') or []
|
||||||
for tool_call in tool_calls:
|
for tool_call in tool_calls:
|
||||||
function = tool_call.get('function', {})
|
function = tool_call.get('function', {})
|
||||||
try:
|
try:
|
||||||
@@ -404,6 +446,10 @@ def convert_openai_to_anthropic_response(openai_response: dict, model: str = '')
|
|||||||
'input_tokens': openai_usage.get('prompt_tokens', 0),
|
'input_tokens': openai_usage.get('prompt_tokens', 0),
|
||||||
'output_tokens': openai_usage.get('completion_tokens', 0),
|
'output_tokens': openai_usage.get('completion_tokens', 0),
|
||||||
}
|
}
|
||||||
|
if 'cache_creation_input_tokens' in openai_usage:
|
||||||
|
usage['cache_creation_input_tokens'] = openai_usage['cache_creation_input_tokens']
|
||||||
|
if 'cache_read_input_tokens' in openai_usage:
|
||||||
|
usage['cache_read_input_tokens'] = openai_usage['cache_read_input_tokens']
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'id': openai_response.get('id', f'msg_{_uuid.uuid4().hex[:24]}'),
|
'id': openai_response.get('id', f'msg_{_uuid.uuid4().hex[:24]}'),
|
||||||
@@ -703,4 +749,3 @@ async def openai_stream_to_anthropic_stream(openai_stream_generator, model: str
|
|||||||
|
|
||||||
# Emit message_stop
|
# Emit message_stop
|
||||||
yield f'event: message_stop\ndata: {json.dumps({"type": "message_stop"})}\n\n'.encode()
|
yield f'event: message_stop\ndata: {json.dumps({"type": "message_stop"})}\n\n'.encode()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user