2025-12-10 20:52:44 +08:00
|
|
|
|
"""
|
|
|
|
|
|
Claude Chat Handler - 基于通用 Chat Handler 基类的简化实现
|
|
|
|
|
|
|
|
|
|
|
|
继承 ChatHandlerBase,只需覆盖格式特定的方法。
|
|
|
|
|
|
代码量从原来的 ~1470 行减少到 ~120 行。
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
2026-01-30 03:10:21 +08:00
|
|
|
|
from typing import Any
|
2025-12-10 20:52:44 +08:00
|
|
|
|
|
|
|
|
|
|
from src.api.handlers.base.chat_handler_base import ChatHandlerBase
|
2026-02-01 17:28:00 +08:00
|
|
|
|
from src.core.api_format import ApiFamily, EndpointKind
|
2026-03-09 18:26:53 +08:00
|
|
|
|
from src.core.usage_tokens import extract_cache_creation_tokens_detail
|
2025-12-10 20:52:44 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ClaudeChatHandler(ChatHandlerBase):
|
|
|
|
|
|
"""
|
|
|
|
|
|
Claude Chat Handler - 处理 Claude Chat/CLI API 格式的请求
|
|
|
|
|
|
|
|
|
|
|
|
格式特点:
|
|
|
|
|
|
- 使用 input_tokens/output_tokens
|
|
|
|
|
|
- 支持 cache_creation_input_tokens/cache_read_input_tokens
|
|
|
|
|
|
- 请求格式:ClaudeMessagesRequest
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
2026-02-01 17:28:00 +08:00
|
|
|
|
FORMAT_ID = "claude:chat"
|
|
|
|
|
|
API_FAMILY = ApiFamily.CLAUDE
|
|
|
|
|
|
ENDPOINT_KIND = EndpointKind.CHAT
|
2025-12-10 20:52:44 +08:00
|
|
|
|
|
|
|
|
|
|
def extract_model_from_request(
|
|
|
|
|
|
self,
|
2026-01-30 03:10:21 +08:00
|
|
|
|
request_body: dict[str, Any],
|
|
|
|
|
|
path_params: dict[str, Any] | None = None, # noqa: ARG002
|
2025-12-10 20:52:44 +08:00
|
|
|
|
) -> str:
|
|
|
|
|
|
"""
|
|
|
|
|
|
从请求中提取模型名 - Claude 格式实现
|
|
|
|
|
|
|
|
|
|
|
|
Claude API 的 model 在请求体顶级字段。
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
request_body: 请求体
|
|
|
|
|
|
path_params: URL 路径参数(Claude 不使用)
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
模型名
|
|
|
|
|
|
"""
|
|
|
|
|
|
model = request_body.get("model")
|
|
|
|
|
|
return str(model) if model else "unknown"
|
|
|
|
|
|
|
|
|
|
|
|
def apply_mapped_model(
|
|
|
|
|
|
self,
|
2026-01-30 03:10:21 +08:00
|
|
|
|
request_body: dict[str, Any],
|
2025-12-10 20:52:44 +08:00
|
|
|
|
mapped_model: str,
|
2026-01-30 03:10:21 +08:00
|
|
|
|
) -> dict[str, Any]:
|
2025-12-10 20:52:44 +08:00
|
|
|
|
"""
|
|
|
|
|
|
将映射后的模型名应用到请求体
|
|
|
|
|
|
|
|
|
|
|
|
Claude API 的 model 在请求体顶级字段。
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
request_body: 原始请求体
|
|
|
|
|
|
mapped_model: 映射后的模型名
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
更新了 model 字段的请求体
|
|
|
|
|
|
"""
|
|
|
|
|
|
result = dict(request_body)
|
|
|
|
|
|
result["model"] = mapped_model
|
|
|
|
|
|
return result
|
|
|
|
|
|
|
2025-12-16 00:02:49 +08:00
|
|
|
|
async def _convert_request(self, request: Any) -> Any:
|
2025-12-10 20:52:44 +08:00
|
|
|
|
"""
|
2026-01-27 12:39:36 +08:00
|
|
|
|
将请求转换为 Claude 格式的 Pydantic 对象
|
|
|
|
|
|
|
|
|
|
|
|
注意:此方法只做类型转换(dict → Pydantic),不做跨格式转换。
|
2026-02-02 21:16:28 +08:00
|
|
|
|
跨格式转换由调度/执行层(TaskService + RequestDispatcher)在选中候选后、发送请求前执行,
|
2026-01-27 12:39:36 +08:00
|
|
|
|
并受全局开关和端点配置控制。
|
2025-12-10 20:52:44 +08:00
|
|
|
|
|
|
|
|
|
|
Args:
|
2026-01-27 12:39:36 +08:00
|
|
|
|
request: 原始请求对象(应已是 Claude 格式)
|
2025-12-10 20:52:44 +08:00
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
ClaudeMessagesRequest 对象
|
|
|
|
|
|
"""
|
|
|
|
|
|
from src.models.claude import ClaudeMessagesRequest
|
2026-01-27 02:17:18 +08:00
|
|
|
|
|
2026-01-27 12:39:36 +08:00
|
|
|
|
# 如果已经是 Claude 格式 Pydantic 对象,直接返回
|
2025-12-10 20:52:44 +08:00
|
|
|
|
if isinstance(request, ClaudeMessagesRequest):
|
|
|
|
|
|
return request
|
|
|
|
|
|
|
2026-01-27 12:39:36 +08:00
|
|
|
|
# 如果是字典,转换为 Pydantic 对象(假设已是 Claude 格式)
|
2025-12-10 20:52:44 +08:00
|
|
|
|
if isinstance(request, dict):
|
|
|
|
|
|
return ClaudeMessagesRequest(**request)
|
|
|
|
|
|
|
|
|
|
|
|
return request
|
|
|
|
|
|
|
2026-01-30 03:10:21 +08:00
|
|
|
|
def _extract_usage(self, response: dict) -> dict[str, int]:
|
2025-12-10 20:52:44 +08:00
|
|
|
|
"""
|
|
|
|
|
|
从 Claude 响应中提取 token 使用情况
|
|
|
|
|
|
|
|
|
|
|
|
Claude 格式使用:
|
|
|
|
|
|
- input_tokens / output_tokens
|
|
|
|
|
|
- cache_creation_input_tokens / cache_read_input_tokens
|
2025-12-16 00:02:49 +08:00
|
|
|
|
- 新格式:claude_cache_creation_5_m_tokens / claude_cache_creation_1_h_tokens
|
2025-12-10 20:52:44 +08:00
|
|
|
|
"""
|
|
|
|
|
|
usage = response.get("usage", {})
|
2026-02-28 11:44:08 +08:00
|
|
|
|
total, t5m, t1h = extract_cache_creation_tokens_detail(usage)
|
2025-12-10 20:52:44 +08:00
|
|
|
|
|
|
|
|
|
|
return {
|
2025-12-16 00:02:49 +08:00
|
|
|
|
"input_tokens": usage.get("input_tokens", 0),
|
|
|
|
|
|
"output_tokens": usage.get("output_tokens", 0),
|
2026-02-28 11:44:08 +08:00
|
|
|
|
"cache_creation_input_tokens": total,
|
2025-12-16 00:02:49 +08:00
|
|
|
|
"cache_read_input_tokens": usage.get("cache_read_input_tokens", 0),
|
2026-02-28 11:44:08 +08:00
|
|
|
|
"cache_creation_input_tokens_5m": t5m,
|
|
|
|
|
|
"cache_creation_input_tokens_1h": t1h,
|
2025-12-10 20:52:44 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-30 03:10:21 +08:00
|
|
|
|
def _normalize_response(self, response: dict[str, Any]) -> dict[str, Any]:
|
2025-12-10 20:52:44 +08:00
|
|
|
|
"""
|
|
|
|
|
|
规范化 Claude 响应
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
response: 原始响应
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
规范化后的响应
|
|
|
|
|
|
"""
|
2025-12-19 01:20:30 +08:00
|
|
|
|
# 作为中转站,直接透传响应,不做标准化处理
|
2025-12-10 20:52:44 +08:00
|
|
|
|
return response
|