mirror of
https://github.com/fawney19/Aether.git
synced 2026-01-03 00:02:28 +08:00
- Refactor CLI handler base for better stream context management - Optimize request/response handling for Claude, OpenAI, and Gemini CLI adapters - Enhance telemetry tracking across CLI handlers
202 lines
6.2 KiB
Python
202 lines
6.2 KiB
Python
"""
|
||
Claude CLI Message Handler - 基于通用 CLI Handler 基类的简化实现
|
||
|
||
继承 CliMessageHandlerBase,只需覆盖格式特定的配置和事件处理逻辑。
|
||
验证新架构的有效性:代码量从数百行减少到 ~80 行。
|
||
"""
|
||
|
||
from typing import Any, Dict, Optional
|
||
|
||
from src.api.handlers.base.cli_handler_base import (
|
||
CliMessageHandlerBase,
|
||
StreamContext,
|
||
)
|
||
from src.api.handlers.base.utils import extract_cache_creation_tokens
|
||
|
||
|
||
class ClaudeCliMessageHandler(CliMessageHandlerBase):
|
||
"""
|
||
Claude CLI Message Handler - 处理 Claude CLI API 格式
|
||
|
||
使用新三层架构 (Provider -> ProviderEndpoint -> ProviderAPIKey)
|
||
通过 FallbackOrchestrator 实现自动故障转移、健康监控和并发控制
|
||
|
||
响应格式特点:
|
||
- 使用 content[] 数组
|
||
- 使用 text 类型
|
||
- 流式事件:message_start, content_block_delta, message_delta, message_stop
|
||
- 支持 cache_creation_input_tokens 和 cache_read_input_tokens
|
||
|
||
模型字段:请求体顶级 model 字段
|
||
"""
|
||
|
||
FORMAT_ID = "CLAUDE_CLI"
|
||
|
||
def extract_model_from_request(
|
||
self,
|
||
request_body: Dict[str, Any],
|
||
path_params: Optional[Dict[str, Any]] = None, # noqa: ARG002
|
||
) -> 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,
|
||
request_body: Dict[str, Any],
|
||
mapped_model: str,
|
||
) -> Dict[str, Any]:
|
||
"""
|
||
Claude API 的 model 在请求体顶级
|
||
|
||
Args:
|
||
request_body: 原始请求体
|
||
mapped_model: 映射后的模型名
|
||
|
||
Returns:
|
||
更新了 model 字段的请求体
|
||
"""
|
||
result = dict(request_body)
|
||
result["model"] = mapped_model
|
||
return result
|
||
|
||
def _process_event_data(
|
||
self,
|
||
ctx: StreamContext,
|
||
event_type: str,
|
||
data: Dict[str, Any],
|
||
) -> None:
|
||
"""
|
||
处理 Claude CLI 格式的 SSE 事件
|
||
|
||
事件类型:
|
||
- message_start: 消息开始,包含初始 usage(含缓存 tokens)
|
||
- content_block_delta: 文本增量
|
||
- message_delta: 消息增量,包含最终 usage
|
||
- message_stop: 消息结束
|
||
"""
|
||
# 处理 message_start 事件
|
||
if event_type == "message_start":
|
||
message = data.get("message", {})
|
||
if message.get("id"):
|
||
ctx.response_id = message["id"]
|
||
|
||
# 提取初始 usage(包含缓存 tokens)
|
||
usage = message.get("usage", {})
|
||
if usage:
|
||
ctx.input_tokens = usage.get("input_tokens", 0)
|
||
|
||
cache_read = usage.get("cache_read_input_tokens", 0)
|
||
if cache_read:
|
||
ctx.cached_tokens = cache_read
|
||
|
||
cache_creation = extract_cache_creation_tokens(usage)
|
||
if cache_creation:
|
||
ctx.cache_creation_tokens = cache_creation
|
||
|
||
# 处理文本增量
|
||
elif event_type == "content_block_delta":
|
||
delta = data.get("delta", {})
|
||
if delta.get("type") == "text_delta":
|
||
text = delta.get("text", "")
|
||
if text:
|
||
ctx.append_text(text)
|
||
|
||
# 处理消息增量(包含最终 usage)
|
||
elif event_type == "message_delta":
|
||
usage = data.get("usage", {})
|
||
if usage:
|
||
if "input_tokens" in usage:
|
||
ctx.input_tokens = usage["input_tokens"]
|
||
if "output_tokens" in usage:
|
||
ctx.output_tokens = usage["output_tokens"]
|
||
|
||
# 更新缓存读取 tokens
|
||
if "cache_read_input_tokens" in usage:
|
||
ctx.cached_tokens = usage["cache_read_input_tokens"]
|
||
|
||
# 更新缓存创建 tokens
|
||
cache_creation = extract_cache_creation_tokens(usage)
|
||
if cache_creation > 0:
|
||
ctx.cache_creation_tokens = cache_creation
|
||
|
||
# 检查是否结束
|
||
delta = data.get("delta", {})
|
||
if delta.get("stop_reason"):
|
||
ctx.has_completion = True
|
||
ctx.final_response = data
|
||
|
||
# 处理消息结束
|
||
elif event_type == "message_stop":
|
||
ctx.has_completion = True
|
||
|
||
def _extract_response_metadata(
|
||
self,
|
||
response: Dict[str, Any],
|
||
) -> Dict[str, Any]:
|
||
"""
|
||
从 Claude 响应中提取元数据
|
||
|
||
提取 model、stop_reason 等字段作为元数据。
|
||
|
||
Args:
|
||
response: Claude API 响应
|
||
|
||
Returns:
|
||
提取的元数据字典
|
||
"""
|
||
metadata: Dict[str, Any] = {}
|
||
|
||
# 提取模型名称(实际使用的模型)
|
||
if "model" in response:
|
||
metadata["model"] = response["model"]
|
||
|
||
# 提取停止原因
|
||
if "stop_reason" in response:
|
||
metadata["stop_reason"] = response["stop_reason"]
|
||
|
||
# 提取消息 ID
|
||
if "id" in response:
|
||
metadata["message_id"] = response["id"]
|
||
|
||
# 提取消息类型
|
||
if "type" in response:
|
||
metadata["type"] = response["type"]
|
||
|
||
return metadata
|
||
|
||
def _finalize_stream_metadata(self, ctx: StreamContext) -> None:
|
||
"""
|
||
从流上下文中提取最终元数据
|
||
|
||
在流传输完成后调用,从收集的事件中提取元数据。
|
||
|
||
Args:
|
||
ctx: 流上下文
|
||
"""
|
||
# 从 response_id 提取消息 ID
|
||
if ctx.response_id:
|
||
ctx.response_metadata["message_id"] = ctx.response_id
|
||
|
||
# 从 final_response 提取停止原因(message_delta 事件中的 delta.stop_reason)
|
||
if ctx.final_response:
|
||
delta = ctx.final_response.get("delta", {})
|
||
if "stop_reason" in delta:
|
||
ctx.response_metadata["stop_reason"] = delta["stop_reason"]
|
||
|
||
# 记录模型名称
|
||
if ctx.model:
|
||
ctx.response_metadata["model"] = ctx.model
|
||
|