From f193c48ce16f3f4a8d7ab82b3b786f77e9cda645 Mon Sep 17 00:00:00 2001 From: h88782481 <54714341+h88782481@users.noreply.github.com> Date: Tue, 10 Mar 2026 08:43:07 +0800 Subject: [PATCH] =?UTF-8?q?=E8=A1=A5=E5=85=A8=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- adapters/cc_anthropic_adapter.py | 3 ++- adapters/responses_cc_adapter.py | 45 +++++++++++++++++++++++++++++++- app.py | 14 ++++++++++ config.py | 6 +++++ routes/admin.py | 15 ++++++++++- routes/chat.py | 3 +++ routes/common.py | 6 ++++- routes/messages.py | 4 ++- routes/responses.py | 3 +++ settings.py | 24 ++++++++++++----- start.py | 1 + utils/http.py | 4 +-- utils/think_tag.py | 1 + 13 files changed, 115 insertions(+), 14 deletions(-) diff --git a/adapters/cc_anthropic_adapter.py b/adapters/cc_anthropic_adapter.py index 55d7107..4f42cad 100644 --- a/adapters/cc_anthropic_adapter.py +++ b/adapters/cc_anthropic_adapter.py @@ -116,6 +116,7 @@ class AnthropicStreamConverter: """ def __init__(self, request_id: str | None = None): + """初始化流式转换所需的请求标识、工具索引和 usage 状态。""" self._id = request_id or gen_id('chatcmpl-') self._tool_index = -1 self._input_tokens = 0 @@ -509,7 +510,7 @@ def _convert_tools(tools: Any) -> list[JsonDict]: def _convert_tool_definition(tool: Any) -> JsonDict | None: - """转换单个工具定义。""" + """将 OpenAI 或 Anthropic 风格的工具定义统一转换为 Anthropic `tools` 项。""" if not isinstance(tool, dict): return None diff --git a/adapters/responses_cc_adapter.py b/adapters/responses_cc_adapter.py index a3eb966..f243e7d 100644 --- a/adapters/responses_cc_adapter.py +++ b/adapters/responses_cc_adapter.py @@ -128,7 +128,11 @@ def responses_to_cc_response(response_data: JsonDict, model: str = '') -> JsonDi @dataclass class _ToolBuffer: - """缓存单个工具调用的流式状态。""" + """缓存单个工具调用的流式状态。 + + Responses 风格的 function_call 会把名称、参数增量和完成时机拆散在多个事件里, + 转换器需要用这个缓冲结构暂存工具标识与累计参数,便于后续按顺序补齐事件。 + """ name: str args: str @@ -155,6 +159,7 @@ class ResponsesStreamConverter: """ def __init__(self, response_id: str | None = None, model: str = ''): + """初始化 Responses 流式状态机所需的各类缓冲区与标识。""" self.resp_id = response_id or gen_id('resp_') self.model = model @@ -366,6 +371,7 @@ class ResponsesStreamConverter: return events def _on_tool_call(self, tool_call: JsonDict) -> list[str]: + """处理来自 CC chunk 的工具调用增量,并映射成 Responses function_call 事件。""" events: list[str] = [] index = tool_call.get('index', 0) function_data = tool_call.get('function') or {} @@ -382,6 +388,7 @@ class ResponsesStreamConverter: return events def _ensure_text_started(self) -> list[str]: + """确保 assistant 文本输出项已开启,并在必要时先关闭 reasoning 项。""" events: list[str] = [] if self._rs_started and not self._rs_closed: events.extend(self._close_reasoning()) @@ -401,6 +408,7 @@ class ResponsesStreamConverter: return events def _start_tool_from_block(self, block: JsonDict) -> list[str]: + """根据 Anthropic `tool_use` block 创建对应的 function_call 输出项。""" return self._start_tool( index=len(self._tools), call_id=block.get('id', gen_id('toolu_')), @@ -408,6 +416,7 @@ class ResponsesStreamConverter: ) def _start_tool(self, *, index: int, call_id: str, name: str) -> list[str]: + """开启一个新的 Responses function_call 输出项,并关闭前置输出段。""" events: list[str] = [] if self._rs_started and not self._rs_closed: events.extend(self._close_reasoning()) @@ -432,6 +441,7 @@ class ResponsesStreamConverter: return events def _append_tool_arguments(self, index: int, arguments_delta: str) -> list[str]: + """向指定 function_call 缓冲区追加参数增量,并发出对应 SSE 事件。""" buffer = self._tools[index] buffer.args += arguments_delta return [self._sse('response.function_call_arguments.delta', { @@ -440,6 +450,7 @@ class ResponsesStreamConverter: })] def _close_reasoning(self) -> list[str]: + """关闭 reasoning 输出项,并补发 summary 完成事件。""" if self._rs_closed: return [] self._rs_closed = True @@ -459,6 +470,7 @@ class ResponsesStreamConverter: ] def _close_text(self) -> list[str]: + """关闭 assistant 文本输出项,并补发文本完成事件。""" if self._text_closed: return [] self._text_closed = True @@ -525,6 +537,7 @@ class ResponsesStreamConverter: return events def _sse(self, event_type: str, data: JsonDict) -> str: + """将事件类型与负载编码为标准 Responses SSE 字符串。""" return f'event: {event_type}\ndata: {json.dumps(data, ensure_ascii=False)}\n\n' def _rewrite_top_level_model(self, payload: JsonDict) -> JsonDict: @@ -554,6 +567,7 @@ class ResponsesToCCStreamConverter: """ def __init__(self, request_id: str | None = None, model: str = ''): + """初始化 Responses → Chat Completions 流式转换所需的状态。""" self._id = request_id or gen_id('chatcmpl-') self._model = model self._tool_index = 0 @@ -652,6 +666,7 @@ class ResponsesToCCStreamConverter: def _copy_request_options(payload: JsonDict, result: JsonDict) -> None: + """将 Responses 请求中的通用选项复制到 CC 请求体。""" if 'tools' in payload: result['tools'] = _convert_tools(payload['tools']) for key in ('temperature', 'top_p'): @@ -664,6 +679,7 @@ def _copy_request_options(payload: JsonDict, result: JsonDict) -> None: def _copy_responses_request_options(payload: JsonDict, result: JsonDict) -> None: + """将聊天补全请求中的通用选项复制到原生 Responses 请求体。""" if 'tools' in payload: result['tools'] = _convert_cc_tools_to_responses(payload['tools']) for key in ('temperature', 'top_p', 'tool_choice'): @@ -678,6 +694,7 @@ def _append_responses_input_item( instructions: list[str], input_items: list[JsonDict], ) -> None: + """将单条 Chat Completions 消息追加为 Responses `input` 项。""" if not isinstance(message, dict): return @@ -711,6 +728,7 @@ def _append_responses_input_item( def _convert_input_items(items: list[Any], messages: list[JsonDict]) -> None: + """将 Responses `input` 数组重建为 Chat Completions `messages` 列表。""" index = 0 while index < len(items): item = items[index] @@ -756,6 +774,7 @@ def _convert_input_items(items: list[Any], messages: list[JsonDict]) -> None: def _append_message_item(items: list[Any], *, start: int, messages: list[JsonDict]) -> int: + """将一个 message 项及其后续连续 function_call 项合并成一条消息。""" item = items[start] role = item.get('role', 'assistant') content = _extract_text(item.get('content', [])) @@ -775,6 +794,7 @@ def _append_message_item(items: list[Any], *, start: int, messages: list[JsonDic def _append_function_call_item(item: JsonDict, messages: list[JsonDict]) -> None: + """将独立的 Responses `function_call` 项挂接到最近的 assistant 消息上。""" tool_call = _build_cc_tool_call(item) if messages and messages[-1]['role'] == 'assistant': @@ -787,6 +807,7 @@ def _append_function_call_item(item: JsonDict, messages: list[JsonDict]) -> None def _convert_function_call_output_item(item: JsonDict) -> JsonDict: + """将 Responses 的 `function_call_output` 项转换为 OpenAI `tool` 消息。""" output = item.get('output', '') if not isinstance(output, str): output = json.dumps(output, ensure_ascii=False) @@ -798,12 +819,14 @@ def _convert_function_call_output_item(item: JsonDict) -> JsonDict: def _normalize_simple_content(content: Any) -> str: + """将简单 content 载荷规范化为纯文本字符串。""" if isinstance(content, list): return _extract_text(content) or '' return str(content) if content is not None else '' def _collect_function_calls(items: list[Any], start: int) -> tuple[list[JsonDict], int]: + """收集从指定位置开始连续出现的 `function_call` 项。""" tool_calls: list[JsonDict] = [] index = start while index < len(items): @@ -817,6 +840,7 @@ def _collect_function_calls(items: list[Any], start: int) -> tuple[list[JsonDict def _build_cc_tool_call(item: JsonDict) -> JsonDict: + """将单个 Responses `function_call` 项转换为 CC `tool_call` 结构。""" return { 'id': item.get('call_id') or gen_id('call_'), 'type': 'function', @@ -833,6 +857,7 @@ def _build_cc_tool_call(item: JsonDict) -> JsonDict: def _build_responses_output(message: JsonDict) -> list[JsonDict]: + """将单条聊天补全消息展开为 Responses `output` 数组。""" output: list[JsonDict] = [] if message.get('reasoning_content'): @@ -846,6 +871,7 @@ def _build_responses_output(message: JsonDict) -> list[JsonDict]: def _make_reasoning_output_item(text: str) -> JsonDict: + """构造单个 Responses reasoning 输出项。""" return { 'type': 'reasoning', 'id': gen_id('rs_'), @@ -854,6 +880,7 @@ def _make_reasoning_output_item(text: str) -> JsonDict: def _make_message_output_item(text: str) -> JsonDict: + """构造单个 Responses message 输出项。""" return { 'type': 'message', 'id': gen_id('msg_'), @@ -864,6 +891,7 @@ def _make_message_output_item(text: str) -> JsonDict: def _make_function_call_output_item(tool_call: JsonDict) -> JsonDict: + """构造单个 Responses function_call 输出项。""" function_data = tool_call.get('function') or {} return { 'type': 'function_call', @@ -876,6 +904,7 @@ def _make_function_call_output_item(tool_call: JsonDict) -> JsonDict: def _build_responses_usage(usage: JsonDict) -> JsonDict: + """将 Chat Completions 的 usage 字段映射为 Responses usage 结构。""" return { 'input_tokens': usage.get('prompt_tokens', 0), 'output_tokens': usage.get('completion_tokens', 0), @@ -884,6 +913,7 @@ def _build_responses_usage(usage: JsonDict) -> JsonDict: def _collect_cc_parts_from_responses_output(output_items: Any) -> tuple[str, str, list[JsonDict]]: + """从 Responses `output` 中提取文本、思考摘要和工具调用。""" content_text = '' reasoning_text = '' tool_calls: list[JsonDict] = [] @@ -906,6 +936,7 @@ def _collect_cc_parts_from_responses_output(output_items: Any) -> tuple[str, str def _extract_reasoning_text(item: JsonDict) -> str: + """从 Responses reasoning 项中拼接出完整的摘要文本。""" summary = item.get('summary', []) if not isinstance(summary, list): return '' @@ -917,6 +948,7 @@ def _extract_reasoning_text(item: JsonDict) -> str: def _build_cc_tool_call_from_responses_output(item: JsonDict, *, index: int) -> JsonDict: + """将 Responses `function_call` 输出项转换为 CC `tool_call`。""" return { 'index': index, 'id': item.get('call_id') or gen_id('call_'), @@ -929,6 +961,7 @@ def _build_cc_tool_call_from_responses_output(item: JsonDict, *, index: int) -> def _cc_finish_reason_from_responses(response_data: JsonDict, tool_calls: list[JsonDict]) -> str: + """根据 Responses 完成状态推断聊天补全的 finish_reason。""" if tool_calls: return 'tool_calls' if response_data.get('status') == 'incomplete': @@ -937,10 +970,12 @@ def _cc_finish_reason_from_responses(response_data: JsonDict, tool_calls: list[J def _response_status_from_finish_reason(finish_reason: str) -> str: + """将聊天补全 finish_reason 映射为 Responses 顶层状态。""" return 'incomplete' if finish_reason == 'length' else 'completed' def _map_anthropic_stop_reason(stop_reason: str) -> str: + """将 Anthropic 的 stop_reason 映射为聊天补全风格的结束原因。""" return {'tool_use': 'tool_calls', 'max_tokens': 'length'}.get(stop_reason, 'stop') @@ -950,6 +985,7 @@ def _map_anthropic_stop_reason(stop_reason: str) -> str: def _extract_text(content: Any) -> str: + """从多种内容块结构中提取并拼接纯文本。""" if isinstance(content, str): return content if not isinstance(content, list): @@ -969,6 +1005,7 @@ def _extract_text(content: Any) -> str: def _content_to_text(content: Any) -> str: + """将任意 content 载荷转换为单个字符串。""" if isinstance(content, str): return content if isinstance(content, list): @@ -977,6 +1014,7 @@ def _content_to_text(content: Any) -> str: def _content_to_responses_parts(content: Any) -> list[JsonDict]: + """将普通消息内容转换为 Responses `input_text` 数组。""" if isinstance(content, list): text = _extract_text(content) else: @@ -985,6 +1023,7 @@ def _content_to_responses_parts(content: Any) -> list[JsonDict]: def _stringify_output(content: Any) -> str: + """将工具输出统一序列化为字符串,便于放入 `function_call_output`。""" if isinstance(content, str): return content if content is None: @@ -993,6 +1032,7 @@ def _stringify_output(content: Any) -> str: def _build_responses_function_call_item(tool_call: JsonDict) -> JsonDict: + """将 CC `tool_call` 结构转换为 Responses `function_call` 输入项。""" function_data = tool_call.get('function') or {} return { 'type': 'function_call', @@ -1003,6 +1043,7 @@ def _build_responses_function_call_item(tool_call: JsonDict) -> JsonDict: def _convert_cc_tools_to_responses(tools: Any) -> list[JsonDict]: + """将聊天补全风格的工具定义转换为 Responses `tools` 列表。""" if not isinstance(tools, list): return [] @@ -1024,6 +1065,7 @@ def _convert_cc_tools_to_responses(tools: Any) -> list[JsonDict]: def _convert_tools(tools: Any) -> list[JsonDict]: + """规范化 Responses 请求中的工具定义列表。""" if not isinstance(tools, list): return [] @@ -1036,6 +1078,7 @@ def _convert_tools(tools: Any) -> list[JsonDict]: def _convert_tool_definition(tool: Any) -> JsonDict | None: + """将扁平工具定义补成标准 Chat Completions `function` 工具格式。""" if not isinstance(tool, dict): return None if tool.get('type') != 'function': diff --git a/app.py b/app.py index 5a5137d..3aefdb3 100644 --- a/app.py +++ b/app.py @@ -19,6 +19,11 @@ logger = logging.getLogger(__name__) def create_app(): + """创建并配置 Flask 应用实例。 + + 这里统一完成跨路由共享的初始化逻辑,包括配置加载、跨域、错误处理、 + 访问鉴权、健康检查以及蓝图注册。 + """ app = Flask(__name__) CORS(app) settings.load() @@ -27,20 +32,28 @@ def create_app(): @app.errorhandler(404) def not_found(e): + """将未匹配到的路径统一转换为 JSON 404 响应。""" return jsonify({'error': {'message': '未找到', 'type': 'not_found'}}), 404 @app.errorhandler(405) def method_not_allowed(e): + """将不支持的请求方法统一转换为 JSON 405 响应。""" return jsonify({'error': {'message': '方法不允许', 'type': 'method_not_allowed'}}), 405 @app.errorhandler(500) def internal_error(e): + """将未捕获的服务端异常统一包装为 JSON 500 响应。""" return jsonify({'error': {'message': '服务器内部错误', 'type': 'server_error'}}), 500 # ─── 全局鉴权中间件 ────────────────────────── @app.before_request def check_access(): + """在进入业务路由前校验访问密钥。 + + 当配置了 `ACCESS_API_KEY` 时,除健康检查和管理面板相关路径外, + 所有请求都必须携带正确的 Bearer Token 或 `x-api-key`。 + """ if not Config.ACCESS_API_KEY: return @@ -61,6 +74,7 @@ def create_app(): @app.route('/health', methods=['GET']) def health(): + """返回服务健康状态和当前生效的上游地址。""" return jsonify({'status': 'ok', 'target': settings.get_url()}) # ─── 注册路由蓝图 ──────────────────────────── diff --git a/config.py b/config.py index d84b134..fdc272b 100644 --- a/config.py +++ b/config.py @@ -4,6 +4,12 @@ import os class Config: + """集中声明服务运行依赖的环境变量配置。 + + 这个类不承担运行时逻辑,只作为模块级配置容器,统一暴露上游地址、 + 鉴权密钥、端口、超时和调试开关,供应用启动、路由鉴权和请求转发层共享。 + """ + # 上游 API 地址 PROXY_TARGET_URL = os.getenv('PROXY_TARGET_URL', 'https://api.anthropic.com') # 上游 API 密钥 diff --git a/routes/admin.py b/routes/admin.py index eadd51e..79125cf 100644 --- a/routes/admin.py +++ b/routes/admin.py @@ -27,11 +27,13 @@ bp = Blueprint('admin', __name__) @bp.route('/admin') @bp.route('/admin/') def admin_page(): + """返回管理面板首页 HTML 页面,供浏览器进入配置界面。""" return send_from_directory(_STATIC_DIR, 'admin.html') @bp.route('/static/') def static_files(filename): + """提供管理面板所需的静态资源文件。""" return send_from_directory(_STATIC_DIR, filename) @@ -40,6 +42,7 @@ def static_files(filename): @bp.route('/v1/models', methods=['GET']) def list_models(): + """返回当前配置的模型列表,供 Cursor 拉取可用模型。""" mappings = settings.get().get('model_mappings', {}) models = [{ 'id': name, @@ -61,6 +64,7 @@ def list_models(): @bp.route('/api/admin/login', methods=['POST']) def admin_login(): + """校验管理面板登录密钥,并返回是否允许进入后台。""" data = request.get_json(force=True) if not Config.ACCESS_API_KEY: return jsonify({'ok': True, 'message': '未配置鉴权'}) @@ -74,6 +78,7 @@ def admin_login(): @bp.route('/api/admin/settings', methods=['GET']) def get_settings(): + """读取当前生效的全局代理配置。""" err = _check_auth() if err: return err @@ -88,6 +93,7 @@ def get_settings(): @bp.route('/api/admin/settings', methods=['PUT']) def update_settings(): + """更新全局上游地址与密钥配置。""" err = _check_auth() if err: return err @@ -104,6 +110,7 @@ def update_settings(): @bp.route('/api/admin/mappings', methods=['GET']) def list_mappings(): + """列出所有模型映射配置,供管理面板读取和展示。""" err = _check_auth() if err: return err @@ -112,6 +119,7 @@ def list_mappings(): @bp.route('/api/admin/mappings', methods=['POST']) def add_mapping(): + """新增一条模型映射,并写入持久化配置。""" err = _check_auth() if err: return err @@ -133,6 +141,7 @@ def add_mapping(): @bp.route('/api/admin/mappings/', methods=['PUT']) def update_mapping(name): + """更新指定名称的模型映射,必要时支持重命名。""" err = _check_auth() if err: return err @@ -158,6 +167,7 @@ def update_mapping(name): @bp.route('/api/admin/mappings/', methods=['DELETE']) def delete_mapping(name): + """删除指定名称的模型映射,并在存在时同步保存配置。""" err = _check_auth() if err: return err @@ -185,7 +195,10 @@ def _check_auth(): def _save_and_respond(data, log_msg): - """保存配置并返回响应""" + """保存配置并返回统一成功响应。 + + 当写盘失败时,这里也负责把异常转成结构化的 JSON 错误返回。 + """ try: settings.save(data) except OSError as e: diff --git a/routes/chat.py b/routes/chat.py index 89382e0..7652e4d 100644 --- a/routes/chat.py +++ b/routes/chat.py @@ -149,6 +149,7 @@ def _handle_openai_stream( payload['stream'] = True def generate(): + """消费上游 OpenAI SSE,并逐段产出给 Cursor 的聊天补全流。""" resp, err = forward_request(url, headers, payload, stream=True) if err: yield chat_error_chunk(str(err)) @@ -235,6 +236,7 @@ def _handle_responses_stream( converter = ResponsesToCCStreamConverter(model=ctx.client_model) def generate(): + """消费上游 Responses 事件,并实时转换成聊天补全 chunk。""" resp, err = forward_request(url, headers, payload, stream=True) if err: yield chat_error_chunk(str(err)) @@ -314,6 +316,7 @@ def _handle_anthropic_stream( converter = AnthropicStreamConverter() def generate(): + """消费上游 Anthropic 事件流,并逐步映射为聊天补全 SSE。""" resp, err = forward_request(url, headers, payload, stream=True) if err: yield chat_error_chunk(str(err)) diff --git a/routes/common.py b/routes/common.py index 6964095..0b9726c 100644 --- a/routes/common.py +++ b/routes/common.py @@ -19,7 +19,11 @@ logger = logging.getLogger(__name__) @dataclass(frozen=True) class RouteContext: - """数据面路由使用的标准请求上下文。""" + """数据面路由使用的标准请求上下文。 + + 路由层会先根据客户端模型名解析出统一上下文,后续处理函数只需要关心 + 上游模型、后端类型、目标地址、鉴权信息和流式标记,而不必重复访问配置层。 + """ client_model: str upstream_model: str diff --git a/routes/messages.py b/routes/messages.py index 0a4d89e..d190036 100644 --- a/routes/messages.py +++ b/routes/messages.py @@ -22,6 +22,7 @@ bp = Blueprint('messages', __name__) @bp.route('/v1/messages', methods=['POST']) def messages_passthrough(): + """透传 Anthropic Messages 请求,并在必要时补齐 thinking 兼容层。""" payload = request.get_json(force=True) model = payload.get('model', 'unknown') is_stream = payload.get('stream', False) @@ -43,6 +44,7 @@ def messages_passthrough(): # 流式透传 def generate(): + """建立上游流式连接并逐段回传处理后的 SSE 数据。""" try: resp = req_lib.post( url, headers=headers, json=payload, @@ -132,7 +134,7 @@ def _process_stream(resp): def _emit_thinking_blocks(text): - """生成 thinking block 的 SSE 事件序列""" + """生成一组等价的 Anthropic thinking block SSE 事件。""" yield ( f'event: content_block_start\n' f'data: {json.dumps({"type": "content_block_start", "index": 0, "content_block": {"type": "thinking", "thinking": ""}})}\n\n' diff --git a/routes/responses.py b/routes/responses.py index 617814a..25edac8 100644 --- a/routes/responses.py +++ b/routes/responses.py @@ -126,6 +126,7 @@ def _handle_openai_stream( converter = ResponsesStreamConverter(model=ctx.client_model) def generate(): + """消费 OpenAI 聊天补全流,并实时改写为 Responses SSE。""" yield from converter.start_events() resp, err = forward_request(url, headers, cc_payload, stream=True) @@ -205,6 +206,7 @@ def _handle_responses_stream( converter = ResponsesStreamConverter(model=ctx.client_model) def generate(): + """透传上游原生 Responses 流,并做轻量模型名改写。""" resp, err = forward_request(url, headers, payload, stream=True) if err: yield responses_error_event(str(err)) @@ -275,6 +277,7 @@ def _handle_anthropic_stream( converter = ResponsesStreamConverter(model=ctx.client_model) def generate(): + """消费 Anthropic SSE,并直接映射为 Responses 事件序列。""" yield from converter.start_events() resp, err = forward_request(url, headers, anthropic_payload, stream=True) diff --git a/settings.py b/settings.py index 83ac318..63b9b15 100644 --- a/settings.py +++ b/settings.py @@ -27,7 +27,10 @@ _DEFAULTS = { def load(): - """从文件加载配置""" + """从持久化文件读取配置并刷新内存缓存。 + + 如果配置文件不存在或内容损坏,会回退到默认值,保证服务仍然可以正常启动。 + """ global _cache with _lock: if os.path.exists(SETTINGS_FILE): @@ -42,7 +45,10 @@ def load(): def save(data): - """保存配置到文件""" + """将当前配置写回到持久化文件并同步缓存。 + + 保存前会确保数据目录存在,并始终以默认配置为基底合并缺失字段。 + """ global _cache with _lock: os.makedirs(DATA_DIR, exist_ok=True) @@ -52,24 +58,24 @@ def save(data): def get(): - """获取当前配置(优先使用缓存)""" + """获取当前配置快照,优先返回内存缓存中的结果。""" if _cache is None: return load() return dict(_cache) def get_url(): - """获取生效的上游 URL:配置文件优先,环境变量兜底""" + """获取当前生效的上游 URL,优先使用持久化配置。""" return get().get('proxy_target_url') or Config.PROXY_TARGET_URL def get_key(): - """获取生效的 API 密钥:配置文件优先,环境变量兜底""" + """获取当前生效的 API 密钥,优先使用持久化配置。""" return get().get('proxy_api_key') or Config.PROXY_API_KEY def resolve_model(model_name): - """解析模型映射,返回 {upstream_model, backend, target_url, api_key}""" + """解析模型映射并返回完整的上游路由信息。""" settings = get() mappings = settings.get('model_mappings', {}) base_url, base_key = get_url(), get_key() @@ -95,6 +101,10 @@ def resolve_model(model_name): def _auto_detect(name): - """根据模型名自动判断后端类型""" + """根据模型名关键字推断默认后端协议类型。 + + 当前规则较为保守:命中 `claude` 或 `anthropic` 走 Anthropic, + 其余模型默认视为 OpenAI 兼容后端。 + """ lower = (name or '').lower() return 'anthropic' if ('claude' in lower or 'anthropic' in lower) else 'openai' diff --git a/start.py b/start.py index 96e621e..8248cf2 100644 --- a/start.py +++ b/start.py @@ -19,6 +19,7 @@ from app import create_app def main(): + """加载应用并以 Waitress 方式启动代理服务。""" app = create_app() print(f'代理服务启动于 0.0.0.0:{Config.PROXY_PORT}') print(f'上游地址: {Config.PROXY_TARGET_URL}') diff --git a/utils/http.py b/utils/http.py index c31db5a..3b5d8c2 100644 --- a/utils/http.py +++ b/utils/http.py @@ -16,7 +16,7 @@ logger = logging.getLogger(__name__) def gen_id(prefix: str = '') -> str: - """生成唯一 ID""" + """生成带可选前缀的短随机 ID,用于请求和工具调用标识。""" return f'{prefix}{uuid.uuid4().hex[:24]}' @@ -57,7 +57,7 @@ def sse_response(generator): def error_json(message, error_type='proxy_error', status=502): - """构建 JSON 错误响应""" + """构造统一的 JSON 错误响应,供非流式链路直接返回。""" return jsonify({'error': {'message': str(message), 'type': error_type}}), status diff --git a/utils/think_tag.py b/utils/think_tag.py index 05158ef..269e7b6 100644 --- a/utils/think_tag.py +++ b/utils/think_tag.py @@ -33,6 +33,7 @@ class ThinkTagExtractor: """ def __init__(self): + """初始化跨 chunk 的 thinking 状态跟踪。""" self._in_thinking = False def process_chunk(self, chunk):