Spaces:
Running
Running
from flask import Flask, render_template, request, jsonify, Response | |
from duckduckgo_search import DDGS | |
import json | |
import time | |
import sys | |
import argparse | |
import logging | |
# Configure logging | |
logging.basicConfig(level=logging.INFO, | |
format='%(asctime)s - %(levelname)s - %(message)s') | |
logger = logging.getLogger(__name__) | |
app = Flask(__name__) | |
class DDGChatAPI: | |
def respond_json(response, key="message"): | |
"""Create an OpenAI-compatible response format.""" | |
return { | |
"id": f"chatcmpl-{int(time.time())}", | |
"object": "chat.completion", | |
"created": int(time.time()), | |
"choices": [{ | |
"index": 0, | |
key: response, | |
"finish_reason": "stop" | |
}], | |
"usage": { | |
"prompt_tokens": 0, | |
"completion_tokens": 0, | |
"total_tokens": 0 | |
} | |
} | |
def validate_messages(messages): | |
"""Validate message format.""" | |
if not isinstance(messages, list): | |
return { | |
"error": { | |
"message": "'messages' must be a list", | |
"code": "invalid_message_list" | |
} | |
}, 400 | |
for message in messages: | |
if not isinstance(message, dict) or 'role' not in message or 'content' not in message: | |
return { | |
"error": { | |
"message": "Each message must have a 'role' and a 'content'", | |
"code": "invalid_message" | |
} | |
}, 400 | |
return None, None | |
def wait_for_full_response(last_message, model='gpt-4o-mini', timeout=120): | |
""" | |
Wait for a complete response from DuckDuckGo chat | |
Args: | |
last_message (str): User's message | |
model (str): Selected AI model | |
timeout (int): Maximum wait time in seconds | |
Returns: | |
str: Full AI response | |
""" | |
start_time = time.time() | |
ddgs = DDGS() | |
attempts = 0 | |
max_attempts = 5 | |
while attempts < max_attempts: | |
try: | |
# Attempt to get response | |
response = ddgs.chat(last_message, model=model) | |
# Clean and validate response | |
cleaned_response = response.strip() | |
# Check response quality | |
if len(cleaned_response) >= 50: | |
logger.info(f"Successfully generated response in {attempts + 1} attempt(s)") | |
return cleaned_response | |
# If response is too short, wait and retry | |
attempts += 1 | |
time.sleep(2) # Wait between attempts | |
# Break if total timeout is exceeded | |
if time.time() - start_time > timeout: | |
break | |
except Exception as e: | |
logger.error(f"Attempt {attempts + 1} failed: {str(e)}") | |
attempts += 1 | |
time.sleep(2) | |
# Fallback message if no good response generated | |
return "I apologize, but I'm unable to generate a complete response at the moment. Please try again later." | |
def homepage(): | |
return render_template("index.html") | |
def chat_completions(): | |
# Get request data | |
data = request.json | |
messages = data.get("messages", []) | |
model = data.get("model", "gpt-4o-mini") | |
stream = data.get("stream", False) | |
timeout = data.get("timeout", 120) | |
# Validate messages | |
errors, status = DDGChatAPI.validate_messages(messages) | |
if errors: | |
return jsonify(errors), status | |
# Extract the last message content | |
last_message = messages[-1]["content"] | |
# Log incoming request | |
logger.info(f"Received chat request: model={model}, message={last_message[:100]}...") | |
try: | |
# Get full response | |
response = DDGChatAPI.wait_for_full_response(last_message, model, timeout) | |
# Log response generation | |
logger.info(f"Response generated (length: {len(response)} chars)") | |
# Handle streaming response | |
if stream: | |
def generate(): | |
# Split response into chunks for streaming | |
max_chunk_size = 50 | |
chunks = [response[i:i+max_chunk_size] for i in range(0, len(response), max_chunk_size)] | |
for chunk in chunks: | |
delta_response = { | |
"role": "assistant", | |
"content": chunk | |
} | |
yield f"data: {json.dumps(DDGChatAPI.respond_json(delta_response, 'delta'))}\n\n" | |
yield "data: [DONE]\n\n" | |
return Response(generate(), mimetype='text/event-stream') | |
# Regular JSON response | |
response_data = { | |
"role": "assistant", | |
"content": response | |
} | |
return jsonify(DDGChatAPI.respond_json(response_data)) | |
except Exception as e: | |
logger.error(f"Error processing request: {str(e)}") | |
return jsonify({ | |
"error": { | |
"message": str(e), | |
"code": "ddg_chat_error" | |
} | |
}), 500 | |
def list_models(): | |
"""Provide a list of available models.""" | |
return jsonify({ | |
"data": [ | |
{"id": "gpt-4o-mini"}, | |
{"id": "claude-3-haiku"}, | |
{"id": "llama-3.1-70b"}, | |
{"id": "mixtral-8x7b"} | |
], | |
"object": "list" | |
}) | |
def main(): | |
app.run(host='0.0.0.0', port=7860) | |
if __name__ == "__main__": | |
main() |