File size: 6,020 Bytes
5693654
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
"""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