yuoop commited on
Commit
757e09b
·
verified ·
1 Parent(s): f4e4c63

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +166 -0
app.py ADDED
@@ -0,0 +1,166 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, request, jsonify
2
+ import requests
3
+ import json
4
+ import os
5
+
6
+ app = Flask(__name__)
7
+
8
+ # 环境变量配置
9
+ STATUS_URL = os.environ.get("STATUS_URL", "https://duckduckgo.com/duckchat/v1/status")
10
+ CHAT_URL = os.environ.get("CHAT_URL", "https://duckduckgo.com/duckchat/v1/chat")
11
+ REFERER = os.environ.get("REFERER", "https://duckduckgo.com/")
12
+ ORIGIN = os.environ.get("ORIGIN", "https://duckduckgo.com")
13
+ USER_AGENT = os.environ.get("USER_AGENT", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36")
14
+ COOKIE = os.environ.get("COOKIE", "dcm=3; s=l; bf=1") # 从环境变量获取 Cookie
15
+
16
+ DEFAULT_HEADERS = {
17
+ "User-Agent": USER_AGENT,
18
+ "Accept": "text/event-stream",
19
+ "Accept-Language": "en-US,en;q=0.5",
20
+ "Accept-Encoding": "gzip, deflate, br",
21
+ "Referer": REFERER,
22
+ "Content-Type": "application/json",
23
+ "Origin": ORIGIN,
24
+ "Connection": "keep-alive",
25
+ "Cookie": COOKIE,
26
+ "Sec-Fetch-Dest": "empty",
27
+ "Sec-Fetch-Mode": "cors",
28
+ "Sec-Fetch-Site": "same-origin",
29
+ "Pragma": "no-cache",
30
+ "TE": "trailers",
31
+ }
32
+
33
+ SUPPORTED_MODELS = ["o3-mini", "gpt-4o-mini", "claude-3-haiku-20240307", "meta-llama/Llama-3.3-70B-Instruct-Turbo"]
34
+
35
+ def get_vqd():
36
+ """获取 DuckDuckGo Chat 的 VQD 值。"""
37
+ headers = {**DEFAULT_HEADERS, "x-vqd-accept": "1"}
38
+ try:
39
+ response = requests.get(STATUS_URL, headers=headers)
40
+ response.raise_for_status() # 抛出 HTTPError,如果状态码不是 200
41
+ vqd = response.headers.get("x-vqd-4")
42
+ if not vqd:
43
+ raise ValueError("x-vqd-4 header 在响应中未找到。")
44
+ return vqd
45
+ except requests.exceptions.RequestException as e:
46
+ raise Exception(f"HTTP 请求失败: {e}")
47
+ except ValueError as e:
48
+ raise Exception(str(e))
49
+
50
+ def duckduckgo_chat(model, messages):
51
+ """与 DuckDuckGo Chat 进行交互。"""
52
+ try:
53
+ x_vqd_4 = get_vqd()
54
+
55
+ chat_headers = {
56
+ **DEFAULT_HEADERS,
57
+ "x-vqd-4": x_vqd_4,
58
+ }
59
+
60
+ body = json.dumps({
61
+ "model": model,
62
+ "messages": messages,
63
+ })
64
+
65
+ response = requests.post(CHAT_URL, headers=chat_headers, data=body, stream=True)
66
+ response.raise_for_status()
67
+
68
+ full_message = ""
69
+ for line in response.iter_lines():
70
+ if line:
71
+ decoded_line = line.decode('utf-8')
72
+ if decoded_line.startswith("data: "):
73
+ try:
74
+ json_data = json.loads(decoded_line[5:])
75
+ full_message += json_data.get("message", "")
76
+ except json.JSONDecodeError as e:
77
+ print(f"JSON 解析错误: {e}, 行: {decoded_line}")
78
+ pass # 忽略解析错误
79
+
80
+ return full_message
81
+
82
+ except requests.exceptions.RequestException as e:
83
+ raise Exception(f"HTTP 请求失败: {e}")
84
+ except Exception as e:
85
+ raise Exception(f"聊天过程中发生错误: {e}")
86
+
87
+ @app.route('/v1/chat/completions', methods=['POST'])
88
+ def chat_completions():
89
+ try:
90
+ body = request.get_json()
91
+ if not body:
92
+ return jsonify({"error": "无效的请求体"}), 400
93
+
94
+ model = body.get("model", "o3-mini")
95
+ if model not in SUPPORTED_MODELS:
96
+ return jsonify({"error": f"模型 \"{model}\" 不支持。支持的模型有: {', '.join(SUPPORTED_MODELS)}."}), 400
97
+
98
+ messages = body.get("messages")
99
+ if not messages:
100
+ return jsonify({"error": "未提供任何消息内容"}), 400
101
+
102
+ # 处理 system 消息
103
+ system_message = next((msg for msg in messages if msg.get("role") == "system"), None)
104
+ system_prompt = f"你将扮演一个{system_message['content']}.\n" if system_message else ""
105
+
106
+ # 提取历史消息并格式化
107
+ history_messages = "\n".join(
108
+ f"{msg['role']}: {msg['content']}"
109
+ for msg in messages
110
+ if msg.get("role") != "system" and msg != messages[-1]
111
+ )
112
+
113
+ # 提取最后一条用户消息
114
+ last_user_message = messages[-1]
115
+ current_question = last_user_message["content"] if last_user_message.get("role") == "user" else ""
116
+
117
+ # 构建合并后的消息
118
+ combined_message_content = f"{system_prompt}以下是历史对话记录:\n{history_messages}\n用户当前提问:{current_question}"
119
+ combined_message = {"role": "user", "content": combined_message_content}
120
+
121
+ # 发送单条消息
122
+ response_text = duckduckgo_chat(model, [combined_message])
123
+
124
+ # 构建 OpenAI 风格的响应
125
+ openai_response = {
126
+ "id": f"chatcmpl-{int(time.time() * 1000)}", # 生成唯一 ID
127
+ "object": "chat.completion",
128
+ "created": int(time.time()),
129
+ "model": model,
130
+ "choices": [
131
+ {
132
+ "message": {
133
+ "role": "assistant",
134
+ "content": response_text,
135
+ },
136
+ "finish_reason": "stop",
137
+ "index": 0,
138
+ },
139
+ ],
140
+ "usage": {
141
+ "prompt_tokens": 0,
142
+ "completion_tokens": 0,
143
+ "total_tokens": 0
144
+ },
145
+ }
146
+
147
+ return jsonify(openai_response)
148
+
149
+ except Exception as e:
150
+ print(f"API 错误: {e}")
151
+ return jsonify({"error": str(e)}), 500
152
+
153
+ @app.errorhandler(404)
154
+ def not_found(error):
155
+ return jsonify({"message": "未找到"}), 404
156
+
157
+ @app.errorhandler(Exception)
158
+ def handle_exception(e):
159
+ print(f"全局错误: {e}")
160
+ return jsonify({"error": "服务器内部错误"}), 500
161
+
162
+
163
+ import time
164
+
165
+ if __name__ == '__main__':
166
+ app.run(debug=False, host='0.0.0.0', port=int(os.environ.get('PORT', 8080)))