File size: 5,934 Bytes
02a645a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
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:
    @staticmethod
    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
            }
        }

    @staticmethod
    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

    @staticmethod
    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."

@app.route("/")
def homepage():
    return render_template("index.html")

@app.route("/v1/chat/completions", methods=["POST"])
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

@app.route("/v1/models", methods=["GET"])
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()