sehsapneb commited on
Commit
e572557
·
verified ·
1 Parent(s): 8412768

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +160 -32
app.py CHANGED
@@ -1,32 +1,160 @@
1
- ---
2
- title: My OpenAI Proxy (Stream/Non-Stream)
3
- emoji: 🚀
4
- colorFrom: blue
5
- colorTo: green
6
- sdk: gradio
7
- sdk_version: 3.45.0
8
- python_version: 3.9
9
- app_file: app.py
10
- app_port: 7860
11
- ---
12
-
13
- # GPT-OSS to OpenAI API Proxy
14
-
15
- 这是一个代理服务,它将标准的OpenAI API请求转换为 `gpt-oss.com` 所需的格式。
16
- **此版本支持流式和非流式响应。**
17
-
18
- ## 如何使用
19
-
20
- 将您的API客户端的 `base_url` 指向本Space的URL,然后使用`/v1/chat/completions`端点。
21
-
22
- ### 流式请求 (会返回思考过程)
23
-
24
- ```bash
25
- curl https://YOUR_SPACE_URL/v1/chat/completions \
26
- -H "Content-Type: application/json" \
27
- -H "Authorization: Bearer sk-any-key-works" \
28
- -d '{
29
- "model": "gpt-oss-120b",
30
- "messages": [{"role": "user", "content": "你好吗?"}],
31
- "stream": true
32
- }'
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import requests
2
+ import json
3
+ import uuid
4
+ import time
5
+ from flask import Flask, request, jsonify, Response
6
+
7
+ # 1. 初始化Flask应用
8
+ app = Flask(__name__)
9
+
10
+ # 2. gpt-oss.com API的固定配置
11
+ GPT_OSS_API_URL = "https://api.gpt-oss.com/chatkit"
12
+ GPT_OSS_HEADERS = {
13
+ 'authority': 'api.gpt-oss.com',
14
+ 'accept': 'text/event-stream',
15
+ 'content-type': 'application/json',
16
+ 'origin': 'https://gpt-oss.com',
17
+ 'referer': 'https://gpt-oss.com/',
18
+ 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
19
+ 'x-selected-model': 'gpt-oss-120b',
20
+ }
21
+
22
+ # 3. 新增:根路由,用于健康检查和显示欢迎信息
23
+ @app.route('/', methods=['GET'])
24
+ def root():
25
+ return jsonify({
26
+ "message": "欢迎使用 GPT-OSS to OpenAI 格式代理API",
27
+ "status": "ok",
28
+ "api_endpoint": "/v1/chat/completions",
29
+ "features": {
30
+ "stream": "支持",
31
+ "non_stream": "支持",
32
+ "conditional_reasoning": "仅在流式模式下显示思考过程"
33
+ }
34
+ })
35
+
36
+ # 4. 核心:创建OpenAI兼容的API端点
37
+ @app.route('/v1/chat/completions', methods=['POST'])
38
+ def chat_completions_proxy():
39
+ try:
40
+ openai_request_data = request.json
41
+ messages = openai_request_data.get("messages", [])
42
+ user_prompt = next((m['content'] for m in reversed(messages) if m.get('role') == 'user'), None)
43
+
44
+ if not user_prompt:
45
+ return jsonify({"error": "在请求中未找到用户消息。"}), 400
46
+
47
+ stream_requested = openai_request_data.get("stream", False)
48
+ except Exception as e:
49
+ return jsonify({"error": f"请求格式无效: {e}"}), 400
50
+
51
+ # (一) 条件性设置请求头
52
+ # 创建一个请求头的副本,以避免修改全局常量
53
+ request_headers = GPT_OSS_HEADERS.copy()
54
+ if stream_requested:
55
+ request_headers['x-show-reasoning'] = 'true'
56
+ else:
57
+ request_headers['x-show-reasoning'] = 'false'
58
+
59
+ random_user_id = str(uuid.uuid4())
60
+ gpt_oss_cookies = {'user_id': random_user_id}
61
+ gpt_oss_payload = {
62
+ "op": "threads.create",
63
+ "params": {"input": {"text": user_prompt, "content": [{"type": "input_text", "text": user_prompt}]}}
64
+ }
65
+
66
+ # (二) 内部生成器,负责获取和解析后端数据
67
+ def _internal_proxy_stream():
68
+ try:
69
+ with requests.post(
70
+ GPT_OSS_API_URL, headers=request_headers, cookies=gpt_oss_cookies,
71
+ json=gpt_oss_payload, stream=True, timeout=120
72
+ ) as response:
73
+ response.raise_for_status()
74
+ for line in response.iter_lines():
75
+ if line:
76
+ line_str = line.decode('utf-8')
77
+ if line_str.startswith('data: '):
78
+ json_data_str = line_str[6:]
79
+ try:
80
+ yield json.loads(json_data_str)
81
+ except json.JSONDecodeError:
82
+ continue
83
+ except requests.exceptions.RequestException as e:
84
+ # 在生成器内部抛出错误,以便外部可以捕获
85
+ raise IOError(f"与后端服务通信失败: {e}")
86
+
87
+ # (三) 根据客户端请求,决定如何格式化响应
88
+ if stream_requested:
89
+ # 如果客户端请求流式响应
90
+ def stream_formatter():
91
+ try:
92
+ for gpt_oss_data in _internal_proxy_stream():
93
+ event_type = gpt_oss_data.get('type')
94
+
95
+ # 格式化思考过程
96
+ if (event_type == 'thread.item_updated' and
97
+ gpt_oss_data.get('update', {}).get('type') == 'cot.entry_added'):
98
+ thought = gpt_oss_data['update']['entry']['content']
99
+ # 我们可以选择发送一个自定义的流块,或者忽略它
100
+ # 这里我们创建一个包含思考过程的自定义块
101
+ reasoning_chunk = { "reasoning": thought }
102
+ yield f"data: {json.dumps(reasoning_chunk)}\n\n"
103
+
104
+ # 格式化文本片段
105
+ if (event_type == 'thread.item_updated' and
106
+ gpt_oss_data.get('update', {}).get('type') == 'assistant_message.content_part.text_delta'):
107
+ delta_content = gpt_oss_data['update'].get('delta', '')
108
+ openai_chunk = {
109
+ "id": f"chatcmpl-{str(uuid.uuid4())}", "object": "chat.completion.chunk",
110
+ "created": int(time.time()), "model": "gpt-oss-120b",
111
+ "choices": [{"index": 0, "delta": {"content": delta_content}, "finish_reason": None}]
112
+ }
113
+ yield f"data: {json.dumps(openai_chunk)}\n\n"
114
+
115
+ yield "data: [DONE]\n\n"
116
+ except IOError as e:
117
+ error_chunk = {"error": str(e)}
118
+ yield f"data: {json.dumps(error_chunk)}\n\n"
119
+
120
+ return Response(stream_formatter(), mimetype='text/event-stream')
121
+
122
+ else:
123
+ # 如果客户端请求非流式响应
124
+ try:
125
+ full_response_content = ""
126
+ for gpt_oss_data in _internal_proxy_stream():
127
+ event_type = gpt_oss_data.get('type')
128
+ # 只关心最终的文本片段
129
+ if (event_type == 'thread.item_updated' and
130
+ gpt_oss_data.get('update', {}).get('type') == 'assistant_message.content_part.text_delta'):
131
+ full_response_content += gpt_oss_data['update'].get('delta', '')
132
+
133
+ # 构建一个标准的OpenAI非流式JSON响应
134
+ final_response = {
135
+ "id": f"chatcmpl-{str(uuid.uuid4())}",
136
+ "object": "chat.completion",
137
+ "created": int(time.time()),
138
+ "model": "gpt-oss-120b",
139
+ "choices": [
140
+ {
141
+ "index": 0,
142
+ "message": {
143
+ "role": "assistant",
144
+ "content": full_response_content.strip()
145
+ },
146
+ "finish_reason": "stop"
147
+ }
148
+ ],
149
+ "usage": { # 提供一个模拟的usage对象
150
+ "prompt_tokens": None, "completion_tokens": None, "total_tokens": None
151
+ }
152
+ }
153
+ return jsonify(final_response)
154
+
155
+ except IOError as e:
156
+ return jsonify({"error": str(e)}), 500
157
+
158
+ # 5. 启动应用
159
+ if __name__ == '__main__':
160
+ app.run(host='0.0.0.0', port=7860)