DeepClaudeProxy / app /clients /claude_client.py
megatrump's picture
init
5693654
"""Claude API 客户端"""
import json
from typing import AsyncGenerator
from app.utils.logger import logger
from .base_client import BaseClient
class ClaudeClient(BaseClient):
def __init__(self, api_key: str, api_url: str = "https://api.anthropic.com/v1/messages", provider: str = "anthropic"):
"""初始化 Claude 客户端
Args:
api_key: Claude API密钥
api_url: Claude API地址
is_openrouter: 是否使用 OpenRouter API
"""
super().__init__(api_key, api_url)
self.provider = provider
async def stream_chat(
self,
messages: list,
model_arg: tuple[float, float, float, float],
model: str,
stream: bool = True
) -> AsyncGenerator[tuple[str, str], None]:
"""流式或非流式对话
Args:
messages: 消息列表
model_arg: 模型参数元组[temperature, top_p, presence_penalty, frequency_penalty]
model: 模型名称。如果是 OpenRouter, 会自动转换为 'anthropic/claude-3.5-sonnet' 格式
stream: 是否使用流式输出,默认为 True
Yields:
tuple[str, str]: (内容类型, 内容)
内容类型: "answer"
内容: 实际的文本内容
"""
if self.provider == "openrouter":
# 转换模型名称为 OpenRouter 格式
model = "anthropic/claude-3.5-sonnet"
headers = {
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json",
"HTTP-Referer": "https://github.com/ErlichLiu/DeepClaude", # OpenRouter 需要
"X-Title": "DeepClaude" # OpenRouter 需要
}
data = {
"model": model, # OpenRouter 使用 anthropic/claude-3.5-sonnet 格式
"messages": messages,
"stream": stream,
"temperature": 1 if model_arg[0] < 0 or model_arg[0] > 1 else model_arg[0],
"top_p": model_arg[1],
"presence_penalty": model_arg[2],
"frequency_penalty": model_arg[3]
}
elif self.provider == "oneapi":
headers = {
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json"
}
data = {
"model": model,
"messages": messages,
"stream": stream,
"temperature": 1 if model_arg[0] < 0 or model_arg[0] > 1 else model_arg[0],
"top_p": model_arg[1],
"presence_penalty": model_arg[2],
"frequency_penalty": model_arg[3]
}
elif self.provider == "anthropic":
headers = {
"x-api-key": self.api_key,
"anthropic-version": "2023-06-01",
"content-type": "application/json",
"accept": "text/event-stream" if stream else "application/json",
}
data = {
"model": model,
"messages": messages,
"max_tokens": 8192,
"stream": stream,
"temperature": 1 if model_arg[0] < 0 or model_arg[0] > 1 else model_arg[0], # Claude仅支持temperature与top_p
"top_p": model_arg[1]
}
else:
raise ValueError(f"不支持的Claude Provider: {self.provider}")
logger.debug(f"开始对话:{data}")
if stream:
async for chunk in self._make_request(headers, data):
chunk_str = chunk.decode('utf-8')
if not chunk_str.strip():
continue
for line in chunk_str.split('\n'):
if line.startswith('data: '):
json_str = line[6:] # 去掉 'data: ' 前缀
if json_str.strip() == '[DONE]':
return
try:
data = json.loads(json_str)
if self.provider in ("openrouter", "oneapi"):
# OpenRouter/OneApi 格式
content = data.get('choices', [{}])[0].get('delta', {}).get('content', '')
if content:
yield "answer", content
elif self.provider == "anthropic":
# Anthropic 格式
if data.get('type') == 'content_block_delta':
content = data.get('delta', {}).get('text', '')
if content:
yield "answer", content
else:
raise ValueError(f"不支持的Claude Provider: {self.provider}")
except json.JSONDecodeError:
continue
else:
# 非流式输出
async for chunk in self._make_request(headers, data):
try:
response = json.loads(chunk.decode('utf-8'))
if self.provider in ("openrouter", "oneapi"):
content = response.get('choices', [{}])[0].get('message', {}).get('content', '')
if content:
yield "answer", content
elif self.provider == "anthropic":
content = response.get('content', [{}])[0].get('text', '')
if content:
yield "answer", content
else:
raise ValueError(f"不支持的Claude Provider: {self.provider}")
except json.JSONDecodeError:
continue