deepak191z commited on
Commit
7a4b76e
·
verified ·
1 Parent(s): 22c9f4d

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +18 -211
main.py CHANGED
@@ -1,60 +1,27 @@
 
1
  from fastapi import FastAPI, Request, HTTPException
2
- from fastapi.responses import JSONResponse, StreamingResponse
3
- from fastapi.middleware.cors import CORSMiddleware
4
  from pydantic import BaseModel
5
- from typing import List, Dict, Any, Union
6
- import os
7
- import time
8
- import httpx
9
- import json
10
- import asyncio
11
- from dotenv import load_dotenv
12
-
13
- load_dotenv()
14
-
15
- # Simple configuration
16
- API_PREFIX = os.getenv("API_PREFIX", "/")
17
- MAX_RETRY_COUNT = int(os.getenv("MAX_RETRY_COUNT", "3"))
18
- RETRY_DELAY = int(os.getenv("RETRY_DELAY", "5000"))
19
-
20
- # Default headers for DuckDuckGo requests
21
- FAKE_HEADERS = {
22
- "Accept": "*/*",
23
- "Accept-Language": "en-US,en;q=0.9",
24
- "Origin": "https://duckduckgo.com/",
25
- "Referer": "https://duckduckgo.com/",
26
- "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36",
27
- }
28
 
29
  app = FastAPI()
30
 
31
- # Add CORS middleware
32
- app.add_middleware(
33
- CORSMiddleware,
34
- allow_origins=["*"],
35
- allow_methods=["*"],
36
- allow_headers=["*"],
37
- )
38
-
39
- # Models for request validation
40
- class Message(BaseModel):
41
- role: str
42
- content: Union[str, List[Dict[str, Any]]]
43
 
44
- class ChatCompletionRequest(BaseModel):
45
- model: str
46
- messages: List[Message]
47
- stream: bool = False
48
-
49
- # Add timing information
50
  @app.middleware("http")
51
- async def add_process_time(request: Request, call_next):
52
  start_time = time.time()
53
  response = await call_next(request)
54
  process_time = time.time() - start_time
55
  print(f"{request.method} {response.status_code} {request.url.path} {process_time*1000:.2f} ms")
56
  return response
57
 
 
 
 
 
 
58
  @app.get("/")
59
  async def root():
60
  return {"message": "API server running"}
@@ -79,175 +46,16 @@ async def get_models():
79
  @app.post(f"{API_PREFIX}v1/chat/completions")
80
  async def chat_completions(request: ChatCompletionRequest):
81
  try:
82
- model = convert_model(request.model)
83
- content = messages_to_text(request.messages)
84
- return await create_completion(model, content, request.stream)
85
- except Exception as e:
86
- raise HTTPException(status_code=500, detail=str(e))
87
-
88
- def convert_model(input_model: str) -> str:
89
- """Convert public model names to DuckDuckGo internal model names"""
90
- model_mapping = {
91
- "claude-3-haiku": "claude-3-haiku-20240307",
92
- "llama-3.1-70b": "meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo",
93
- "mixtral-8x7b": "mistralai/Mixtral-8x7B-Instruct-v0.1",
94
- "o3-mini": "o3-mini"
95
- }
96
- return model_mapping.get(input_model.lower(), "gpt-4o-mini")
97
-
98
- def messages_to_text(messages: List[Message]) -> str:
99
- """Convert message array to text format expected by DuckDuckGo API"""
100
- result = ""
101
- for message in messages:
102
- role = "user" if message.role == "system" else message.role
103
-
104
- if role in ["user", "assistant"]:
105
- # Handle both string content and structured content
106
- if isinstance(message.content, list):
107
- content_str = "".join([item.get("text", "") for item in message.content if item.get("text", "")])
108
- else:
109
- content_str = message.content
110
-
111
- result += f"{role}:{content_str};\r\n"
112
-
113
- return result
114
-
115
- async def request_token() -> str:
116
- """Get auth token from DuckDuckGo"""
117
- try:
118
- async with httpx.AsyncClient() as client:
119
- response = await client.get(
120
- "https://duckduckgo.com/duckchat/v1/status",
121
- headers={**FAKE_HEADERS, "x-vqd-accept": "1"}
122
- )
123
- return response.headers.get("x-vqd-4", "")
124
- except Exception as e:
125
- print(f"Token request error: {e}")
126
- return ""
127
-
128
- async def create_completion(model: str, content: str, return_stream: bool, retry_count: int = 0):
129
- """Create a chat completion via DuckDuckGo API"""
130
- token = await request_token()
131
-
132
- try:
133
- # Fixed: Don't pass stream=True to AsyncClient.post() - it's not supported
134
- async with httpx.AsyncClient() as client:
135
- response = await client.post(
136
- "https://duckduckgo.com/duckchat/v1/chat",
137
- headers={
138
- **FAKE_HEADERS,
139
- "Accept": "text/event-stream",
140
- "Content-Type": "application/json",
141
- "x-vqd-4": token,
142
- },
143
- json={
144
- "model": model,
145
- "messages": [{"role": "user", "content": content}]
146
- }
147
- )
148
 
149
- if response.status_code != 200:
150
- raise HTTPException(status_code=response.status_code, detail="API request failed")
151
-
152
- return await process_stream(model, response, return_stream)
153
  except Exception as e:
