yuoop commited on
Commit
779c51d
·
verified ·
1 Parent(s): 38e91b7

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +220 -135
app.py CHANGED
@@ -1,12 +1,23 @@
1
  from fastapi import FastAPI, HTTPException, Request
2
- from fastapi.responses import JSONResponse
 
3
  import requests
4
  import json
5
  import os
6
  import time
 
7
 
8
  app = FastAPI()
9
 
 
 
 
 
 
 
 
 
 
10
  # 环境变量配置
11
  STATUS_URL = os.environ.get("STATUS_URL", "https://duckduckgo.com/duckchat/v1/status")
12
  CHAT_URL = os.environ.get("CHAT_URL", "https://duckduckgo.com/duckchat/v1/chat")
@@ -16,151 +27,225 @@ USER_AGENT = os.environ.get("USER_AGENT", "Mozilla/5.0 (Macintosh; Intel Mac OS
16
  COOKIE = os.environ.get("COOKIE", "dcm=3; s=l; bf=1") # 从环境变量获取 Cookie
17
 
18
  DEFAULT_HEADERS = {
19
- "User-Agent": USER_AGENT,
20
- "Accept": "text/event-stream",
21
- "Accept-Language": "en-US,en;q=0.5",
22
- "Accept-Encoding": "gzip, deflate, br",
23
- "Referer": REFERER,
24
- "Content-Type": "application/json",
25
- "Origin": ORIGIN,
26
- "Connection": "keep-alive",
27
- "Cookie": COOKIE,
28
- "Sec-Fetch-Dest": "empty",
29
- "Sec-Fetch-Mode": "cors",
30
- "Sec-Fetch-Site": "same-origin",
31
- "Pragma": "no-cache",
32
- "TE": "trailers",
33
  }
34
 
35
  SUPPORTED_MODELS = ["o3-mini", "gpt-4o-mini", "claude-3-haiku-20240307", "meta-llama/Llama-3.3-70B-Instruct-Turbo"]
36
 
37
  async def get_vqd():
38
- """获取 DuckDuckGo Chat 的 VQD 值。"""
39
- headers = {**DEFAULT_HEADERS, "x-vqd-accept": "1"}
40
- try:
41
- response = requests.get(STATUS_URL, headers=headers)
42
- response.raise_for_status() # 抛出 HTTPError,如果状态码不是 200
43
- vqd = response.headers.get("x-vqd-4")
44
- if not vqd:
45
- raise ValueError("x-vqd-4 header 在响应中未找到。")
46
- return vqd
47
- except requests.exceptions.RequestException as e:
48
- raise HTTPException(status_code=500, detail=f"HTTP 请求失败: {e}")
49
- except ValueError as e:
50
- raise HTTPException(status_code=500, detail=str(e))
51
-
52
- async def duckduckgo_chat(model, messages):
53
- """与 DuckDuckGo Chat 进行交互。"""
54
- try:
55
- x_vqd_4 = await get_vqd()
56
-
57
- chat_headers = {
58
- **DEFAULT_HEADERS,
59
- "x-vqd-4": x_vqd_4,
60
- }
61
-
62
- body = json.dumps({
63
- "model": model,
64
- "messages": messages,
65
- })
66
-
67
- response = requests.post(CHAT_URL, headers=chat_headers, data=body, stream=True)
68
- response.raise_for_status()
69
-
70
- full_message = ""
71
- for line in response.iter_lines():
72
- if line:
73
- decoded_line = line.decode('utf-8')
74
- if decoded_line.startswith("data: "):
75
- try:
76
- json_data = json.loads(decoded_line[5:])
77
- full_message += json_data.get("message", "")
78
- except json.JSONDecodeError as e:
79
- print(f"JSON 解析错误: {e}, 行: {decoded_line}")
80
- pass # 忽略解析错误
81
-
82
- return full_message
83
-
84
- except requests.exceptions.RequestException as e:
85
- raise HTTPException(status_code=500, detail=f"HTTP 请求失败: {e}")
86
- except Exception as e:
87
- raise HTTPException(status_code=500, detail=f"聊天过程中发生错误: {e}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
88
 
89
  @app.post("/v1/chat/completions")
90
  async def chat_completions(request: Request):
91
- try:
92
- body = await request.json()
93
- if not body:
94
- raise HTTPException(status_code=400, detail="无效的请求体")
95
-
96
- model = body.get("model", "o3-mini")
97
- if model not in SUPPORTED_MODELS:
98
- raise HTTPException(status_code=400, detail=f"模型 \"{model}\" 不支持。支持的模型有: {', '.join(SUPPORTED_MODELS)}.")
99
-
100
- messages = body.get("messages")
101
- if not messages:
102
- raise HTTPException(status_code=400, detail="未提供任何消息内容")
103
-
104
- # 处理 system 消息
105
- system_message = next((msg for msg in messages if msg.get("role") == "system"), None)
106
- system_prompt = f"你将扮演一个{system_message['content']}.\n" if system_message else ""
107
-
108
- # 提取历史消息并格式化
109
- history_messages = "\n".join(
110
- f"{msg['role']}: {msg['content']}"
111
- for msg in messages
112
- if msg.get("role") != "system" and msg != messages[-1]
113
- )
114
-
115
- # 提取最后一条用��消息
116
- last_user_message = messages[-1]
117
- current_question = last_user_message["content"] if last_user_message.get("role") == "user" else ""
118
-
119
- # 构建合并后的消息
120
- combined_message_content = f"{system_prompt}以下是历史对话记录:\n{history_messages}\n用户当前提问:{current_question}"
121
- combined_message = {"role": "user", "content": combined_message_content}
122
-
123
- # 发送单条消息
124
- response_text = await duckduckgo_chat(model, [combined_message])
125
-
126
- # 构建 OpenAI 风格的响应
127
- openai_response = {
128
- "id": f"chatcmpl-{int(time.time() * 1000)}", # 生成唯一 ID
129
- "object": "chat.completion",
130
- "created": int(time.time()),
131
- "model": model,
132
- "choices": [
133
- {
134
- "message": {
135
- "role": "assistant",
136
- "content": response_text,
137
- },
138
- "finish_reason": "stop",
139
- "index": 0,
140
- },
141
- ],
142
- "usage": {
143
- "prompt_tokens": 0,
144
- "completion_tokens": 0,
145
- "total_tokens": 0
146
- },
147
- }
148
-
149
- return JSONResponse(content=openai_response)
150
-
151
- except HTTPException as e:
152
- raise e # 重新抛出 HTTPException,以便 FastAPI 处理
153
- except Exception as e:
154
- print(f"API 错误: {e}")
155
- raise HTTPException(status_code=500, detail=f"服务器内部错误: {e}")
 
 
 
 
 
156
 
157
  @app.exception_handler(HTTPException)
158
  async def http_exception_handler(request: Request, exc: HTTPException):
159
- return JSONResponse(
160
- status_code=exc.status_code,
161
- content={"detail": exc.detail},
162
- )
 
163
 
164
  @app.get("/")
165
  async def greet_json():
166
- return {"Hello": "World!"}
 
1
  from fastapi import FastAPI, HTTPException, Request
2
+ from fastapi.responses import JSONResponse, StreamingResponse
3
+ from fastapi.middleware.cors import CORSMiddleware
4
  import requests
5
  import json
6
  import os
7
  import time
8
+ import asyncio
9
 
10
  app = FastAPI()
11
 
12
+ # CORS 设置
13
+ app.add_middleware(
14
+ CORSMiddleware,
15
+ allow_origins=["*"], # 允许所有来源,生产环境请修改为具体的域名
16
+ allow_credentials=True,
17
+ allow_methods=["*"],
18
+ allow_headers=["*"],
19
+ )
20
+
21
  # 环境变量配置
22
  STATUS_URL = os.environ.get("STATUS_URL", "https://duckduckgo.com/duckchat/v1/status")
23
  CHAT_URL = os.environ.get("CHAT_URL", "https://duckduckgo.com/duckchat/v1/chat")
 
27
  COOKIE = os.environ.get("COOKIE", "dcm=3; s=l; bf=1") # 从环境变量获取 Cookie
28
 
29
  DEFAULT_HEADERS = {
30
+ "User-Agent": USER_AGENT,
31
+ "Accept": "text/event-stream",
32
+ "Accept-Language": "en-US,en;q=0.5",
33
+ "Referer": REFERER,
34
+ "Content-Type": "application/json",
35
+ "Origin": ORIGIN,
36
+ "Connection": "keep-alive",
37
+ "Cookie": COOKIE,
38
+ "Sec-Fetch-Dest": "empty",
39
+ "Sec-Fetch-Mode": "cors",
40
+ "Sec-Fetch-Site": "same-origin",
41
+ "Pragma": "no-cache",
42
+ "TE": "trailers",
 
43
  }
44
 
45
  SUPPORTED_MODELS = ["o3-mini", "gpt-4o-mini", "claude-3-haiku-20240307", "meta-llama/Llama-3.3-70B-Instruct-Turbo"]
46
 
47
  async def get_vqd():
48
+ """获取 DuckDuckGo Chat 的 VQD 值。"""
49
+ headers = {**DEFAULT_HEADERS, "x-vqd-accept": "1"}
50
+ try:
51
+ response = requests.get(STATUS_URL, headers=headers)
52
+ response.raise_for_status() # 抛出 HTTPError,如果状态码不是 200
53
+ vqd = response.headers.get("x-vqd-4")
54
+ if not vqd:
55
+ raise ValueError("x-vqd-4 header 在响应中未找到。")
56
+ return vqd
57
+ except requests.exceptions.RequestException as e:
58
+ raise HTTPException(status_code=500, detail=f"HTTP 请求失败: {e}")
59
+ except ValueError as e:
60
+ raise HTTPException(status_code=500, detail=str(e))
61
+
62
+ async def duckduckgo_chat_stream(model, messages):
63
+ """与 DuckDuckGo Chat 进行交互,流式输出。"""
64
+ try:
65
+ x_vqd_4 = await get_vqd()
66
+
67
+ chat_headers = {
68
+ **DEFAULT_HEADERS,
69
+ "x-vqd-4": x_vqd_4,
70
+ "Accept": "text/event-stream", # 确保接受 SSE
71
+ }
72
+
73
+ body = json.dumps({
74
+ "model": model,
75
+ "messages": messages,
76
+ })
77
+
78
+ response = requests.post(CHAT_URL, headers=chat_headers, data=body, stream=True)
79
+ response.raise_for_status()
80
+
81
+ async def event_stream():
82
+ try:
83
+ for line in response.iter_lines():
84
+ if line:
85
+ decoded_line = line.decode('utf-8')
86
+ if decoded_line.startswith("data: "):
87
+ try:
88
+ json_data = json.loads(decoded_line[5:])
89
+ message_content = json_data.get("message", "")
90
+ if message_content:
91
+ # 构建 OpenAI 风格的流式响应
92
+ openai_stream_response = {
93
+ "id": f"chatcmpl-{int(time.time() * 1000)}",
94
+ "object": "chat.completion.chunk",
95
+ "created": int(time.time()),
96
+ "model": model,
97
+ "choices": [
98
+ {
99
+ "delta": {
100
+ "content": message_content
101
+ },
102
+ "index": 0,
103
+ "finish_reason": None
104
+ }
105
+ ],
106
+ }
107
+ yield f"data: {json.dumps(openai_stream_response)}\n\n"
108
+ await asyncio.sleep(0.01) # 避免 CPU 占用过高
109
+ except json.JSONDecodeError as e:
110
+ print(f"JSON 解析错误: {e}, 行: {decoded_line}")
111
+ yield f"data: {json.dumps({'error': 'JSON 解析错误'})}\n\n" # 返回错误信息
112
+ break # 停止流式传输
113
+ except requests.exceptions.RequestException as e:
114
+ print(f"请求错误: {e}")
115
+ yield f"data: {json.dumps({'error': '请求错误'})}\n\n"
116
+ except Exception as e:
117
+ print(f"发生错误: {e}")
118
+ yield f"data: {json.dumps({'error': '发生错误'})}\n\n"
119
+ finally:
120
+ yield "data: [DONE]\n\n" # 结束 SSE 流
121
+
122
+ return StreamingResponse(event_stream(), media_type="text/event-stream")
123
+
124
+ except requests.exceptions.RequestException as e:
125
+ raise HTTPException(status_code=500, detail=f"HTTP 请求失败: {e}")
126
+ except Exception as e:
127
+ raise HTTPException(status_code=500, detail=f"聊天过程中发生错误: {e}")
128
+
129
+
130
+ async def duckduckgo_chat_non_stream(model, messages):
131
+ """与 DuckDuckGo Chat 进行交互,非流式输出。"""
132
+ try:
133
+ x_vqd_4 = await get_vqd()
134
+
135
+ chat_headers = {
136
+ **DEFAULT_HEADERS,
137
+ "x-vqd-4": x_vqd_4,
138
+ }
139
+
140
+ body = json.dumps({
141
+ "model": model,
142
+ "messages": messages,
143
+ })
144
+
145
+ response = requests.post(CHAT_URL, headers=chat_headers, data=body)
146
+ response.raise_for_status()
147
+
148
+ full_message = ""
149
+ for line in response.iter_lines():
150
+ if line:
151
+ decoded_line = line.decode('utf-8')
152
+ if decoded_line.startswith("data: "):
153
+ try:
154
+ json_data = json.loads(decoded_line[5:])
155
+ full_message += json_data.get("message", "")
156
+ except json.JSONDecodeError as e:
157
+ print(f"JSON 解析错误: {e}, 行: {decoded_line}")
158
+ pass # 忽略解析错误
159
+
160
+ return full_message
161
+
162
+ except requests.exceptions.RequestException as e:
163
+ raise HTTPException(status_code=500, detail=f"HTTP 请求失败: {e}")
164
+ except Exception as e:
165
+ raise HTTPException(status_code=500, detail=f"聊天过程中发生错误: {e}")
166
+
167
 
168
  @app.post("/v1/chat/completions")
169
  async def chat_completions(request: Request):
170
+ try:
171
+ body = await request.json()
172
+ if not body:
173
+ raise HTTPException(status_code=400, detail="无效的请求体")
174
+
175
+ model = body.get("model", "o3-mini")
176
+ if model not in SUPPORTED_MODELS:
177
+ raise HTTPException(status_code=400, detail=f"模型 \"{model}\" 不支持。支持的模型有: {', '.join(SUPPORTED_MODELS)}.")
178
+
179
+ messages = body.get("messages")
180
+ if not messages:
181
+ raise HTTPException(status_code=400, detail="未提供任何消息内容")
182
+
183
+ stream = body.get("stream", False) # 获取 stream 参数,默认为 False
184
+
185
+ # 处理 system 消息
186
+ system_message = next((msg for msg in messages if msg.get("role") == "system"), None)
187
+ system_prompt = f"你将扮演一个{system_message['content']}.\n" if system_message else ""
188
+
189
+ # 提取历史消息并格式化
190
+ history_messages = "\n".join(
191
+ f"{msg['role']}: {msg['content']}"
192
+ for msg in messages
193
+ if msg.get("role") != "system" and msg != messages[-1]
194
+ )
195
+
196
+ # 提取最后一条用户消息
197
+ last_user_message = messages[-1]
198
+ current_question = last_user_message["content"] if last_user_message.get("role") == "user" else ""
199
+
200
+ # 构建合并后的消息
201
+ combined_message_content = f"{system_prompt}以下是历史对话记录:\n{history_messages}\n用户当前提问:{current_question}"
202
+ combined_message = {"role": "user", "content": combined_message_content}
203
+
204
+ if stream:
205
+ return await duckduckgo_chat_stream(model, [combined_message])
206
+ else:
207
+ response_text = await duckduckgo_chat_non_stream(model, [combined_message])
208
+
209
+ # 构建 OpenAI 风格的响应
210
+ openai_response = {
211
+ "id": f"chatcmpl-{int(time.time() * 1000)}", # 生成唯一 ID
212
+ "object": "chat.completion",
213
+ "created": int(time.time()),
214
+ "model": model,
215
+ "choices": [
216
+ {
217
+ "message": {
218
+ "role": "assistant",
219
+ "content": response_text,
220
+ },
221
+ "finish_reason": "stop",
222
+ "index": 0,
223
+ },
224
+ ],
225
+ "usage": {
226
+ "prompt_tokens": 0,
227
+ "completion_tokens": 0,
228
+ "total_tokens": 0
229
+ },
230
+ }
231
+
232
+ return JSONResponse(content=openai_response)
233
+
234
+ except HTTPException as e:
235
+ raise e # 重新抛出 HTTPException,以便 FastAPI 处理
236
+ except Exception as e:
237
+ print(f"API 错误: {e}")
238
+ raise HTTPException(status_code=500, detail=f"服务器内部错误: {e}")
239
+
240
 
241
  @app.exception_handler(HTTPException)
242
  async def http_exception_handler(request: Request, exc: HTTPException):
243
+ return JSONResponse(
244
+ status_code=exc.status_code,
245
+ content={"detail": exc.detail},
246
+ )
247
+
248
 
249
  @app.get("/")
250
  async def greet_json():
251
+ return {"Hello": "World!"}