回退旧版本

This commit is contained in:
h88782481 2026-03-26 11:34:27 +08:00
parent cd577d17c3
commit a8f5ada8e1
9 changed files with 1582 additions and 1216 deletions

View file

@ -12,6 +12,7 @@ import logging
from typing import Any
import settings
from utils.http import build_anthropic_headers, build_gemini_headers, build_openai_headers
logger = logging.getLogger(__name__)
@ -54,6 +55,42 @@ def build_route_context(client_model: str, is_stream: bool) -> RouteContext:
)
def build_openai_target(ctx: RouteContext) -> tuple[str, dict[str, str]]:
"""根据路由上下文生成 OpenAI 兼容后端的地址和请求头。"""
url = f'{ctx.target_url.rstrip("/")}/v1/chat/completions'
headers = build_openai_headers(ctx.api_key)
return url, headers
def build_responses_target(ctx: RouteContext) -> tuple[str, dict[str, str]]:
"""根据路由上下文生成 OpenAI Responses 后端的地址和请求头。"""
url = f'{ctx.target_url.rstrip("/")}/v1/responses'
headers = build_openai_headers(ctx.api_key)
return url, headers
def build_anthropic_target(ctx: RouteContext) -> tuple[str, dict[str, str]]:
"""根据路由上下文生成 Anthropic 后端的地址和请求头。"""
url = f'{ctx.target_url.rstrip("/")}/v1/messages'
headers = build_anthropic_headers(ctx.api_key)
return url, headers
def build_gemini_target(ctx: RouteContext, stream: bool = False) -> tuple[str, dict[str, str]]:
"""根据路由上下文生成 Gemini 后端的地址和请求头。
Gemini URL 格式: {base}/v1/models/{model}:generateContent
流式: {base}/v1/models/{model}:streamGenerateContent?alt=sse
"""
base = ctx.target_url.rstrip('/')
model = ctx.upstream_model
if stream:
url = f'{base}/v1/models/{model}:streamGenerateContent?alt=sse'
else:
url = f'{base}/v1/models/{model}:generateContent'
headers = build_gemini_headers(ctx.api_key)
return url, headers
def log_route_context(route_name: str, ctx: RouteContext, *, extra: str = '') -> None:
"""统一输出路由级日志,避免不同入口的日志格式逐渐漂移。"""
@ -100,6 +137,11 @@ def sse_event_message(event_type: str, data: Any) -> str:
return f'event: {event_type}\ndata: {payload}\n\n'
def chat_error_chunk(message: str, error_type: str = 'upstream_error') -> str:
"""构造聊天补全流式接口使用的错误消息。"""
return sse_data_message({'error': {'message': message, 'type': error_type}})
def responses_error_event(message: str) -> str:
"""构造 Responses 流式接口使用的错误事件。"""
return sse_event_message('error', {'error': message})
@ -173,20 +215,6 @@ def inject_instructions_anthropic(payload: dict[str, Any], instructions: str, po
return payload
def should_inject_thinking(backend: str) -> bool:
"""判断当前后端是否需要注入历史 thinking。
仅对明确能消费历史 reasoning/thinking 的后端启用
- anthropic
- gemini
- responses
OpenAI Chat 兼容后端通常不接受 `reasoning_content` 历史字段
若注入会导致上游报错因此显式排除
"""
return backend in ('anthropic', 'gemini', 'responses')
# ─── Body / Header 修改 ──────────────────────────
@ -220,140 +248,3 @@ def apply_header_modifications(headers: dict[str, str], modifications: dict[str,
headers[key] = str(value)
logger.info('已应用 header_modifications: %s', list(modifications.keys()))
return headers
# ═══════════════════════════════════════════════════════════
# 后端注册表 + ClientFormatter 实现
# ═══════════════════════════════════════════════════════════
def get_outbound(backend: str):
"""根据后端类型获取对应的 OutboundTransformer 实例。"""
from adapters.cc_anthropic_adapter import AnthropicOutbound
from adapters.cc_gemini_adapter import GeminiOutbound
from adapters.openai_compat_fixer import OpenAIChatOutbound
from adapters.responses_cc_adapter import ResponsesOutbound
registry = {
'openai': OpenAIChatOutbound,
'anthropic': AnthropicOutbound,
'gemini': GeminiOutbound,
'responses': ResponsesOutbound,
}
cls = registry.get(backend, OpenAIChatOutbound)
return cls()
class CCClientFormatter:
"""Chat Completions 客户端格式化器。
将通用处理结果格式化为 OpenAI Chat Completions 格式
/v1/chat/completions 端点使用
"""
def format_response(self, cc_response: dict[str, Any], model: str) -> dict[str, Any]:
cc_response['model'] = model
return cc_response
def wrap_stream_item(self, item: Any) -> str:
payload = item if isinstance(item, str) else json.dumps(item, ensure_ascii=False)
return f'data: {payload}\n\n'
def format_error(self, message: str) -> str:
return sse_data_message({'error': {'message': message, 'type': 'upstream_error'}})
def format_done(self) -> str | None:
return sse_data_message('[DONE]')
def start_events(self) -> list[str]:
return []
@property
def usage_input_key(self) -> str:
return 'prompt_tokens'
@property
def usage_output_key(self) -> str:
return 'completion_tokens'
class ResponsesClientFormatter:
"""Responses API 客户端格式化器。
将通用处理结果格式化为 OpenAI Responses 格式
/v1/responses 端点使用
流式场景使用 ResponsesStreamConverter CC chunk Responses SSE 转换
"""
def __init__(self, model: str = ''):
from adapters.responses_cc_adapter import ResponsesStreamConverter, cc_to_responses
self._model = model
self._converter = ResponsesStreamConverter(model=model)
self._cc_to_responses = cc_to_responses
def format_response(self, cc_response: dict[str, Any], model: str) -> dict[str, Any]:
return self._cc_to_responses(cc_response, model)
def wrap_stream_item(self, item: Any) -> str:
if isinstance(item, str):
return item
events = self._converter.process_cc_chunk(item)
return ''.join(events)
def format_error(self, message: str) -> str:
return responses_error_event(message)
def format_done(self) -> str | None:
events = self._converter.finalize()
return ''.join(events) if events else None
def start_events(self) -> list[str]:
return self._converter.start_events()
@property
def usage_input_key(self) -> str:
return 'input_tokens'
@property
def usage_output_key(self) -> str:
return 'output_tokens'
class ResponsesPassthroughFormatter:
"""Responses 透传格式化器。
当后端本身就是 Responses 格式时使用做轻量模型名改写
"""
def __init__(self, model: str = ''):
self._model = model
def format_response(self, response_data: dict[str, Any], model: str) -> dict[str, Any]:
response_data['model'] = model
return response_data
def wrap_stream_item(self, item: Any) -> str:
if isinstance(item, str):
return item
event_type = item.pop('_sse_event_type', None)
if event_type:
return f'event: {event_type}\ndata: {json.dumps(item, ensure_ascii=False)}\n\n'
return f'data: {json.dumps(item, ensure_ascii=False)}\n\n'
def format_error(self, message: str) -> str:
return responses_error_event(message)
def format_done(self) -> str | None:
return None
def start_events(self) -> list[str]:
return []
@property
def usage_input_key(self) -> str:
return 'input_tokens'
@property
def usage_output_key(self) -> str:
return 'output_tokens'