ZyphrZero commited on
Commit ·
d382277
1
Parent(s): 7d20731
Update main.py
Browse files
main.py
CHANGED
|
@@ -12,8 +12,9 @@ import json
|
|
| 12 |
import os
|
| 13 |
import re
|
| 14 |
import time
|
|
|
|
| 15 |
from datetime import datetime
|
| 16 |
-
from typing import Dict, List, Optional, Any, Union, Generator, Tuple
|
| 17 |
|
| 18 |
import requests
|
| 19 |
from fastapi import FastAPI, Request, Response, HTTPException, Header
|
|
@@ -31,6 +32,7 @@ class ServerConfig:
|
|
| 31 |
# API Configuration
|
| 32 |
API_ENDPOINT: str = os.getenv("API_ENDPOINT", "https://chat.z.ai/api/chat/completions")
|
| 33 |
AUTH_TOKEN: str = os.getenv("AUTH_TOKEN", "sk-your-api-key")
|
|
|
|
| 34 |
BACKUP_TOKEN: str = os.getenv("BACKUP_TOKEN", "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjMxNmJjYjQ4LWZmMmYtNGExNS04NTNkLWYyYTI5YjY3ZmYwZiIsImVtYWlsIjoiR3Vlc3QtMTc1NTg0ODU4ODc4OEBndWVzdC5jb20ifQ.PktllDySS3trlyuFpTeIZf-7hl8Qu1qYF3BxjgIul0BrNux2nX9hVzIjthLXKMWAf9V0qM8Vm_iyDqkjPGsaiQ")
|
| 35 |
|
| 36 |
# Model Configuration
|
|
@@ -179,12 +181,141 @@ class Model(BaseModel):
|
|
| 179 |
owned_by: str
|
| 180 |
|
| 181 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 182 |
class ModelsResponse(BaseModel):
|
| 183 |
"""Models list response model"""
|
| 184 |
object: str = "list"
|
| 185 |
data: List[Model]
|
| 186 |
|
| 187 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 188 |
# =============================================================================
|
| 189 |
# SSE Parser
|
| 190 |
# =============================================================================
|
|
@@ -615,27 +746,6 @@ def get_auth_token() -> str:
|
|
| 615 |
return ServerConfig.BACKUP_TOKEN
|
| 616 |
|
| 617 |
|
| 618 |
-
def transform_thinking_content(content: str) -> str:
|
| 619 |
-
"""Transform thinking content according to configuration"""
|
| 620 |
-
# Remove summary tags
|
| 621 |
-
content = re.sub(r'(?s)<summary>.*?</summary>', '', content)
|
| 622 |
-
# Clean up remaining tags
|
| 623 |
-
content = content.replace("</thinking>", "").replace("<Full>", "").replace("</Full>", "")
|
| 624 |
-
content = content.strip()
|
| 625 |
-
|
| 626 |
-
if ServerConfig.THINKING_PROCESSING == "think":
|
| 627 |
-
content = re.sub(r'<details[^>]*>', '<think>', content)
|
| 628 |
-
content = content.replace("</details>", "</think>")
|
| 629 |
-
elif ServerConfig.THINKING_PROCESSING == "strip":
|
| 630 |
-
content = re.sub(r'<details[^>]*>', '', content)
|
| 631 |
-
content = content.replace("</details>", "")
|
| 632 |
-
|
| 633 |
-
# Remove line prefixes
|
| 634 |
-
content = content.lstrip("> ")
|
| 635 |
-
content = content.replace("\n> ", "\n")
|
| 636 |
-
|
| 637 |
-
return content.strip()
|
| 638 |
-
|
| 639 |
|
| 640 |
def create_openai_response_chunk(
|
| 641 |
model: str,
|
|
@@ -1159,6 +1269,174 @@ async def chat_completions(
|
|
| 1159 |
raise HTTPException(status_code=500, detail=f"Internal server error: {str(e)}")
|
| 1160 |
|
| 1161 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1162 |
# =============================================================================
|
| 1163 |
# Main Entry Point
|
| 1164 |
# =============================================================================
|
|
|
|
| 12 |
import os
|
| 13 |
import re
|
| 14 |
import time
|
| 15 |
+
import uuid
|
| 16 |
from datetime import datetime
|
| 17 |
+
from typing import Dict, List, Optional, Any, Union, Generator, Tuple, Literal
|
| 18 |
|
| 19 |
import requests
|
| 20 |
from fastapi import FastAPI, Request, Response, HTTPException, Header
|
|
|
|
| 32 |
# API Configuration
|
| 33 |
API_ENDPOINT: str = os.getenv("API_ENDPOINT", "https://chat.z.ai/api/chat/completions")
|
| 34 |
AUTH_TOKEN: str = os.getenv("AUTH_TOKEN", "sk-your-api-key")
|
| 35 |
+
ANTHROPIC_API_KEY: str = os.getenv("ANTHROPIC_API_KEY", AUTH_TOKEN)
|
| 36 |
BACKUP_TOKEN: str = os.getenv("BACKUP_TOKEN", "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjMxNmJjYjQ4LWZmMmYtNGExNS04NTNkLWYyYTI5YjY3ZmYwZiIsImVtYWlsIjoiR3Vlc3QtMTc1NTg0ODU4ODc4OEBndWVzdC5jb20ifQ.PktllDySS3trlyuFpTeIZf-7hl8Qu1qYF3BxjgIul0BrNux2nX9hVzIjthLXKMWAf9V0qM8Vm_iyDqkjPGsaiQ")
|
| 37 |
|
| 38 |
# Model Configuration
|
|
|
|
| 181 |
owned_by: str
|
| 182 |
|
| 183 |
|
| 184 |
+
# ANTHROPIC API 兼容性模型
|
| 185 |
+
class ContentBlock(BaseModel):
|
| 186 |
+
type: str
|
| 187 |
+
text: str
|
| 188 |
+
|
| 189 |
+
|
| 190 |
+
class AnthropicMessage(BaseModel):
|
| 191 |
+
role: Literal["user", "assistant"]
|
| 192 |
+
content: Union[str, List[ContentBlock]]
|
| 193 |
+
|
| 194 |
+
|
| 195 |
+
class AnthropicRequest(BaseModel):
|
| 196 |
+
model: str
|
| 197 |
+
messages: List[AnthropicMessage]
|
| 198 |
+
system: Optional[Union[str, List[ContentBlock]]] = None
|
| 199 |
+
max_tokens: int = 1024
|
| 200 |
+
stream: bool = False
|
| 201 |
+
temperature: Optional[float] = None
|
| 202 |
+
|
| 203 |
+
|
| 204 |
class ModelsResponse(BaseModel):
|
| 205 |
"""Models list response model"""
|
| 206 |
object: str = "list"
|
| 207 |
data: List[Model]
|
| 208 |
|
| 209 |
|
| 210 |
+
# ANTHROPIC API 兼容性函数
|
| 211 |
+
def stream_anthropic_generator(upstream_response: requests.Response, request_id: str, requested_model: str):
|
| 212 |
+
"""生成 Anthropic 兼容的流式响应事件"""
|
| 213 |
+
usage = {"input_tokens": 0, "output_tokens": 0}
|
| 214 |
+
|
| 215 |
+
start_event = {
|
| 216 |
+
"type": "message_start",
|
| 217 |
+
"message": {
|
| 218 |
+
"id": request_id,
|
| 219 |
+
"type": "message",
|
| 220 |
+
"role": "assistant",
|
| 221 |
+
"content": [],
|
| 222 |
+
"model": requested_model,
|
| 223 |
+
"stop_reason": None,
|
| 224 |
+
"stop_sequence": None,
|
| 225 |
+
"usage": usage
|
| 226 |
+
}
|
| 227 |
+
}
|
| 228 |
+
yield f"event: {start_event['type']}\ndata: {json.dumps(start_event['message'])}\n\n"
|
| 229 |
+
|
| 230 |
+
# 发送 content_block_start 事件
|
| 231 |
+
content_start_data = {
|
| 232 |
+
"type": "content_block_start",
|
| 233 |
+
"index": 0,
|
| 234 |
+
"content_block": {
|
| 235 |
+
"type": "text",
|
| 236 |
+
"text": ""
|
| 237 |
+
}
|
| 238 |
+
}
|
| 239 |
+
yield f"event: content_block_start\ndata: {json.dumps(content_start_data)}\n\n"
|
| 240 |
+
|
| 241 |
+
# 处理上游响应
|
| 242 |
+
for line in upstream_response.iter_lines():
|
| 243 |
+
if not line.startswith(b"data:"): continue
|
| 244 |
+
data_str = line[5:].strip()
|
| 245 |
+
if not data_str: continue
|
| 246 |
+
try:
|
| 247 |
+
data = json.loads(data_str.decode('utf-8'))
|
| 248 |
+
delta_content = data.get("data", {}).get("delta_content", "")
|
| 249 |
+
phase = data.get("data", {}).get("phase", "")
|
| 250 |
+
|
| 251 |
+
# 处理内容增量
|
| 252 |
+
if delta_content:
|
| 253 |
+
out_content = transform_thinking_content(delta_content) if phase == "thinking" else delta_content
|
| 254 |
+
if out_content:
|
| 255 |
+
usage["output_tokens"] += len(out_content) // 4 # 简单估算
|
| 256 |
+
delta_data = {
|
| 257 |
+
"type": "content_block_delta",
|
| 258 |
+
"index": 0,
|
| 259 |
+
"delta": {
|
| 260 |
+
"type": "text_delta",
|
| 261 |
+
"text": out_content
|
| 262 |
+
}
|
| 263 |
+
}
|
| 264 |
+
yield f"event: content_block_delta\ndata: {json.dumps(delta_data)}\n\n"
|
| 265 |
+
|
| 266 |
+
# 处理结束
|
| 267 |
+
if data.get("data", {}).get("done", False) or phase == "done":
|
| 268 |
+
# 发送 content_block_stop
|
| 269 |
+
content_stop_data = {
|
| 270 |
+
"type": "content_block_stop",
|
| 271 |
+
"index": 0
|
| 272 |
+
}
|
| 273 |
+
yield f"event: content_block_stop\ndata: {json.dumps(content_stop_data)}\n\n"
|
| 274 |
+
|
| 275 |
+
# 发送 message_delta
|
| 276 |
+
message_delta_data = {
|
| 277 |
+
"type": "message_delta",
|
| 278 |
+
"delta": {
|
| 279 |
+
"stop_reason": "end_turn",
|
| 280 |
+
"stop_sequence": None,
|
| 281 |
+
"usage": {
|
| 282 |
+
"input_tokens": usage["input_tokens"],
|
| 283 |
+
"output_tokens": usage["output_tokens"]
|
| 284 |
+
}
|
| 285 |
+
}
|
| 286 |
+
}
|
| 287 |
+
yield f"event: message_delta\ndata: {json.dumps(message_delta_data)}\n\n"
|
| 288 |
+
|
| 289 |
+
# 发送 message_stop
|
| 290 |
+
yield f"event: message_stop\ndata: {json.dumps({'type': 'message_stop'})}\n\n"
|
| 291 |
+
break
|
| 292 |
+
|
| 293 |
+
except json.JSONDecodeError:
|
| 294 |
+
continue
|
| 295 |
+
|
| 296 |
+
|
| 297 |
+
def transform_thinking_content(content: str) -> str:
|
| 298 |
+
"""Transform thinking content according to configuration"""
|
| 299 |
+
# Remove summary tags
|
| 300 |
+
content = re.sub(r'(?s)<summary>.*?</summary>', '', content)
|
| 301 |
+
# Clean up remaining tags
|
| 302 |
+
content = content.replace("</thinking>", "").replace("<Full>", "").replace("</Full>", "")
|
| 303 |
+
content = content.strip()
|
| 304 |
+
|
| 305 |
+
if ServerConfig.THINKING_PROCESSING == "think":
|
| 306 |
+
content = re.sub(r'<details[^>]*>', '<think>', content)
|
| 307 |
+
content = content.replace("</details>", "</think>")
|
| 308 |
+
elif ServerConfig.THINKING_PROCESSING == "strip":
|
| 309 |
+
content = re.sub(r'<details[^>]*>', '', content)
|
| 310 |
+
content = content.replace("</details>", "")
|
| 311 |
+
|
| 312 |
+
# Remove line prefixes
|
| 313 |
+
content = content.lstrip("> ")
|
| 314 |
+
content = content.replace("\n> ", "\n")
|
| 315 |
+
|
| 316 |
+
return content.strip()
|
| 317 |
+
|
| 318 |
+
|
| 319 |
# =============================================================================
|
| 320 |
# SSE Parser
|
| 321 |
# =============================================================================
|
|
|
|
| 746 |
return ServerConfig.BACKUP_TOKEN
|
| 747 |
|
| 748 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 749 |
|
| 750 |
def create_openai_response_chunk(
|
| 751 |
model: str,
|
|
|
|
| 1269 |
raise HTTPException(status_code=500, detail=f"Internal server error: {str(e)}")
|
| 1270 |
|
| 1271 |
|
| 1272 |
+
# ANTHROPIC API 兼容端点
|
| 1273 |
+
@app.post("/v1/messages")
|
| 1274 |
+
async def handle_anthropic_message(
|
| 1275 |
+
req: AnthropicRequest,
|
| 1276 |
+
x_api_key: str = Header(None, alias="x-api-key"),
|
| 1277 |
+
authorization: str = Header(None, alias="authorization")
|
| 1278 |
+
):
|
| 1279 |
+
"""Handle Anthropic message requests"""
|
| 1280 |
+
debug_log("收到 Anthropic message 请求")
|
| 1281 |
+
|
| 1282 |
+
# 验证 API key
|
| 1283 |
+
api_key = None
|
| 1284 |
+
if x_api_key:
|
| 1285 |
+
api_key = x_api_key
|
| 1286 |
+
elif authorization and authorization.startswith("Bearer "):
|
| 1287 |
+
api_key = authorization[7:]
|
| 1288 |
+
|
| 1289 |
+
if not api_key or api_key != ServerConfig.ANTHROPIC_API_KEY:
|
| 1290 |
+
debug_log(f"无效的 API key: {api_key}")
|
| 1291 |
+
raise HTTPException(status_code=401, detail="Invalid API key")
|
| 1292 |
+
|
| 1293 |
+
debug_log(f"API key 验证通过")
|
| 1294 |
+
debug_log(f"请求解析成功 - 模型: {req.model}, 流式: {req.stream}, 消息数: {len(req.messages)}")
|
| 1295 |
+
|
| 1296 |
+
# 确定上游模型和功能
|
| 1297 |
+
upstream_model = "GLM-4.5"
|
| 1298 |
+
if req.model == ServerConfig.THINKING_MODEL:
|
| 1299 |
+
upstream_model = "GLM-4.5-Thinking"
|
| 1300 |
+
elif req.model == ServerConfig.SEARCH_MODEL:
|
| 1301 |
+
upstream_model = "GLM-4.5-Search"
|
| 1302 |
+
|
| 1303 |
+
debug_log(f"收到请求 (模型: {req.model}) -> 代理到上游 (模型: {upstream_model})")
|
| 1304 |
+
|
| 1305 |
+
# 生成 ID
|
| 1306 |
+
chat_id, msg_id = generate_request_ids()
|
| 1307 |
+
|
| 1308 |
+
# 转换消息格式
|
| 1309 |
+
openai_messages = []
|
| 1310 |
+
if req.system:
|
| 1311 |
+
# 处理两种格式的 system 内容
|
| 1312 |
+
if isinstance(req.system, str):
|
| 1313 |
+
# 字符串格式
|
| 1314 |
+
system_content = req.system
|
| 1315 |
+
else:
|
| 1316 |
+
# 对象数组格式
|
| 1317 |
+
system_content = ""
|
| 1318 |
+
for block in req.system:
|
| 1319 |
+
if block.type == "text":
|
| 1320 |
+
system_content += block.text
|
| 1321 |
+
|
| 1322 |
+
openai_messages.append({"role": "system", "content": system_content})
|
| 1323 |
+
|
| 1324 |
+
for msg in req.messages:
|
| 1325 |
+
# 处理两种格式的内容
|
| 1326 |
+
if isinstance(msg.content, str):
|
| 1327 |
+
# 字符串格式
|
| 1328 |
+
text_content = msg.content
|
| 1329 |
+
else:
|
| 1330 |
+
# 对象数组格式
|
| 1331 |
+
text_content = ""
|
| 1332 |
+
for block in msg.content:
|
| 1333 |
+
if block.type == "text":
|
| 1334 |
+
text_content += block.text
|
| 1335 |
+
|
| 1336 |
+
openai_messages.append({
|
| 1337 |
+
"role": msg.role,
|
| 1338 |
+
"content": text_content
|
| 1339 |
+
})
|
| 1340 |
+
|
| 1341 |
+
# 构建上游请求
|
| 1342 |
+
upstream_messages = []
|
| 1343 |
+
for msg in openai_messages:
|
| 1344 |
+
content = msg.get("content", "")
|
| 1345 |
+
if content is None:
|
| 1346 |
+
content = ""
|
| 1347 |
+
upstream_messages.append(Message(
|
| 1348 |
+
role=msg["role"],
|
| 1349 |
+
content=content
|
| 1350 |
+
))
|
| 1351 |
+
|
| 1352 |
+
upstream_req = UpstreamRequest(
|
| 1353 |
+
stream=True, # 总是使用上游的流式
|
| 1354 |
+
chat_id=chat_id,
|
| 1355 |
+
id=msg_id,
|
| 1356 |
+
model="0727-360B-API", # 实际的上游模型 ID
|
| 1357 |
+
messages=upstream_messages,
|
| 1358 |
+
params={},
|
| 1359 |
+
features={"enable_thinking": True},
|
| 1360 |
+
background_tasks={
|
| 1361 |
+
"title_generation": False,
|
| 1362 |
+
"tags_generation": False,
|
| 1363 |
+
},
|
| 1364 |
+
mcp_servers=[],
|
| 1365 |
+
model_item=ModelItem(
|
| 1366 |
+
id="0727-360B-API",
|
| 1367 |
+
name="GLM-4.5",
|
| 1368 |
+
owned_by="openai"
|
| 1369 |
+
),
|
| 1370 |
+
tool_servers=[],
|
| 1371 |
+
variables={
|
| 1372 |
+
"{{USER_NAME}}": "User",
|
| 1373 |
+
"{{USER_LOCATION}}": "Unknown",
|
| 1374 |
+
"{{CURRENT_DATETIME}}": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
| 1375 |
+
}
|
| 1376 |
+
)
|
| 1377 |
+
|
| 1378 |
+
# 获取认证 token
|
| 1379 |
+
auth_token = get_auth_token()
|
| 1380 |
+
|
| 1381 |
+
try:
|
| 1382 |
+
# 调用上游 API
|
| 1383 |
+
headers = get_browser_headers(chat_id)
|
| 1384 |
+
headers["Authorization"] = f"Bearer {auth_token}"
|
| 1385 |
+
|
| 1386 |
+
response = requests.post(
|
| 1387 |
+
ServerConfig.API_ENDPOINT,
|
| 1388 |
+
json=upstream_req.model_dump(exclude_none=True),
|
| 1389 |
+
headers=headers,
|
| 1390 |
+
timeout=60.0,
|
| 1391 |
+
stream=True
|
| 1392 |
+
)
|
| 1393 |
+
response.raise_for_status()
|
| 1394 |
+
except requests.HTTPError as e:
|
| 1395 |
+
debug_log(f"上游 API 返回错误状态: {e.response.status_code}, 响应: {e.response.text}")
|
| 1396 |
+
raise HTTPException(status_code=502, detail="Upstream API error")
|
| 1397 |
+
except requests.RequestException as e:
|
| 1398 |
+
debug_log(f"请求上游 API 失败: {e}")
|
| 1399 |
+
raise HTTPException(status_code=502, detail=f"Failed to call upstream API: {e}")
|
| 1400 |
+
|
| 1401 |
+
request_id = f"msg_{uuid.uuid4().hex}"
|
| 1402 |
+
|
| 1403 |
+
if req.stream:
|
| 1404 |
+
# 流式响应
|
| 1405 |
+
return StreamingResponse(
|
| 1406 |
+
stream_anthropic_generator(response, request_id, req.model),
|
| 1407 |
+
media_type="text/event-stream",
|
| 1408 |
+
headers={"Cache-Control": "no-cache", "Connection": "keep-alive"}
|
| 1409 |
+
)
|
| 1410 |
+
else:
|
| 1411 |
+
# 非流式响应
|
| 1412 |
+
full_content = ""
|
| 1413 |
+
for line in response.iter_lines():
|
| 1414 |
+
if not line.startswith(b"data:"): continue
|
| 1415 |
+
data_str = line[5:].strip()
|
| 1416 |
+
if not data_str: continue
|
| 1417 |
+
try:
|
| 1418 |
+
data = json.loads(data_str.decode('utf-8'))
|
| 1419 |
+
delta_content = data.get("data", {}).get("delta_content", "")
|
| 1420 |
+
phase = data.get("data", {}).get("phase", "")
|
| 1421 |
+
if delta_content:
|
| 1422 |
+
out_content = transform_thinking_content(delta_content) if phase == "thinking" else delta_content
|
| 1423 |
+
if out_content: full_content += out_content
|
| 1424 |
+
if data.get("data", {}).get("done", False) or phase == "done":
|
| 1425 |
+
break
|
| 1426 |
+
except json.JSONDecodeError:
|
| 1427 |
+
continue
|
| 1428 |
+
|
| 1429 |
+
return {
|
| 1430 |
+
"id": request_id,
|
| 1431 |
+
"type": "message",
|
| 1432 |
+
"role": "assistant",
|
| 1433 |
+
"model": req.model,
|
| 1434 |
+
"content": [{"type": "text", "text": full_content}],
|
| 1435 |
+
"stop_reason": "end_turn",
|
| 1436 |
+
"usage": {"input_tokens": 0, "output_tokens": len(full_content) // 4}
|
| 1437 |
+
}
|
| 1438 |
+
|
| 1439 |
+
|
| 1440 |
# =============================================================================
|
| 1441 |
# Main Entry Point
|
| 1442 |
# =============================================================================
|