Severian commited on
Commit
3dc447d
·
verified ·
1 Parent(s): dd04926

Create server.py

Browse files
Files changed (1) hide show
  1. server.py +250 -0
server.py ADDED
@@ -0,0 +1,250 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from flask import Flask, request, jsonify, Response, stream_with_context
3
+ import requests
4
+ import logging
5
+ from dotenv import load_dotenv
6
+ import json
7
+
8
+ # Load environment variables
9
+ load_dotenv()
10
+
11
+ app = Flask(__name__)
12
+
13
+ ANTHROPIC_API_URL = os.getenv('ANTHROPIC_API_URL', 'https://relay.stagwellmarketingcloud.io/anthropic/v1/messages')
14
+ API_KEY = os.getenv('ANTHROPIC_API_KEY')
15
+
16
+ # Configure logging
17
+ logging.basicConfig(level=logging.INFO)
18
+ logger = logging.getLogger(__name__)
19
+
20
+ def handle_completion_request(request_data):
21
+ headers = {
22
+ 'Authorization': f'Bearer {API_KEY}',
23
+ 'Content-Type': 'application/json'
24
+ }
25
+
26
+ try:
27
+ # Transform to Anthropic format
28
+ anthropic_request = {
29
+ "model": "claude-3-sonnet-20240229",
30
+ "max_tokens": request_data.get('max_tokens', 1024),
31
+ "messages": [{"role": "user", "content": request_data.get('prompt', '')}]
32
+ }
33
+
34
+ # Make request to Anthropic
35
+ response = requests.post(
36
+ ANTHROPIC_API_URL,
37
+ headers=headers,
38
+ json=anthropic_request
39
+ )
40
+ response.raise_for_status()
41
+
42
+ # Get Anthropic response
43
+ anthropic_response = response.json()
44
+
45
+ # Transform to OpenAI format
46
+ openai_response = {
47
+ "id": "cmpl-" + anthropic_response.get('id', 'default'),
48
+ "object": "text_completion",
49
+ "created": anthropic_response.get('created', 0),
50
+ "choices": [{
51
+ "text": anthropic_response['content'][0]['text'],
52
+ "index": 0,
53
+ "finish_reason": "stop"
54
+ }],
55
+ "usage": {
56
+ "prompt_tokens": -1,
57
+ "completion_tokens": -1,
58
+ "total_tokens": -1
59
+ }
60
+ }
61
+
62
+ return jsonify(openai_response), 200
63
+
64
+ except requests.RequestException as e:
65
+ logger.error(f"Error communicating with Anthropic API: {str(e)}")
66
+ if e.response:
67
+ logger.error(f"Response content: {e.response.text}")
68
+ return jsonify({"error": "Error communicating with AI service"}), 500
69
+ except Exception as e:
70
+ logger.error(f"Unexpected error: {str(e)}")
71
+ return jsonify({"error": "An unexpected error occurred"}), 500
72
+
73
+ def stream_completion(request_data, is_chat=False):
74
+ headers = {
75
+ 'Authorization': f'Bearer {API_KEY}',
76
+ 'Content-Type': 'application/json',
77
+ 'Accept': 'text/event-stream'
78
+ }
79
+
80
+ try:
81
+ # Transform to Anthropic format
82
+ anthropic_request = {
83
+ "model": "claude-3-sonnet-20240229",
84
+ "max_tokens": request_data.get('max_tokens', 1024),
85
+ "stream": True
86
+ }
87
+
88
+ if is_chat:
89
+ anthropic_request["messages"] = request_data.get('messages', [])
90
+ else:
91
+ anthropic_request["messages"] = [{"role": "user", "content": request_data.get('prompt', '')}]
92
+
93
+ logger.info(f"Sending streaming request to Anthropic: {json.dumps(anthropic_request)}")
94
+
95
+ # Make streaming request to Anthropic
96
+ response = requests.post(
97
+ ANTHROPIC_API_URL,
98
+ headers=headers,
99
+ json=anthropic_request,
100
+ stream=True
101
+ )
102
+ response.raise_for_status()
103
+
104
+ def generate():
105
+ for line in response.iter_lines():
106
+ if line:
107
+ line_text = line.decode('utf-8')
108
+ logger.info(f"Received line: {line_text}")
109
+
110
+ # Skip empty lines
111
+ if not line_text.strip():
112
+ continue
113
+
114
+ # Handle SSE prefix
115
+ if line_text.startswith('data: '):
116
+ line_text = line_text[6:] # Remove 'data: ' prefix
117
+
118
+ try:
119
+ # Skip [DONE] message
120
+ if line_text.strip() == '[DONE]':
121
+ yield "data: [DONE]\n\n"
122
+ continue
123
+
124
+ data = json.loads(line_text)
125
+ logger.info(f"Parsed data: {json.dumps(data)}")
126
+
127
+ if is_chat:
128
+ chunk = {
129
+ "id": "chatcmpl-" + data.get('id', 'default'),
130
+ "object": "chat.completion.chunk",
131
+ "created": data.get('created', 0),
132
+ "model": "claude-3-sonnet-20240229",
133
+ "choices": [{
134
+ "index": 0,
135
+ "delta": {
136
+ "role": "assistant",
137
+ "content": data.get('content', [{}])[0].get('text', '')
138
+ },
139
+ "finish_reason": data.get('stop_reason')
140
+ }]
141
+ }
142
+ else:
143
+ chunk = {
144
+ "id": "cmpl-" + data.get('id', 'default'),
145
+ "object": "text_completion",
146
+ "created": data.get('created', 0),
147
+ "choices": [{
148
+ "text": data.get('content', [{}])[0].get('text', ''),
149
+ "index": 0,
150
+ "finish_reason": data.get('stop_reason')
151
+ }]
152
+ }
153
+
154
+ yield f"data: {json.dumps(chunk)}\n\n"
155
+
156
+ if data.get('stop_reason'):
157
+ yield "data: [DONE]\n\n"
158
+
159
+ except json.JSONDecodeError as e:
160
+ logger.error(f"Error decoding JSON: {e}")
161
+ logger.error(f"Problematic line: {line_text}")
162
+ continue
163
+ except Exception as e:
164
+ logger.error(f"Error processing stream: {str(e)}")
165
+ continue
166
+
167
+ return Response(
168
+ stream_with_context(generate()),
169
+ mimetype='text/event-stream',
170
+ headers={
171
+ 'Cache-Control': 'no-cache',
172
+ 'Transfer-Encoding': 'chunked'
173
+ }
174
+ )
175
+
176
+ except requests.RequestException as e:
177
+ logger.error(f"Error communicating with Anthropic API: {str(e)}")
178
+ if e.response:
179
+ logger.error(f"Response content: {e.response.text}")
180
+ return jsonify({"error": "Error communicating with AI service"}), 500
181
+ except Exception as e:
182
+ logger.error(f"Unexpected error: {str(e)}")
183
+ return jsonify({"error": "An unexpected error occurred"}), 500
184
+
185
+ @app.route('/v1/completions', methods=['POST'])
186
+ def completions():
187
+ request_data = request.json
188
+ if request_data.get('stream', False):
189
+ return stream_completion(request_data, is_chat=False)
190
+ return handle_completion_request(request_data)
191
+
192
+ @app.route('/v1/chat/completions', methods=['POST'])
193
+ def chat_completions():
194
+ request_data = request.json
195
+ if request_data.get('stream', False):
196
+ return stream_completion(request_data, is_chat=True)
197
+
198
+ headers = {
199
+ 'Authorization': f'Bearer {API_KEY}',
200
+ 'Content-Type': 'application/json'
201
+ }
202
+
203
+ try:
204
+ anthropic_request = {
205
+ "model": "claude-3-sonnet-20240229",
206
+ "max_tokens": request_data.get('max_tokens', 1024),
207
+ "messages": request_data.get('messages', [])
208
+ }
209
+
210
+ response = requests.post(
211
+ ANTHROPIC_API_URL,
212
+ headers=headers,
213
+ json=anthropic_request
214
+ )
215
+ response.raise_for_status()
216
+
217
+ anthropic_response = response.json()
218
+
219
+ openai_response = {
220
+ "id": "chatcmpl-" + anthropic_response.get('id', 'default'),
221
+ "object": "chat.completion",
222
+ "created": anthropic_response.get('created', 0),
223
+ "choices": [{
224
+ "index": 0,
225
+ "message": {
226
+ "role": "assistant",
227
+ "content": anthropic_response['content'][0]['text']
228
+ },
229
+ "finish_reason": "stop"
230
+ }],
231
+ "usage": {
232
+ "prompt_tokens": -1,
233
+ "completion_tokens": -1,
234
+ "total_tokens": -1
235
+ }
236
+ }
237
+
238
+ return jsonify(openai_response), 200
239
+
240
+ except requests.RequestException as e:
241
+ logger.error(f"Error communicating with Anthropic API: {str(e)}")
242
+ if e.response:
243
+ logger.error(f"Response content: {e.response.text}")
244
+ return jsonify({"error": "Error communicating with AI service"}), 500
245
+ except Exception as e:
246
+ logger.error(f"Unexpected error: {str(e)}")
247
+ return jsonify({"error": "An unexpected error occurred"}), 500
248
+
249
+ if __name__ == '__main__':
250
+ app.run(host='0.0.0.0', port=4224)