154
- if retry_count < MAX_RETRY_COUNT:
155
- print(f"Retrying... attempt {retry_count + 1}")
156
- await asyncio.sleep(RETRY_DELAY / 1000)
157
- return await create_completion(model, content, return_stream, retry_count + 1)
158
  raise HTTPException(status_code=500, detail=str(e))
159
 
160
- async def process_stream(model: str, response, return_stream: bool):
161
- """Process streaming response from DuckDuckGo"""
162
- buffer = ""
163
- full_text = ""
164
-
165
- async def generate_stream():
166
- nonlocal buffer, full_text
167
-
168
- # Process chunks as they arrive
169
- # Use response.aiter_text() instead of aiter_bytes() for easier text handling
170
- async for chunk_str in response.aiter_text():
171
- chunk_str = chunk_str.strip()
172
-
173
- # Handle buffer from previous chunk if needed
174
- if buffer:
175
- chunk_str = buffer + chunk_str
176
- buffer = ""
177
-
178
- # Handle incomplete chunks
179
- if not chunk_str.endswith('"}') and "[DONE]" not in chunk_str:
180
- buffer = chunk_str
181
- continue
182
-
183
- # Process each line in the chunk
184
- for line in chunk_str.split('\n'):
185
- if len(line) < 6:
186
- continue
187
-
188
- # Remove prefix (data: )
189
- line = line[6:] if line.startswith("data: ") else line
190
-
191
- # Handle completion signal
192
- if line == "[DONE]":
193
- if return_stream:
194
- yield f"data: {json.dumps(create_stop_chunk(model))}\n\n"
195
- return
196
-
197
- # Parse and handle message content
198
- try:
199
- data = json.loads(line)
200
- if data.get("action") == "success" and "message" in data:
201
- message = data["message"]
202
- full_text += message
203
-
204
- if return_stream:
205
- yield f"data: {json.dumps(create_chunk(message, model))}\n\n"
206
- except json.JSONDecodeError:
207
- continue
208
-
209
- # Return appropriate response based on streaming preference
210
- if return_stream:
211
- return StreamingResponse(generate_stream(), media_type="text/event-stream")
212
- else:
213
- # For non-streaming, collect all the text
214
- async for chunk in generate_stream():
215
- pass # Just collecting text in full_text
216
-
217
- return JSONResponse(content=create_complete_response(full_text, model))
218
-
219
- def create_chunk(text: str, model: str) -> dict:
220
- """Create a streaming chunk response"""
221
- return {
222
- "id": "chatcmpl-123",
223
- "object": "chat.completion.chunk",
224
- "created": int(time.time()),
225
- "model": model,
226
- "choices": [
227
- {
228
- "index": 0,
229
- "delta": {"content": text},
230
- "finish_reason": None,
231
- },
232
- ],
233
- }
234
-
235
- def create_stop_chunk(model: str) -> dict:
236
- """Create a final streaming chunk with stop reason"""
237
- return {
238
- "id": "chatcmpl-123",
239
- "object": "chat.completion.chunk",
240
- "created": int(time.time()),
241
- "model": model,
242
- "choices": [
243
- {
244
- "index": 0,
245
- "delta": {},
246
- "finish_reason": "stop",
247
- },
248
- ],
249
- }
250
-
251
  def create_complete_response(text: str, model: str) -> dict:
252
  """Create a complete non-streaming response"""
253
  return {
@@ -266,5 +74,4 @@ def create_complete_response(text: str, model: str) -> dict:
266
  }
267
 
268
  if __name__ == "__main__":
269
- import uvicorn
270
- uvicorn.run("app:app", host="0.0.0.0", port=7860, reload=True)
 
1
+ import time
2
  from fastapi import FastAPI, Request, HTTPException
 
 
3
  from pydantic import BaseModel
4
+ from duckai import DuckAI
5
+ import uvicorn
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
 
7
  app = FastAPI()
8
 
9
+ API_PREFIX = "/"
 
 
 
 
 
 
 
 
 
 
 
10
 
11
+ # Middleware for logging request time
 
 
 
 
 
12
  @app.middleware("http")
13
+ async def log_process_time(request: Request, call_next):
14
  start_time = time.time()
15
  response = await call_next(request)
16
  process_time = time.time() - start_time
17
  print(f"{request.method} {response.status_code} {request.url.path} {process_time*1000:.2f} ms")
18
  return response
19
 
20
+ # Request body model
21
+ class ChatCompletionRequest(BaseModel):
22
+ model: str
23
+ messages: list[dict]
24
+
25
  @app.get("/")
26
  async def root():
27
  return {"message": "API server running"}
 
46
  @app.post(f"{API_PREFIX}v1/chat/completions")
47
  async def chat_completions(request: ChatCompletionRequest):
48
  try:
49
+ # Only using DuckAI directly
50
+ content = " ".join([msg.get("content", "") for msg in request.messages])
51
+ duck = DuckAI()
52
+ results = duck.chat(content, model=request.model)
53
+ response = create_complete_response(results, request.model)
54
+ return response
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55
 
 
 
 
 
56
  except Exception as e:
 
 
 
 
57
  raise HTTPException(status_code=500, detail=str(e))
58
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
  def create_complete_response(text: str, model: str) -> dict:
60
  """Create a complete non-streaming response"""
61
  return {
 
74
  }
75
 
76
  if __name__ == "__main__":
77
+ uvicorn.run("app:app", host="0.0.0.0", port=7860, reload=True)