devme commited on
Commit
55b8e20
·
verified ·
1 Parent(s): 89447f6

Delete replicate.py

Browse files
Files changed (1) hide show
  1. replicate.py +0 -199
replicate.py DELETED
@@ -1,199 +0,0 @@
1
- import json
2
- import uuid
3
- import asyncio
4
- from typing import Dict, Optional, Tuple, List, AsyncGenerator, Any
5
- import httpx
6
-
7
- from utils import get_proxies, load_module, create_proxy_mounts
8
- from config import AMAZONQ_API_URL, DEFAULT_HEADERS
9
-
10
- try:
11
- _parser = load_module("v2_claude_parser", "claude_parser.py")
12
- EventStreamParser = _parser.EventStreamParser
13
- extract_event_info = _parser.extract_event_info
14
- except Exception as e:
15
- print(f"Warning: Failed to load claude_parser: {e}")
16
- EventStreamParser = None
17
- extract_event_info = None
18
-
19
- class StreamTracker:
20
- def __init__(self):
21
- self.has_content = False
22
-
23
- async def track(self, gen: AsyncGenerator[str, None]) -> AsyncGenerator[str, None]:
24
- async for item in gen:
25
- if item:
26
- self.has_content = True
27
- yield item
28
-
29
- def load_template() -> Tuple[str, Dict[str, str]]:
30
- """
31
- 加载 Amazon Q API 请求模板
32
-
33
- Returns:
34
- (url, headers): API 端点 URL 和默认请求头
35
- """
36
- return AMAZONQ_API_URL, DEFAULT_HEADERS.copy()
37
-
38
- def _merge_headers(as_log: Dict[str, str], bearer_token: str) -> Dict[str, str]:
39
- headers = dict(as_log)
40
- for k in list(headers.keys()):
41
- kl = k.lower()
42
- if kl in ("content-length","host","connection","transfer-encoding"):
43
- headers.pop(k, None)
44
- def set_header(name: str, value: str):
45
- for key in list(headers.keys()):
46
- if key.lower() == name.lower():
47
- del headers[key]
48
- headers[name] = value
49
- set_header("Authorization", f"Bearer {bearer_token}")
50
- set_header("amz-sdk-invocation-id", str(uuid.uuid4()))
51
- return headers
52
-
53
- async def send_chat_request(
54
- access_token: str,
55
- messages: List[Dict[str, Any]],
56
- model: Optional[str] = None,
57
- stream: bool = False,
58
- timeout: Tuple[int,int] = (30,300),
59
- client: Optional[httpx.AsyncClient] = None,
60
- raw_payload: Dict[str, Any] = None
61
- ) -> Tuple[Optional[str], Optional[AsyncGenerator[str, None]], StreamTracker, Optional[AsyncGenerator[Any, None]]]:
62
- """
63
- 发送聊天请求到 Amazon Q API
64
-
65
- Args:
66
- access_token: Amazon Q access token
67
- messages: 消息列表(已废弃,使用 raw_payload)
68
- model: 模型名称(已废弃,使用 raw_payload)
69
- stream: 是否流式响应
70
- timeout: 超时配置
71
- client: HTTP 客户端
72
- raw_payload: Claude API 转换后的请求体(必需)
73
- """
74
- if raw_payload is None:
75
- raise ValueError("raw_payload is required")
76
-
77
- url, headers_from_log = load_template()
78
- headers_from_log["amz-sdk-invocation-id"] = str(uuid.uuid4())
79
-
80
- # Use raw payload (for Claude API)
81
- body_json = raw_payload
82
- # Ensure conversationId is set if missing
83
- if "conversationState" in body_json and "conversationId" not in body_json["conversationState"]:
84
- body_json["conversationState"]["conversationId"] = str(uuid.uuid4())
85
-
86
- payload_str = json.dumps(body_json, ensure_ascii=False)
87
- headers = _merge_headers(headers_from_log, access_token)
88
-
89
- local_client = False
90
- if client is None:
91
- local_client = True
92
- mounts = create_proxy_mounts()
93
- # 增加连接超时时间,避免 TLS 握手超时
94
- timeout_config = httpx.Timeout(connect=60.0, read=timeout[1], write=timeout[0], pool=10.0)
95
- # 只在有代理时才传递 mounts 参数
96
- if mounts:
97
- client = httpx.AsyncClient(mounts=mounts, timeout=timeout_config)
98
- else:
99
- client = httpx.AsyncClient(timeout=timeout_config)
100
-
101
- # Use manual request sending to control stream lifetime
102
- req = client.build_request("POST", url, headers=headers, content=payload_str)
103
-
104
- resp = None
105
- try:
106
- resp = await client.send(req, stream=True)
107
-
108
- if resp.status_code >= 400:
109
- try:
110
- await resp.read()
111
- err = resp.text
112
- except Exception:
113
- err = f"HTTP {resp.status_code}"
114
- await resp.aclose()
115
- if local_client:
116
- await client.aclose()
117
- raise httpx.HTTPError(f"Upstream error {resp.status_code}: {err}")
118
-
119
- tracker = StreamTracker()
120
-
121
- # Track if the response has been consumed to avoid double-close
122
- response_consumed = False
123
-
124
- async def _iter_events() -> AsyncGenerator[Any, None]:
125
- nonlocal response_consumed
126
- try:
127
- # Use EventStreamParser from claude_parser.py
128
- async def byte_gen():
129
- async for chunk in resp.aiter_bytes():
130
- if chunk:
131
- yield chunk
132
-
133
- async for message in EventStreamParser.parse_stream(byte_gen()):
134
- event_info = extract_event_info(message)
135
- if event_info:
136
- event_type = event_info.get('event_type')
137
- payload = event_info.get('payload')
138
- if event_type and payload:
139
- yield (event_type, payload)
140
- except Exception:
141
- if not tracker.has_content:
142
- raise
143
- finally:
144
- response_consumed = True
145
- await resp.aclose()
146
- if local_client:
147
- await client.aclose()
148
-
149
- if stream:
150
- # Wrap generator to ensure cleanup on early termination
151
- async def _safe_iter_events():
152
- try:
153
- # 托底方案: 300秒强制超时
154
- async with asyncio.timeout(300):
155
- async for item in _iter_events():
156
- yield item
157
- except asyncio.TimeoutError:
158
- # 超时强制关闭
159
- if resp and not resp.is_closed:
160
- await resp.aclose()
161
- if local_client and client:
162
- await client.aclose()
163
- raise
164
- except GeneratorExit:
165
- # Generator was closed without being fully consumed
166
- # Ensure cleanup happens even if finally block wasn't reached
167
- if resp and not resp.is_closed:
168
- await resp.aclose()
169
- if local_client and client:
170
- await client.aclose()
171
- raise
172
- except Exception:
173
- # Any exception should also trigger cleanup
174
- if resp and not resp.is_closed:
175
- await resp.aclose()
176
- if local_client and client:
177
- await client.aclose()
178
- raise
179
- return None, None, tracker, _safe_iter_events()
180
- else:
181
- # Non-streaming: consume all events
182
- try:
183
- async for _ in _iter_events():
184
- pass
185
- finally:
186
- # Ensure response is closed even if iteration is incomplete
187
- if not response_consumed and resp:
188
- await resp.aclose()
189
- if local_client:
190
- await client.aclose()
191
- return None, None, tracker, None
192
-
193
- except Exception:
194
- # Critical: close response on any exception before generators are created
195
- if resp and not resp.is_closed:
196
- await resp.aclose()
197
- if local_client and client:
198
- await client.aclose()
199
- raise