openfree commited on
Commit
e3e9b56
Β·
verified Β·
1 Parent(s): 86c2e4c

Upload 3 files

Browse files
Files changed (3) hide show
  1. app (35).py +608 -0
  2. recursive_thinking_ai.py +291 -0
  3. requirements (16).txt +13 -0
app (35).py ADDED
@@ -0,0 +1,608 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, WebSocket, HTTPException, WebSocketDisconnect
2
+ from fastapi.responses import JSONResponse, HTMLResponse
3
+ from fastapi.staticfiles import StaticFiles
4
+ from fastapi.middleware.cors import CORSMiddleware
5
+ from pydantic import BaseModel, Field
6
+ import uvicorn
7
+ import json
8
+ import os
9
+ import asyncio
10
+ from datetime import datetime
11
+ from typing import List, Dict, Optional, Any
12
+ import logging
13
+ import uuid
14
+
15
+ # λͺ¨λ“ˆμ΄ μ‘΄μž¬ν•˜μ§€ μ•ŠμœΌλ©΄ ν˜„μž¬ λ””λ ‰ν† λ¦¬μ—μ„œ κ°€μ Έμ˜€λ„λ‘ μ‹œλ„
16
+ try:
17
+ from recursive_thinking_ai import EnhancedRecursiveThinkingChat
18
+ except ModuleNotFoundError:
19
+ # ν˜„μž¬ 디렉토리에 recursive_thinking_ai.py 파일이 μžˆμ–΄μ•Ό 함
20
+ import sys
21
+ sys.path.append('.')
22
+ from recursive_thinking_ai import EnhancedRecursiveThinkingChat
23
+
24
+ # Set up logging
25
+ logging.basicConfig(
26
+ level=logging.INFO,
27
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
28
+ )
29
+ logger = logging.getLogger(__name__)
30
+
31
+ app = FastAPI(
32
+ title="RecThink API",
33
+ description="API for Enhanced Recursive Thinking Chat",
34
+ version="1.0.0"
35
+ )
36
+
37
+ # Add CORS middleware
38
+ app.add_middleware(
39
+ CORSMiddleware,
40
+ allow_origins=["*"], # In production, replace with specific origins
41
+ allow_credentials=True,
42
+ allow_methods=["*"],
43
+ allow_headers=["*"],
44
+ )
45
+
46
+ # Create a dictionary to store chat instances
47
+ chat_instances = {}
48
+
49
+ # Pydantic models for request/response validation
50
+ class ChatConfig(BaseModel):
51
+ api_key: str
52
+ model: str = "mistralai/mistral-small-3.1-24b-instruct:free"
53
+ temperature: Optional[float] = Field(default=0.7, ge=0.0, le=1.0)
54
+
55
+ class MessageRequest(BaseModel):
56
+ session_id: str
57
+ message: str
58
+ thinking_rounds: Optional[int] = Field(default=None, ge=1, le=10)
59
+ alternatives_per_round: Optional[int] = Field(default=3, ge=1, le=5)
60
+ temperature: Optional[float] = Field(default=None, ge=0.0, le=1.0)
61
+
62
+ class SaveRequest(BaseModel):
63
+ session_id: str
64
+ filename: Optional[str] = None
65
+ full_log: bool = False
66
+
67
+ class SessionInfo(BaseModel):
68
+ session_id: str
69
+ message_count: int
70
+ created_at: str
71
+ model: str
72
+
73
+ class SessionResponse(BaseModel):
74
+ sessions: List[SessionInfo]
75
+
76
+ class InitResponse(BaseModel):
77
+ session_id: str
78
+ status: str
79
+
80
+ # κ°„λ‹¨ν•œ HTML μΈν„°νŽ˜μ΄μŠ€ 제곡
81
+ @app.get("/", response_class=HTMLResponse)
82
+ async def root():
83
+ """Root endpoint with a simple HTML interface"""
84
+ html_content = """
85
+ <!DOCTYPE html>
86
+ <html>
87
+ <head>
88
+ <title>RecThink API</title>
89
+ <style>
90
+ body {
91
+ font-family: Arial, sans-serif;
92
+ max-width: 800px;
93
+ margin: 0 auto;
94
+ padding: 20px;
95
+ line-height: 1.6;
96
+ }
97
+ h1 {
98
+ color: #333;
99
+ border-bottom: 1px solid #eee;
100
+ padding-bottom: 10px;
101
+ }
102
+ .container {
103
+ background-color: #f9f9f9;
104
+ border-radius: 5px;
105
+ padding: 20px;
106
+ margin-top: 20px;
107
+ }
108
+ label {
109
+ display: block;
110
+ margin-bottom: 5px;
111
+ font-weight: bold;
112
+ }
113
+ input, textarea, select {
114
+ width: 100%;
115
+ padding: 8px;
116
+ margin-bottom: 10px;
117
+ border: 1px solid #ddd;
118
+ border-radius: 4px;
119
+ }
120
+ button {
121
+ background-color: #4CAF50;
122
+ color: white;
123
+ padding: 10px 15px;
124
+ border: none;
125
+ border-radius: 4px;
126
+ cursor: pointer;
127
+ }
128
+ button:hover {
129
+ background-color: #45a049;
130
+ }
131
+ #response {
132
+ white-space: pre-wrap;
133
+ background-color: #f5f5f5;
134
+ padding: 15px;
135
+ border-radius: 4px;
136
+ margin-top: 20px;
137
+ min-height: 100px;
138
+ }
139
+ .log {
140
+ margin-top: 20px;
141
+ font-size: 0.9em;
142
+ color: #666;
143
+ }
144
+ </style>
145
+ </head>
146
+ <body>
147
+ <h1>RecThink API μΈν„°νŽ˜μ΄μŠ€</h1>
148
+ <div class="container">
149
+ <div id="init-form">
150
+ <h2>1. μ±„νŒ… μ΄ˆκΈ°ν™”</h2>
151
+ <label for="api-key">OpenRouter API ν‚€:</label>
152
+ <input type="text" id="api-key" placeholder="OpenRouter API ν‚€λ₯Ό μž…λ ₯ν•˜μ„Έμš”">
153
+
154
+ <label for="model">λͺ¨λΈ:</label>
155
+ <input type="text" id="model" value="mistralai/mistral-small-3.1-24b-instruct:free">
156
+
157
+ <label for="temperature">μ˜¨λ„ (Temperature):</label>
158
+ <input type="number" id="temperature" min="0" max="1" step="0.1" value="0.7">
159
+
160
+ <button onclick="initializeChat()">μ΄ˆκΈ°ν™”</button>
161
+ </div>
162
+
163
+ <div id="chat-form" style="display: none;">
164
+ <h2>2. λ©”μ‹œμ§€ 전솑</h2>
165
+ <p>μ„Έμ…˜ ID: <span id="session-id"></span></p>
166
+
167
+ <label for="message">λ©”μ‹œμ§€:</label>
168
+ <textarea id="message" rows="4" placeholder="λ©”μ‹œμ§€λ₯Ό μž…λ ₯ν•˜μ„Έμš”"></textarea>
169
+
170
+ <label for="thinking-rounds">사고 λΌμš΄λ“œ (선택사항):</label>
171
+ <input type="number" id="thinking-rounds" min="1" max="10" placeholder="μžλ™ κ²°μ •">
172
+
173
+ <label for="alternatives">λŒ€μ•ˆ 개수 (선택사항):</label>
174
+ <input type="number" id="alternatives" min="1" max="5" value="3">
175
+
176
+ <button onclick="sendMessage()">전솑</button>
177
+ <button onclick="resetChat()" style="background-color: #f44336;">μ΄ˆκΈ°ν™”</button>
178
+ </div>
179
+
180
+ <div id="response-container" style="display: none;">
181
+ <h2>3. 응닡</h2>
182
+ <div id="response">응닡이 여기에 ν‘œμ‹œλ©λ‹ˆλ‹€...</div>
183
+ <div class="log">
184
+ <h3>생각 κ³Όμ • 둜그:</h3>
185
+ <div id="thinking-log"></div>
186
+ </div>
187
+ </div>
188
+ </div>
189
+
190
+ <script>
191
+ let currentSessionId = null;
192
+
193
+ async function initializeChat() {
194
+ const apiKey = document.getElementById('api-key').value;
195
+ const model = document.getElementById('model').value;
196
+ const temperature = parseFloat(document.getElementById('temperature').value);
197
+
198
+ if (!apiKey) {
199
+ alert('API ν‚€λ₯Ό μž…λ ₯ν•΄μ£Όμ„Έμš”');
200
+ return;
201
+ }
202
+
203
+ try {
204
+ const response = await fetch('/api/initialize', {
205
+ method: 'POST',
206
+ headers: {
207
+ 'Content-Type': 'application/json',
208
+ },
209
+ body: JSON.stringify({
210
+ api_key: apiKey,
211
+ model: model,
212
+ temperature: temperature
213
+ }),
214
+ });
215
+
216
+ const data = await response.json();
217
+
218
+ if (response.ok) {
219
+ currentSessionId = data.session_id;
220
+ document.getElementById('session-id').textContent = currentSessionId;
221
+ document.getElementById('init-form').style.display = 'none';
222
+ document.getElementById('chat-form').style.display = 'block';
223
+ document.getElementById('response-container').style.display = 'block';
224
+ } else {
225
+ alert('μ΄ˆκΈ°ν™” μ‹€νŒ¨: ' + (data.detail || 'μ•Œ 수 μ—†λŠ” 였λ₯˜'));
226
+ }
227
+ } catch (error) {
228
+ alert('였λ₯˜ λ°œμƒ: ' + error.message);
229
+ }
230
+ }
231
+
232
+ async function sendMessage() {
233
+ if (!currentSessionId) {
234
+ alert('λ¨Όμ € μ±„νŒ…μ„ μ΄ˆκΈ°ν™”ν•΄μ£Όμ„Έμš”');
235
+ return;
236
+ }
237
+
238
+ const message = document.getElementById('message').value;
239
+ const thinkingRounds = document.getElementById('thinking-rounds').value;
240
+ const alternatives = document.getElementById('alternatives').value;
241
+
242
+ if (!message) {
243
+ alert('λ©”μ‹œμ§€λ₯Ό μž…λ ₯ν•΄μ£Όμ„Έμš”');
244
+ return;
245
+ }
246
+
247
+ document.getElementById('response').textContent = '처리 쀑...';
248
+ document.getElementById('thinking-log').textContent = '';
249
+
250
+ try {
251
+ const response = await fetch('/api/send_message', {
252
+ method: 'POST',
253
+ headers: {
254
+ 'Content-Type': 'application/json',
255
+ },
256
+ body: JSON.stringify({
257
+ session_id: currentSessionId,
258
+ message: message,
259
+ thinking_rounds: thinkingRounds ? parseInt(thinkingRounds) : null,
260
+ alternatives_per_round: alternatives ? parseInt(alternatives) : 3
261
+ }),
262
+ });
263
+
264
+ const data = await response.json();
265
+
266
+ if (response.ok) {
267
+ document.getElementById('response').textContent = data.response;
268
+
269
+ // Display thinking history
270
+ let thinkingLogHTML = '';
271
+ data.thinking_history.forEach(item => {
272
+ const selected = item.selected ? ' βœ“ 선택됨' : '';
273
+ thinkingLogHTML += `<p><strong>λΌμš΄λ“œ ${item.round}${selected}:</strong> `;
274
+
275
+ if (item.explanation && item.selected) {
276
+ thinkingLogHTML += `<br><em>선택 이유: ${item.explanation}</em>`;
277
+ }
278
+ thinkingLogHTML += '</p>';
279
+ });
280
+
281
+ document.getElementById('thinking-log').innerHTML = thinkingLogHTML;
282
+ } else {
283
+ document.getElementById('response').textContent = '였λ₯˜: ' + (data.detail || 'μ•Œ 수 μ—†λŠ” 였λ₯˜');
284
+ }
285
+ } catch (error) {
286
+ document.getElementById('response').textContent = '였λ₯˜ λ°œμƒ: ' + error.message;
287
+ }
288
+ }
289
+
290
+ function resetChat() {
291
+ currentSessionId = null;
292
+ document.getElementById('init-form').style.display = 'block';
293
+ document.getElementById('chat-form').style.display = 'none';
294
+ document.getElementById('response-container').style.display = 'none';
295
+ document.getElementById('message').value = '';
296
+ document.getElementById('thinking-rounds').value = '';
297
+ document.getElementById('alternatives').value = '3';
298
+ }
299
+ </script>
300
+ </body>
301
+ </html>
302
+ """
303
+ return html_content
304
+
305
+ # Healthcheck endpoint
306
+ @app.get("/health")
307
+ async def health_check():
308
+ """Health check endpoint"""
309
+ return {"status": "healthy", "timestamp": datetime.now().isoformat()}
310
+
311
+ @app.post("/api/initialize", response_model=InitResponse)
312
+ async def initialize_chat(config: ChatConfig):
313
+ """Initialize a new chat session"""
314
+ try:
315
+ # Generate a session ID
316
+ session_id = f"session_{datetime.now().strftime('%Y%m%d%H%M%S')}_{uuid.uuid4().hex[:8]}"
317
+
318
+ # Initialize the chat instance
319
+ chat = EnhancedRecursiveThinkingChat(
320
+ api_key=config.api_key,
321
+ model=config.model,
322
+ temperature=config.temperature
323
+ )
324
+ chat_instances[session_id] = {
325
+ "chat": chat,
326
+ "created_at": datetime.now().isoformat(),
327
+ "model": config.model
328
+ }
329
+
330
+ return {"session_id": session_id, "status": "initialized"}
331
+ except Exception as e:
332
+ logger.error(f"Error initializing chat: {str(e)}")
333
+ raise HTTPException(status_code=500, detail=f"Failed to initialize chat: {str(e)}")
334
+
335
+ @app.post("/api/send_message")
336
+ async def send_message(request: MessageRequest):
337
+ """Send a message and get a response with thinking process"""
338
+ try:
339
+ if request.session_id not in chat_instances:
340
+ raise HTTPException(status_code=404, detail="Session not found")
341
+
342
+ chat = chat_instances[request.session_id]["chat"]
343
+
344
+ # Override class parameters if provided
345
+ original_thinking_fn = chat._determine_thinking_rounds
346
+ original_alternatives_fn = chat._generate_alternatives
347
+ original_temperature = getattr(chat, "temperature", 0.7)
348
+
349
+ if request.thinking_rounds is not None:
350
+ # Override the thinking rounds determination
351
+ chat._determine_thinking_rounds = lambda _: request.thinking_rounds
352
+
353
+ if request.alternatives_per_round is not None:
354
+ # Store the original function
355
+ def modified_generate_alternatives(base_response, prompt, num_alternatives=3):
356
+ return original_alternatives_fn(base_response, prompt, request.alternatives_per_round)
357
+
358
+ chat._generate_alternatives = modified_generate_alternatives
359
+
360
+ # Override temperature if provided
361
+ if request.temperature is not None:
362
+ setattr(chat, "temperature", request.temperature)
363
+
364
+ # Process the message
365
+ logger.info(f"Processing message for session {request.session_id}")
366
+ start_time = datetime.now()
367
+ result = chat.think_and_respond(request.message, verbose=True)
368
+ processing_time = (datetime.now() - start_time).total_seconds()
369
+ logger.info(f"Message processed in {processing_time:.2f} seconds")
370
+
371
+ # Restore original functions and parameters
372
+ chat._determine_thinking_rounds = original_thinking_fn
373
+ chat._generate_alternatives = original_alternatives_fn
374
+ if request.temperature is not None:
375
+ setattr(chat, "temperature", original_temperature)
376
+
377
+ return {
378
+ "session_id": request.session_id,
379
+ "response": result["response"],
380
+ "thinking_rounds": result["thinking_rounds"],
381
+ "thinking_history": result["thinking_history"],
382
+ "processing_time": processing_time
383
+ }
384
+ except Exception as e:
385
+ logger.error(f"Error processing message: {str(e)}")
386
+ raise HTTPException(status_code=500, detail=f"Failed to process message: {str(e)}")
387
+
388
+ @app.post("/api/save")
389
+ async def save_conversation(request: SaveRequest):
390
+ """Save the conversation or full thinking log"""
391
+ try:
392
+ if request.session_id not in chat_instances:
393
+ raise HTTPException(status_code=404, detail="Session not found")
394
+
395
+ chat = chat_instances[request.session_id]["chat"]
396
+
397
+ # Generate default filename if not provided
398
+ filename = request.filename
399
+ if filename is None:
400
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
401
+ log_type = "full_log" if request.full_log else "conversation"
402
+ filename = f"recthink_{log_type}_{timestamp}.json"
403
+
404
+ # Make sure the output directory exists
405
+ os.makedirs("logs", exist_ok=True)
406
+ file_path = os.path.join("logs", filename)
407
+
408
+ if request.full_log:
409
+ chat.save_full_log(file_path)
410
+ else:
411
+ chat.save_conversation(file_path)
412
+
413
+ return {"status": "saved", "filename": filename, "path": file_path}
414
+ except Exception as e:
415
+ logger.error(f"Error saving conversation: {str(e)}")
416
+ raise HTTPException(status_code=500, detail=f"Failed to save conversation: {str(e)}")
417
+
418
+ @app.get("/api/sessions", response_model=SessionResponse)
419
+ async def list_sessions():
420
+ """List all active chat sessions"""
421
+ sessions = []
422
+ for session_id, session_data in chat_instances.items():
423
+ chat = session_data["chat"]
424
+ message_count = len(chat.conversation_history) // 2 # Each message-response pair counts as 2
425
+
426
+ sessions.append(SessionInfo(
427
+ session_id=session_id,
428
+ message_count=message_count,
429
+ created_at=session_data["created_at"],
430
+ model=session_data["model"]
431
+ ))
432
+
433
+ return {"sessions": sessions}
434
+
435
+ @app.get("/api/sessions/{session_id}")
436
+ async def get_session(session_id: str):
437
+ """Get details for a specific chat session"""
438
+ if session_id not in chat_instances:
439
+ raise HTTPException(status_code=404, detail="Session not found")
440
+
441
+ session_data = chat_instances[session_id]
442
+ chat = session_data["chat"]
443
+
444
+ # Extract conversation history
445
+ conversation = []
446
+ for i in range(0, len(chat.conversation_history), 2):
447
+ if i+1 < len(chat.conversation_history):
448
+ conversation.append({
449
+ "user": chat.conversation_history[i],
450
+ "assistant": chat.conversation_history[i+1]
451
+ })
452
+
453
+ return {
454
+ "session_id": session_id,
455
+ "created_at": session_data["created_at"],
456
+ "model": session_data["model"],
457
+ "message_count": len(conversation),
458
+ "conversation": conversation
459
+ }
460
+
461
+ @app.delete("/api/sessions/{session_id}")
462
+ async def delete_session(session_id: str):
463
+ """Delete a chat session"""
464
+ if session_id not in chat_instances:
465
+ raise HTTPException(status_code=404, detail="Session not found")
466
+
467
+ del chat_instances[session_id]
468
+ return {"status": "deleted", "session_id": session_id}
469
+
470
+ # WebSocket connection manager
471
+ class ConnectionManager:
472
+ def __init__(self):
473
+ self.active_connections: Dict[str, WebSocket] = {}
474
+
475
+ async def connect(self, session_id: str, websocket: WebSocket):
476
+ await websocket.accept()
477
+ self.active_connections[session_id] = websocket
478
+
479
+ def disconnect(self, session_id: str):
480
+ if session_id in self.active_connections:
481
+ del self.active_connections[session_id]
482
+
483
+ async def send_json(self, session_id: str, data: dict):
484
+ if session_id in self.active_connections:
485
+ await self.active_connections[session_id].send_json(data)
486
+
487
+ manager = ConnectionManager()
488
+
489
+ # WebSocket for streaming thinking process
490
+ @app.websocket("/ws/{session_id}")
491
+ async def websocket_endpoint(websocket: WebSocket, session_id: str):
492
+ try:
493
+ await manager.connect(session_id, websocket)
494
+
495
+ if session_id not in chat_instances:
496
+ await websocket.send_json({"error": "Session not found"})
497
+ await websocket.close()
498
+ return
499
+
500
+ chat = chat_instances[session_id]["chat"]
501
+
502
+ # Set up a custom callback to stream thinking process
503
+ original_call_api = chat._call_api
504
+
505
+ async def stream_callback(chunk):
506
+ await manager.send_json(session_id, {"type": "chunk", "content": chunk})
507
+
508
+ # Override the _call_api method to also send updates via WebSocket
509
+ def ws_call_api(messages, temperature=0.7, stream=True):
510
+ result = original_call_api(messages, temperature, stream)
511
+ # Send the chunk via WebSocket if we're streaming
512
+ if stream:
513
+ asyncio.create_task(stream_callback(result))
514
+ return result
515
+
516
+ # Replace the method temporarily
517
+ chat._call_api = ws_call_api
518
+
519
+ # Wait for messages from the client
520
+ while True:
521
+ data = await websocket.receive_text()
522
+ message_data = json.loads(data)
523
+
524
+ if message_data["type"] == "message":
525
+ # Process the message
526
+ start_time = datetime.now()
527
+
528
+ try:
529
+ # Get parameters if they exist
530
+ thinking_rounds = message_data.get("thinking_rounds", None)
531
+ alternatives_per_round = message_data.get("alternatives_per_round", None)
532
+ temperature = message_data.get("temperature", None)
533
+
534
+ # Override if needed
535
+ original_thinking_fn = chat._determine_thinking_rounds
536
+ original_alternatives_fn = chat._generate_alternatives
537
+ original_temperature = getattr(chat, "temperature", 0.7)
538
+
539
+ if thinking_rounds is not None:
540
+ chat._determine_thinking_rounds = lambda _: thinking_rounds
541
+
542
+ if alternatives_per_round is not None:
543
+ def modified_generate_alternatives(base_response, prompt, num_alternatives=3):
544
+ return original_alternatives_fn(base_response, prompt, alternatives_per_round)
545
+
546
+ chat._generate_alternatives = modified_generate_alternatives
547
+
548
+ if temperature is not None:
549
+ setattr(chat, "temperature", temperature)
550
+
551
+ # Send a status message that we've started processing
552
+ await manager.send_json(session_id, {
553
+ "type": "status",
554
+ "status": "processing",
555
+ "message": "Starting recursive thinking process..."
556
+ })
557
+
558
+ # Process the message
559
+ result = chat.think_and_respond(message_data["content"], verbose=True)
560
+ processing_time = (datetime.now() - start_time).total_seconds()
561
+
562
+ # Restore original functions
563
+ chat._determine_thinking_rounds = original_thinking_fn
564
+ chat._generate_alternatives = original_alternatives_fn
565
+ if temperature is not None:
566
+ setattr(chat, "temperature", original_temperature)
567
+
568
+ # Send the final result
569
+ await manager.send_json(session_id, {
570
+ "type": "final",
571
+ "response": result["response"],
572
+ "thinking_rounds": result["thinking_rounds"],
573
+ "thinking_history": result["thinking_history"],
574
+ "processing_time": processing_time
575
+ })
576
+
577
+ except Exception as e:
578
+ error_msg = str(e)
579
+ logger.error(f"Error in WebSocket message processing: {error_msg}")
580
+ await manager.send_json(session_id, {
581
+ "type": "error",
582
+ "error": error_msg
583
+ })
584
+
585
+ except WebSocketDisconnect:
586
+ logger.info(f"WebSocket disconnected: {session_id}")
587
+ manager.disconnect(session_id)
588
+ except Exception as e:
589
+ error_msg = str(e)
590
+ logger.error(f"WebSocket error: {error_msg}")
591
+ try:
592
+ await websocket.send_json({"type": "error", "error": error_msg})
593
+ except:
594
+ pass
595
+ finally:
596
+ # Restore original method if needed
597
+ if 'chat' in locals() and 'original_call_api' in locals():
598
+ chat._call_api = original_call_api
599
+
600
+ # Make sure to disconnect
601
+ manager.disconnect(session_id)
602
+
603
+ # 포트 μ„€μ • - ν—ˆκΉ…νŽ˜μ΄μŠ€ μŠ€νŽ˜μ΄μŠ€μ—μ„œλŠ” 7860 포트λ₯Ό μ‚¬μš©ν•΄μ•Ό 함
604
+ if __name__ == "__main__":
605
+ # ν—ˆκΉ…νŽ˜μ΄μŠ€ μŠ€νŽ˜μ΄μŠ€μ—μ„œ μ‹€ν–‰μ‹œ 포트 7860 μ‚¬μš©
606
+ port = 7860
607
+ print(f"Starting server on port {port}")
608
+ uvicorn.run("app:app", host="0.0.0.0", port=port)
recursive_thinking_ai.py ADDED
@@ -0,0 +1,291 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import openai
2
+ import os
3
+ from typing import List, Dict
4
+ import json
5
+ import requests
6
+ from datetime import datetime
7
+ import sys
8
+ import time
9
+
10
+ class EnhancedRecursiveThinkingChat:
11
+ def __init__(self, api_key: str = None, model: str = "mistralai/mistral-small-3.1-24b-instruct:free", temperature: float = 0.7):
12
+ """Initialize with OpenRouter API."""
13
+ self.api_key = api_key or os.getenv("OPENROUTER_API_KEY")
14
+ self.model = model
15
+ self.temperature = temperature
16
+ self.base_url = "https://openrouter.ai/api/v1/chat/completions"
17
+ self.headers = {
18
+ "Authorization": f"Bearer {self.api_key}",
19
+ "HTTP-Referer": "http://localhost:3000",
20
+ "X-Title": "Recursive Thinking Chat",
21
+ "Content-Type": "application/json"
22
+ }
23
+ self.conversation_history = []
24
+ self.full_thinking_log = []
25
+
26
+ def _call_api(self, messages: List[Dict], temperature: float = None, stream: bool = True) -> str:
27
+ """Make an API call to OpenRouter with streaming support."""
28
+ if temperature is None:
29
+ temperature = self.temperature
30
+
31
+ payload = {
32
+ "model": self.model,
33
+ "messages": messages,
34
+ "temperature": temperature,
35
+ "stream": stream,
36
+ "reasoning": {
37
+ "max_tokens": 10386,
38
+ }
39
+ }
40
+
41
+ try:
42
+ response = requests.post(self.base_url, headers=self.headers, json=payload, stream=stream)
43
+ response.raise_for_status()
44
+
45
+ if stream:
46
+ full_response = ""
47
+ for line in response.iter_lines():
48
+ if line:
49
+ line = line.decode('utf-8')
50
+ if line.startswith("data: "):
51
+ line = line[6:]
52
+ if line.strip() == "[DONE]":
53
+ break
54
+ try:
55
+ chunk = json.loads(line)
56
+ if "choices" in chunk and len(chunk["choices"]) > 0:
57
+ delta = chunk["choices"][0].get("delta", {})
58
+ content = delta.get("content", "")
59
+ if content:
60
+ full_response += content
61
+ print(content, end="", flush=True)
62
+ except json.JSONDecodeError:
63
+ continue
64
+ print() # New line after streaming
65
+ return full_response
66
+ else:
67
+ return response.json()['choices'][0]['message']['content'].strip()
68
+ except Exception as e:
69
+ print(f"API Error: {e}")
70
+ return "Error: Could not get response from API"
71
+
72
+ def _determine_thinking_rounds(self, prompt: str) -> int:
73
+ """Let the model decide how many rounds of thinking are needed."""
74
+ meta_prompt = f"""Given this message: "{prompt}"
75
+ How many rounds of iterative thinking (1-5) would be optimal to generate the best response?
76
+ Consider the complexity and nuance required.
77
+ Respond with just a number between 1 and 5."""
78
+
79
+ messages = [{"role": "user", "content": meta_prompt}]
80
+ print("\n=== DETERMINING THINKING ROUNDS ===")
81
+ response = self._call_api(messages, temperature=0.3, stream=True)
82
+ print("=" * 50 + "\n")
83
+
84
+ try:
85
+ rounds = int(''.join(filter(str.isdigit, response)))
86
+ return min(max(rounds, 1), 5)
87
+ except:
88
+ return 3
89
+
90
+ def _generate_alternatives(self, base_response: str, prompt: str, num_alternatives: int = 3) -> List[str]:
91
+ """Generate alternative responses."""
92
+ alternatives = []
93
+
94
+ for i in range(num_alternatives):
95
+ print(f"\n=== GENERATING ALTERNATIVE {i+1} ===")
96
+ alt_prompt = f"""Original message: {prompt}
97
+ Current response: {base_response}
98
+ Generate an alternative response that might be better. Be creative and consider different approaches.
99
+ Alternative response:"""
100
+
101
+ messages = self.conversation_history + [{"role": "user", "content": alt_prompt}]
102
+ alternative = self._call_api(messages, temperature=0.7 + i * 0.1, stream=True)
103
+ alternatives.append(alternative)
104
+ print("=" * 50)
105
+
106
+ return alternatives
107
+
108
+ def _evaluate_responses(self, prompt: str, current_best: str, alternatives: List[str]) -> tuple[str, str]:
109
+ """Evaluate responses and select the best one."""
110
+ print("\n=== EVALUATING RESPONSES ===")
111
+ eval_prompt = f"""Original message: {prompt}
112
+ Evaluate these responses and choose the best one:
113
+ Current best: {current_best}
114
+ Alternatives:
115
+ {chr(10).join([f"{i+1}. {alt}" for i, alt in enumerate(alternatives)])}
116
+ Which response best addresses the original message? Consider accuracy, clarity, and completeness.
117
+ First, respond with ONLY 'current' or a number (1-{len(alternatives)}).
118
+ Then on a new line, explain your choice in one sentence."""
119
+
120
+ messages = [{"role": "user", "content": eval_prompt}]
121
+ evaluation = self._call_api(messages, temperature=0.2, stream=True)
122
+ print("=" * 50)
123
+
124
+ # Better parsing
125
+ lines = [line.strip() for line in evaluation.split('\n') if line.strip()]
126
+ choice = 'current'
127
+ explanation = "No explanation provided"
128
+
129
+ if lines:
130
+ first_line = lines[0].lower()
131
+ if 'current' in first_line:
132
+ choice = 'current'
133
+ else:
134
+ for char in first_line:
135
+ if char.isdigit():
136
+ choice = char
137
+ break
138
+
139
+ if len(lines) > 1:
140
+ explanation = ' '.join(lines[1:])
141
+
142
+ if choice == 'current':
143
+ return current_best, explanation
144
+ else:
145
+ try:
146
+ index = int(choice) - 1
147
+ if 0 <= index < len(alternatives):
148
+ return alternatives[index], explanation
149
+ except:
150
+ pass
151
+
152
+ return current_best, explanation
153
+
154
+ def think_and_respond(self, user_input: str, verbose: bool = True) -> Dict:
155
+ """Process user input with recursive thinking."""
156
+ print("\n" + "=" * 50)
157
+ print("πŸ€” RECURSIVE THINKING PROCESS STARTING")
158
+ print("=" * 50)
159
+
160
+ thinking_rounds = self._determine_thinking_rounds(user_input)
161
+
162
+ if verbose:
163
+ print(f"\nπŸ€” Thinking... ({thinking_rounds} rounds needed)")
164
+
165
+ # Initial response
166
+ print("\n=== GENERATING INITIAL RESPONSE ===")
167
+ messages = self.conversation_history + [{"role": "user", "content": user_input}]
168
+ current_best = self._call_api(messages, stream=True)
169
+ print("=" * 50)
170
+
171
+ thinking_history = [{"round": 0, "response": current_best, "selected": True}]
172
+
173
+ # Iterative improvement
174
+ for round_num in range(1, thinking_rounds + 1):
175
+ if verbose:
176
+ print(f"\n=== ROUND {round_num}/{thinking_rounds} ===")
177
+
178
+ # Generate alternatives
179
+ alternatives = self._generate_alternatives(current_best, user_input)
180
+
181
+ # Store alternatives in history
182
+ for i, alt in enumerate(alternatives):
183
+ thinking_history.append({
184
+ "round": round_num,
185
+ "response": alt,
186
+ "selected": False,
187
+ "alternative_number": i + 1
188
+ })
189
+
190
+ # Evaluate and select best
191
+ new_best, explanation = self._evaluate_responses(user_input, current_best, alternatives)
192
+
193
+ # Update selection in history
194
+ if new_best != current_best:
195
+ for item in thinking_history:
196
+ if item["round"] == round_num and item["response"] == new_best:
197
+ item["selected"] = True
198
+ item["explanation"] = explanation
199
+ current_best = new_best
200
+ if verbose:
201
+ print(f"\n βœ“ Selected alternative: {explanation}")
202
+ else:
203
+ for item in thinking_history:
204
+ if item["selected"] and item["response"] == current_best:
205
+ item["explanation"] = explanation
206
+ break
207
+ if verbose:
208
+ print(f"\n βœ“ Kept current response: {explanation}")
209
+
210
+ # Add to conversation history
211
+ self.conversation_history.append({"role": "user", "content": user_input})
212
+ self.conversation_history.append({"role": "assistant", "content": current_best})
213
+
214
+ # Keep conversation history manageable
215
+ if len(self.conversation_history) > 10:
216
+ self.conversation_history = self.conversation_history[-10:]
217
+
218
+ print("\n" + "=" * 50)
219
+ print("🎯 FINAL RESPONSE SELECTED")
220
+ print("=" * 50)
221
+
222
+ return {
223
+ "response": current_best,
224
+ "thinking_rounds": thinking_rounds,
225
+ "thinking_history": thinking_history
226
+ }
227
+
228
+ def save_full_log(self, filename: str = None):
229
+ """Save the full thinking process log."""
230
+ if filename is None:
231
+ filename = f"full_thinking_log_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
232
+
233
+ with open(filename, 'w', encoding='utf-8') as f:
234
+ json.dump({
235
+ "conversation": self.conversation_history,
236
+ "full_thinking_log": self.full_thinking_log,
237
+ "timestamp": datetime.now().isoformat()
238
+ }, f, indent=2, ensure_ascii=False)
239
+
240
+ print(f"Full thinking log saved to {filename}")
241
+
242
+ def save_conversation(self, filename: str = None):
243
+ """Save the conversation and thinking history."""
244
+ if filename is None:
245
+ filename = f"chat_history_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
246
+
247
+ with open(filename, 'w', encoding='utf-8') as f:
248
+ json.dump({
249
+ "conversation": self.conversation_history,
250
+ "timestamp": datetime.now().isoformat()
251
+ }, f, indent=2, ensure_ascii=False)
252
+
253
+ print(f"Conversation saved to {filename}")
254
+
255
+ # For direct use as a script
256
+ if __name__ == "__main__":
257
+ print("πŸ€– Enhanced Recursive Thinking Chat")
258
+ print("=" * 50)
259
+
260
+ # Get API key
261
+ api_key = input("Enter your OpenRouter API key (or press Enter to use env variable): ").strip()
262
+
263
+ if not api_key:
264
+ api_key = os.getenv("OPENROUTER_API_KEY")
265
+ if not api_key:
266
+ print("Error: No API key provided and OPENROUTER_API_KEY not found in environment")
267
+ sys.exit(1)
268
+
269
+ # Initialize chat
270
+ chat = EnhancedRecursiveThinkingChat(api_key=api_key)
271
+
272
+ print("\nChat initialized! Type 'exit' to quit, 'save' to save conversation.")
273
+ print("The AI will think recursively before each response.\n")
274
+
275
+ while True:
276
+ user_input = input("You: ").strip()
277
+
278
+ if user_input.lower() == 'exit':
279
+ break
280
+ elif user_input.lower() == 'save':
281
+ chat.save_conversation()
282
+ continue
283
+ elif user_input.lower() == 'save full':
284
+ chat.save_full_log()
285
+ continue
286
+ elif not user_input:
287
+ continue
288
+
289
+ # Get response with thinking process
290
+ result = chat.think_and_respond(user_input)
291
+ print(f"\nπŸ€– AI FINAL RESPONSE: {result['response']}\n")
requirements (16).txt ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ openai
3
+ fastapi==0.104.1
4
+ uvicorn==0.23.2
5
+ pydantic==2.4.2
6
+ python-dotenv==1.0.0
7
+ websockets==11.0.3
8
+ python-multipart==0.0.6
9
+ aiofiles==23.2.1
10
+ httpx==0.25.0
11
+ requests==2.31.0
12
+ asyncio==3.4.3
13
+ uuid==1.30