dan92 commited on
Commit
3e671d8
·
verified ·
1 Parent(s): 00bc2bb

Delete app.py

Browse files
Files changed (1) hide show
  1. app.py +0 -581
app.py DELETED
@@ -1,581 +0,0 @@
1
- import secrets
2
- import time
3
- import uuid
4
- import hashlib
5
- import json
6
- import httpx
7
- import logging
8
- from typing import AsyncGenerator, List, Dict, Union
9
- from pydantic import BaseModel, Field
10
- from fastapi import FastAPI, HTTPException, Header
11
- from fastapi.responses import StreamingResponse
12
- from collections import OrderedDict
13
- from datetime import datetime
14
- import random,uvicorn
15
-
16
- # 设置日志记录
17
- logging.basicConfig(level=logging.INFO)
18
- logger = logging.getLogger(__name__)
19
-
20
- app = FastAPI()
21
-
22
- # 配置
23
- class Config(BaseModel):
24
- # API 密钥
25
- API_KEY: str = Field(
26
- default="sk_gUXNcLwm0rnnEt55Mg8hq88",
27
- description="API key for authentication"
28
- )
29
-
30
- # 最大历史记录数
31
- MAX_HISTORY: int = Field(
32
- default=30,
33
- description="Maximum number of conversation histories to keep"
34
- )
35
-
36
- # API 域名
37
- API_DOMAIN: str = Field(
38
- default="https://ai-api.dangbei.net",
39
- description="API Domain for requests"
40
- )
41
-
42
- # User Agents 列表
43
- USER_AGENTS: List[str] = Field(
44
- default=[
45
- "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36",
46
- "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36",
47
- "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36",
48
- "Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1",
49
- "Mozilla/5.0 (iPad; CPU OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1"
50
- ],
51
- description="List of User Agent strings for requests"
52
- )
53
-
54
- # 每个设备 ID 最大会话数
55
- DEVICE_CONVERSATIONS_LIMIT: int = Field(
56
- default=10,
57
- description="Number of conversations before generating new device ID"
58
- )
59
-
60
- # 创建全局配置实例
61
- config = Config()
62
-
63
- # 辅助函数:验证 API 密钥
64
- async def verify_api_key(authorization: str = Header(None)):
65
- if not authorization:
66
- raise HTTPException(status_code=401, detail="Missing API key")
67
-
68
- api_key = authorization.replace("Bearer ", "").strip()
69
- if api_key != config.API_KEY: # 使用配置中的 API_KEY
70
- raise HTTPException(status_code=401, detail="Invalid API key")
71
- return api_key
72
-
73
- class Message(BaseModel):
74
- role: str
75
- content: str
76
-
77
- class Config:
78
- # 允许额外的字段
79
- extra = "allow"
80
-
81
- class ChatRequest(BaseModel):
82
- model: str
83
- messages: List[Union[dict, Message]] # 允许字典或 Message 对象
84
- stream: bool = False
85
-
86
- # 添加额外的可选字段,以适应更多的客户端请求
87
- temperature: float | None = None
88
- top_p: float | None = None
89
- n: int | None = None
90
- max_tokens: int | None = None
91
- presence_penalty: float | None = None
92
- frequency_penalty: float | None = None
93
- user: str | None = None
94
-
95
- class Config:
96
- # 允许额外的字段
97
- extra = "allow"
98
- # 允许从字典直接构造
99
- arbitrary_types_allowed = True
100
-
101
- @property
102
- def messages_as_dicts(self) -> List[dict]:
103
- """将消息转换为字典格式"""
104
- return [
105
- msg if isinstance(msg, dict) else msg.dict()
106
- for msg in self.messages
107
- ]
108
-
109
- class ChatHistory:
110
- def __init__(self):
111
- self.current_device_id = None
112
- self.current_conversation_id = None
113
- self.conversation_count = 0
114
- self.total_conversations = 0 # 添加总会话计数
115
-
116
- def get_or_create_ids(self, force_new=False) -> tuple[str, str]:
117
- """
118
- 获取或创建新的 device_id 和 conversation_id
119
-
120
- Args:
121
- force_new (bool): 是否强制创建新会话,用于清除上下文
122
-
123
- Returns:
124
- tuple[str, str]: (device_id, conversation_id)
125
- """
126
- # 检查是否需要创建新的设备 ID
127
- if (not self.current_device_id or
128
- self.total_conversations >= config.DEVICE_CONVERSATIONS_LIMIT):
129
- self.current_device_id = self._generate_device_id()
130
- self.current_conversation_id = None
131
- self.conversation_count = 0
132
- self.total_conversations = 0
133
- logger.info(f"Generated new device ID: {self.current_device_id}")
134
-
135
- # 如果强制新建会话(清除上下文)或没有当前会话 ID
136
- if force_new or not self.current_conversation_id:
137
- self.current_conversation_id = None
138
- self.conversation_count = 0
139
- logger.info("Forcing new conversation")
140
-
141
- return self.current_device_id, self.current_conversation_id
142
-
143
- def add_conversation(self, conversation_id: str):
144
- """
145
- 添加新的对话记录
146
-
147
- Args:
148
- conversation_id (str): 新的会话 ID
149
- """
150
- if not self.current_device_id:
151
- return
152
-
153
- self.current_conversation_id = conversation_id
154
- self.conversation_count += 1
155
- self.total_conversations += 1
156
- logger.info(f"Added conversation {conversation_id} (count: {self.conversation_count}, total: {self.total_conversations})")
157
-
158
- def _generate_device_id(self) -> str:
159
- """生成新的设备ID,并随机选择新的 USER_AGENT"""
160
- # 随机选择新的 USER_AGENT
161
- user_agent = random.choice(config.USER_AGENTS)
162
- logger.info(f"Selected new User-Agent: {user_agent}")
163
-
164
- uuid_str = uuid.uuid4().hex
165
- nanoid_str = ''.join(random.choices(
166
- "useandom26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict",
167
- k=20
168
- ))
169
- return f"{uuid_str}_{nanoid_str}"
170
-
171
- class Pipe:
172
- def __init__(self):
173
- self.data_prefix = "data:"
174
- self.user_agent = random.choice(config.USER_AGENTS) # 初始化时随机选择一个 USER_AGENT
175
- self.chat_history = ChatHistory()
176
- # 添加支持联网的模型映射,保持实际请求时使用小写
177
- self.search_models = {
178
- "DeepSeek-R1-Search": "deepseek",
179
- "DeepSeek-V3-Search": "deepseek",
180
- "Doubao-Search": "doubao", # 显示用大写,映射用小写
181
- "Qwen-Search": "qwen" # 显示用大写,映射用小写
182
- }
183
-
184
- def _build_full_prompt(self, messages: List[Dict]) -> str:
185
- """构建完整的提示,包含系统提示、聊天历史和当前问题"""
186
- if not messages:
187
- return ''
188
-
189
- system_prompt = ''
190
- history = []
191
- last_user_message = ''
192
-
193
- # 修改消息处理逻辑,直接使用字典访问
194
- for msg in messages:
195
- if msg['role'] == 'system' and not system_prompt:
196
- system_prompt = msg['content']
197
- elif msg['role'] == 'user':
198
- history.append(f"user: {msg['content']}")
199
- last_user_message = msg['content']
200
- elif msg['role'] == 'assistant':
201
- history.append(f"assistant: {msg['content']}")
202
-
203
- # 构建最终提示
204
- parts = []
205
- if system_prompt:
206
- parts.append(f"[System Prompt]\n{system_prompt}")
207
- if len(history) > 1: # 如果有历史对话
208
- parts.append(f"[Chat History]\n{chr(10).join(history[:-1])}")
209
- parts.append(f"[Question]\n{last_user_message}")
210
-
211
- return chr(10).join(parts)
212
-
213
- async def pipe(self, body: dict) -> AsyncGenerator[Dict, None]:
214
- thinking_state = {"thinking": -1}
215
-
216
- try:
217
- # 构建完整提示
218
- full_prompt = self._build_full_prompt(body["messages"])
219
-
220
- # 修改 force_new_context 的判断逻辑
221
- force_new_context = False
222
- messages = body["messages"]
223
- if len(messages) == 1: # 只有一条消息时,说明是新对话
224
- force_new_context = True
225
- elif len(messages) >= 2: # 检查是否清除了历史
226
- last_two = messages[-2:]
227
- if last_two[0]["role"] == "user" and last_two[1]["role"] == "user":
228
- force_new_context = True
229
-
230
- # 获取或创建设备ID和会话ID
231
- device_id, conversation_id = self.chat_history.get_or_create_ids(force_new_context)
232
-
233
- # 添加会话信息日志
234
- logger.info(f"Current session - Device ID: {device_id}, Conversation ID: {conversation_id}, Force new: {force_new_context}, Messages count: {len(messages)}")
235
-
236
- # 如果没有会话ID,创建新的会话
237
- if not conversation_id:
238
- conversation_id = await self._create_conversation(device_id)
239
- if not conversation_id:
240
- yield {"error": "Failed to create conversation"}
241
- return
242
- # 保存新的对话记录
243
- self.chat_history.add_conversation(conversation_id)
244
- logger.info(f"Created new conversation: {conversation_id}")
245
-
246
- # 模型名称处理
247
- model_name = None
248
- is_search_model = body["model"].endswith("-Search")
249
- if is_search_model:
250
- # 如果是搜索模型,使用映射的基础模型名
251
- base_model = body["model"].replace("-Search", "")
252
- model_name = self.search_models.get(body["model"], base_model.lower())
253
- else:
254
- # 非搜索模型使用原有逻辑
255
- is_deepseek_model = body["model"] in ["DeepSeek-R1", "DeepSeek-V3"]
256
- model_name = "deepseek" if is_deepseek_model else body["model"].lower() # 确保转换为小写
257
-
258
- # 确定 userAction 参数
259
- user_action = ""
260
- if "DeepSeek-R1" in body["model"]:
261
- user_action = "deep"
262
- if is_search_model:
263
- # 如果已经有值,添加逗号分隔
264
- if user_action:
265
- user_action += ",online"
266
- else:
267
- user_action = "online" # 为联网模型设置 userAction 为 "online"
268
-
269
- payload = {
270
- "stream": True,
271
- "botCode": "AI_SEARCH",
272
- "userAction": user_action,
273
- "model": model_name,
274
- "conversationId": conversation_id,
275
- "question": full_prompt,
276
- }
277
-
278
- timestamp = str(int(time.time()))
279
- nonce = self._nanoid(21)
280
- sign = self._generate_sign(timestamp, payload, nonce)
281
-
282
- headers = {
283
- "Origin": "https://ai.dangbei.com",
284
- "Referer": "https://ai.dangbei.com/",
285
- "User-Agent": self.user_agent,
286
- "deviceId": device_id,
287
- "nonce": nonce,
288
- "sign": sign,
289
- "timestamp": timestamp,
290
- }
291
-
292
- api = f"{config.API_DOMAIN}/ai-search/chatApi/v1/chat" # 使用配置中的 API_DOMAIN
293
-
294
- async with httpx.AsyncClient() as client:
295
- async with client.stream("POST", api, json=payload, headers=headers, timeout=1200) as response:
296
- if response.status_code != 200:
297
- error = await response.aread()
298
- yield {"error": self._format_error(response.status_code, error)}
299
- return
300
-
301
- card_messages = [] # 用于收集卡片消息
302
-
303
- async for line in response.aiter_lines():
304
- if not line.startswith(self.data_prefix):
305
- continue
306
-
307
- json_str = line[len(self.data_prefix):]
308
-
309
- try:
310
- data = json.loads(json_str)
311
- except json.JSONDecodeError as e:
312
- yield {"error": f"JSONDecodeError: {str(e)}", "data": json_str}
313
- return
314
-
315
- if data.get("type") == "answer":
316
- content = data.get("content")
317
- content_type = data.get("content_type")
318
-
319
- # 处理思考状态
320
- if thinking_state["thinking"] == -1 and content_type == "thinking":
321
- thinking_state["thinking"] = 0
322
- yield {"choices": [{"delta": {"content": "<think>\n\n"}, "finish_reason": None}]}
323
- elif thinking_state["thinking"] == 0 and content_type == "text":
324
- thinking_state["thinking"] = 1
325
- yield {"choices": [{"delta": {"content": "\n"}, "finish_reason": None}]}
326
- yield {"choices": [{"delta": {"content": "</think>"}, "finish_reason": None}]}
327
- yield {"choices": [{"delta": {"content": "\n\n"}, "finish_reason": None}]}
328
-
329
- # 处理卡片内容
330
- if content_type == "card":
331
- try:
332
- card_content = json.loads(content)
333
- card_items = card_content["cardInfo"]["cardItems"]
334
- markdown_output = "\n\n---\n\n"
335
-
336
- # 处理搜索关键词(type: 2001)
337
- search_keywords = next((item for item in card_items if item["type"] == "2001"), None)
338
- if search_keywords:
339
- keywords = json.loads(search_keywords["content"])
340
- markdown_output += f"搜索关键字:{'; '.join(keywords)}\n"
341
-
342
- # 处理搜索结果(type: 2002)
343
- search_results = next((item for item in card_items if item["type"] == "2002"), None)
344
- if search_results:
345
- results = json.loads(search_results["content"])
346
- markdown_output += f"共找到 {len(results)} 个搜索结果:\n"
347
- for result in results:
348
- markdown_output += f"[{result['idIndex']}] [{result['name']}]({result['url']}) 来源:{result['siteName']}\n"
349
-
350
- card_messages.append(markdown_output)
351
- except Exception as e:
352
- logger.error(f"Error processing card: {str(e)}")
353
-
354
- # 处理普通文本内容
355
- if content and content_type in ["text", "thinking"]:
356
- yield {"choices": [{"delta": {"content": content}, "finish_reason": None}]}
357
-
358
- # 在最后输出所有卡片消息
359
- if card_messages:
360
- yield {"choices": [{"delta": {"content": "".join(card_messages)}, "finish_reason": None}]}
361
-
362
- # 在最后添加元数据
363
- yield {"choices": [{"delta": {"meta": {
364
- "device_id": device_id,
365
- "conversation_id": conversation_id
366
- }}, "finish_reason": None}]}
367
-
368
- except Exception as e:
369
- logger.error(f"Error in pipe: {str(e)}")
370
- yield {"error": self._format_exception(e)}
371
-
372
- def _format_error(self, status_code: int, error: bytes) -> str:
373
- error_str = error.decode(errors="ignore") if isinstance(error, bytes) else error
374
- return json.dumps({"error": f"HTTP {status_code}: {error_str}"}, ensure_ascii=False)
375
-
376
- def _format_exception(self, e: Exception) -> str:
377
- return json.dumps({"error": f"{type(e).__name__}: {str(e)}"}, ensure_ascii=False)
378
-
379
- def _nanoid(self, size=21) -> str:
380
- url_alphabet = "useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict"
381
- random_bytes = secrets.token_bytes(size)
382
- return "".join([url_alphabet[b & 63] for b in reversed(random_bytes)])
383
-
384
- def _generate_sign(self, timestamp: str, payload: dict, nonce: str) -> str:
385
- payload_str = json.dumps(payload, separators=(",", ":"), ensure_ascii=False)
386
- sign_str = f"{timestamp}{payload_str}{nonce}"
387
- return hashlib.md5(sign_str.encode("utf-8")).hexdigest().upper()
388
-
389
- async def _create_conversation(self, device_id: str) -> str:
390
- """创建新的会话"""
391
- payload = {"botCode": "AI_SEARCH"}
392
- timestamp = str(int(time.time()))
393
- nonce = self._nanoid(21)
394
- sign = self._generate_sign(timestamp, payload, nonce)
395
-
396
- headers = {
397
- "Origin": "https://ai.dangbei.com",
398
- "Referer": "https://ai.dangbei.com/",
399
- "User-Agent": self.user_agent,
400
- "deviceId": device_id,
401
- "nonce": nonce,
402
- "sign": sign,
403
- "timestamp": timestamp,
404
- }
405
-
406
- api = f"{config.API_DOMAIN}/ai-search/conversationApi/v1/create"
407
- try:
408
- async with httpx.AsyncClient() as client:
409
- response = await client.post(api, json=payload, headers=headers)
410
- if response.status_code == 200:
411
- data = response.json()
412
- if data.get("success"):
413
- return data["data"]["conversationId"]
414
- except Exception as e:
415
- logger.error(f"Error creating conversation: {str(e)}")
416
- return None
417
-
418
- # 创建实例
419
- pipe = Pipe()
420
-
421
- @app.post("/v1/chat/completions")
422
- async def chat(request: ChatRequest, authorization: str = Header(None)):
423
- """
424
- OpenAI API 兼容的 Chat 端点
425
- """
426
- # 添加请求日志
427
- logger.info(f"Received chat request: {request.model_dump()}")
428
-
429
- await verify_api_key(authorization)
430
-
431
- # 使用 messages_as_dicts 属性
432
- request_data = request.model_dump()
433
- request_data['messages'] = request.messages_as_dicts
434
-
435
- async def response_generator():
436
- """流式响应生成器"""
437
- thinking_content = []
438
- is_thinking = False
439
-
440
- async for chunk in pipe.pipe(request_data):
441
- if "choices" in chunk and chunk["choices"]:
442
- delta = chunk["choices"][0]["delta"]
443
- if "content" in delta:
444
- content = delta["content"]
445
- if content == "<think>\n":
446
- is_thinking = True
447
- elif content == "\n</think>\n\n":
448
- is_thinking = False
449
- if is_thinking and content != "<think>\n":
450
- thinking_content.append(content)
451
-
452
- yield f"data: {json.dumps(chunk, ensure_ascii=False)}\n\n"
453
- yield "data: [DONE]\n\n"
454
-
455
- if request.stream:
456
- return StreamingResponse(response_generator(), media_type="text/event-stream")
457
-
458
- # 非流式响应
459
- content = ""
460
- meta = None
461
- try:
462
- async for chunk in pipe.pipe(request_data):
463
- if "choices" in chunk and chunk["choices"]:
464
- delta = chunk["choices"][0]["delta"]
465
- if "content" in delta:
466
- content += delta["content"]
467
- if "meta" in delta:
468
- meta = delta["meta"]
469
- except Exception as e:
470
- logger.error(f"Error processing chat request: {str(e)}")
471
- raise HTTPException(status_code=500, detail="Internal Server Error")
472
-
473
- parts = content.split("\n\n\n", 1)
474
- reasoning_content = parts[0] if len(parts) > 0 else ""
475
- content = parts[1] if len(parts) > 1 else ""
476
-
477
- # 处理嵌套的 think 标签和特殊字符
478
- if reasoning_content:
479
- # 先尝试找到最外层的 think 标签
480
- start_idx = reasoning_content.find("<think>")
481
- end_idx = reasoning_content.rfind("</think>")
482
-
483
- if start_idx != -1 and end_idx != -1:
484
- # 如果找到了完整的外层标签,提取其中的内容
485
- inner_content = reasoning_content[start_idx + 7:end_idx].strip()
486
- # 移除内部的 think 标签
487
- inner_content = inner_content.replace("<think>", "").replace("</think>", "").strip()
488
- reasoning_content = f"<think>\n{inner_content}\n</think>"
489
- else:
490
- # 如果没有找到完整的标签,则移除所有 think 标签并重新添加
491
- reasoning_content = reasoning_content.replace("<think>", "").replace("</think>", "").strip()
492
- reasoning_content = f"<think>\n{reasoning_content}\n</think>"
493
-
494
- return {
495
- "id": str(uuid.uuid4()),
496
- "object": "chat.completion",
497
- "created": int(time.time()),
498
- "model": request.model,
499
- "choices": [{
500
- "message": {
501
- "role": "assistant",
502
- "reasoning_content": reasoning_content,
503
- "content": content,
504
- "meta": meta
505
- },
506
- "finish_reason": "stop"
507
- }]
508
- }
509
-
510
- @app.get("/v1/models")
511
- async def get_models(authorization: str = Header(None)):
512
- # 验证 API 密钥
513
- await verify_api_key(authorization)
514
-
515
- current_time = int(time.time())
516
- return {
517
- "object": "list",
518
- "data": [
519
- # 原始模型
520
- {
521
- "id": "DeepSeek-R1",
522
- "object": "model",
523
- "created": current_time,
524
- "owned_by": "library"
525
- },
526
- {
527
- "id": "DeepSeek-V3",
528
- "object": "model",
529
- "created": current_time,
530
- "owned_by": "library"
531
- },
532
- {
533
- "id": "Doubao", # 改为大写开头
534
- "object": "model",
535
- "created": current_time,
536
- "owned_by": "library"
537
- },
538
- {
539
- "id": "Qwen", # 改为大写开头
540
- "object": "model",
541
- "created": current_time,
542
- "owned_by": "library"
543
- },
544
- # 支持联网的模型
545
- {
546
- "id": "DeepSeek-R1-Search",
547
- "object": "model",
548
- "created": current_time,
549
- "owned_by": "library",
550
- "features": ["online_search"]
551
- },
552
- {
553
- "id": "DeepSeek-V3-Search",
554
- "object": "model",
555
- "created": current_time,
556
- "owned_by": "library",
557
- "features": ["online_search"]
558
- },
559
- {
560
- "id": "Doubao-Search", # 改为大写开头
561
- "object": "model",
562
- "created": current_time,
563
- "owned_by": "library",
564
- "features": ["online_search"]
565
- },
566
- {
567
- "id": "Qwen-Search", # 改为大写开头
568
- "object": "model",
569
- "created": current_time,
570
- "owned_by": "library",
571
- "features": ["online_search"]
572
- }
573
- ]
574
- }
575
- @app.get("/")
576
- def index():
577
- return "it's work!"
578
-
579
-
580
- if __name__ == "__main__":
581
- uvicorn.run(app, host="0.0.0.0", port=8000)