harismlnaslm commited on
Commit
e035194
·
1 Parent(s): c75fdb8

Fix 503 error with minimal working app.py

Browse files
Files changed (1) hide show
  1. app.py +23 -577
app.py CHANGED
@@ -1,610 +1,56 @@
1
  #!/usr/bin/env python3
2
  """
3
- Textilindo AI Training API - Pure API Version
4
- No HTML interfaces, only API endpoints for training and chat
5
  """
6
 
7
  import os
8
- import json
9
- import logging
10
- import torch
11
- from pathlib import Path
12
- from datetime import datetime
13
- from typing import Dict, Any, Optional
14
- from fastapi import FastAPI, HTTPException, BackgroundTasks
15
  from pydantic import BaseModel
16
  import uvicorn
17
 
18
- # Setup logging
19
- logging.basicConfig(level=logging.INFO)
20
- logger = logging.getLogger(__name__)
21
-
22
- # Initialize FastAPI app
23
- app = FastAPI(
24
- title="Textilindo AI Training API",
25
- description="Pure API-based training system for Textilindo AI Assistant",
26
- version="1.0.0"
27
- )
28
-
29
- # Training status storage
30
- training_status = {
31
- "is_training": False,
32
- "progress": 0,
33
- "status": "idle",
34
- "current_step": 0,
35
- "total_steps": 0,
36
- "loss": 0.0,
37
- "start_time": None,
38
- "end_time": None,
39
- "error": None
40
- }
41
-
42
- # Request/Response models
43
- class TrainingRequest(BaseModel):
44
- model_name: str = "distilgpt2"
45
- dataset_path: str = "data/lora_dataset_20250910_145055.jsonl"
46
- config_path: str = "configs/training_config.yaml"
47
- max_samples: int = 20
48
- epochs: int = 1
49
- batch_size: int = 1
50
- learning_rate: float = 5e-5
51
-
52
- class TrainingResponse(BaseModel):
53
- success: bool
54
- message: str
55
- training_id: str
56
- status: str
57
 
58
  class ChatRequest(BaseModel):
59
  message: str
60
- conversation_id: Optional[str] = None
61
 
62
  class ChatResponse(BaseModel):
63
  response: str
64
- conversation_id: str
65
  status: str = "success"
66
 
67
- # API Information
68
  @app.get("/")
69
- async def api_info():
70
- """API information endpoint"""
71
- return {
72
- "name": "Textilindo AI Training API",
73
- "version": "1.0.0",
74
- "description": "Pure API-based training system for Textilindo AI Assistant",
75
- "hardware": "2 vCPU, 16 GB RAM (CPU basic)",
76
- "status": "ready",
77
- "endpoints": {
78
- "training": {
79
- "start": "POST /api/train/start",
80
- "status": "GET /api/train/status",
81
- "data": "GET /api/train/data",
82
- "gpu": "GET /api/train/gpu",
83
- "test": "POST /api/train/test"
84
- },
85
- "chat": {
86
- "chat": "POST /chat",
87
- "health": "GET /health"
88
- }
89
- }
90
- }
91
 
92
- # Health check
93
  @app.get("/health")
94
- async def health_check():
95
- """Health check endpoint"""
96
- return {
97
- "status": "healthy",
98
- "timestamp": datetime.now().isoformat(),
99
- "hardware": "2 vCPU, 16 GB RAM"
100
- }
101
 
102
  @app.get("/debug/env")
103
- async def debug_environment():
104
- """Debug endpoint to check environment variables"""
105
  api_key = os.getenv("HUGGINGFACE_API_KEY")
106
  return {
107
- "huggingface_api_key_present": bool(api_key),
108
- "huggingface_api_key_length": len(api_key) if api_key else 0,
109
- "huggingface_api_key_prefix": api_key[:10] + "..." if api_key else "None",
110
- "all_env_vars": {k: v[:10] + "..." if len(v) > 10 else v for k, v in os.environ.items() if "HUGGINGFACE" in k or "API" in k},
111
- "python_path": os.getcwd(),
112
- "files_in_dir": [f for f in os.listdir(".") if f.endswith((".py", ".txt", ".md"))]
113
  }
114
 
115
- # Training API endpoints
116
- @app.post("/api/train/start", response_model=TrainingResponse)
117
- async def start_training(request: TrainingRequest, background_tasks: BackgroundTasks):
118
- """Start training process"""
119
- global training_status
120
-
121
- if training_status["is_training"]:
122
- raise HTTPException(status_code=400, detail="Training already in progress")
123
-
124
- # Validate inputs
125
- if not Path(request.dataset_path).exists():
126
- raise HTTPException(status_code=404, detail=f"Dataset not found: {request.dataset_path}")
127
-
128
- if not Path(request.config_path).exists():
129
- raise HTTPException(status_code=404, detail=f"Config not found: {request.config_path}")
130
-
131
- # Start training in background
132
- training_id = f"train_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
133
-
134
- background_tasks.add_task(
135
- train_model_async,
136
- request.model_name,
137
- request.dataset_path,
138
- request.config_path,
139
- request.max_samples,
140
- request.epochs,
141
- request.batch_size,
142
- request.learning_rate
143
- )
144
-
145
- return TrainingResponse(
146
- success=True,
147
- message="Training started successfully",
148
- training_id=training_id,
149
- status="started"
150
- )
151
-
152
- @app.get("/api/train/status")
153
- async def get_training_status():
154
- """Get current training status"""
155
- return training_status
156
-
157
- @app.get("/api/train/data")
158
- async def get_training_data_info():
159
- """Get information about available training data"""
160
- data_dir = Path("data")
161
- if not data_dir.exists():
162
- return {"files": [], "count": 0}
163
-
164
- jsonl_files = list(data_dir.glob("*.jsonl"))
165
- files_info = []
166
-
167
- for file in jsonl_files:
168
- try:
169
- with open(file, 'r', encoding='utf-8') as f:
170
- lines = f.readlines()
171
- files_info.append({
172
- "name": file.name,
173
- "size": file.stat().st_size,
174
- "lines": len(lines)
175
- })
176
- except Exception as e:
177
- files_info.append({
178
- "name": file.name,
179
- "error": str(e)
180
- })
181
-
182
- return {
183
- "files": files_info,
184
- "count": len(jsonl_files)
185
  }
186
-
187
- @app.get("/api/train/gpu")
188
- async def get_gpu_info():
189
- """Get GPU information"""
190
- try:
191
- gpu_available = torch.cuda.is_available()
192
- if gpu_available:
193
- gpu_count = torch.cuda.device_count()
194
- gpu_name = torch.cuda.get_device_name(0)
195
- gpu_memory = torch.cuda.get_device_properties(0).total_memory / (1024**3)
196
- return {
197
- "available": True,
198
- "count": gpu_count,
199
- "name": gpu_name,
200
- "memory_gb": round(gpu_memory, 2)
201
- }
202
- else:
203
- return {"available": False}
204
- except Exception as e:
205
- return {"error": str(e)}
206
-
207
- @app.post("/api/train/test")
208
- async def test_trained_model():
209
- """Test the trained model"""
210
- model_path = "./models/textilindo-trained"
211
- if not Path(model_path).exists():
212
- return {"error": "No trained model found"}
213
 
214
- try:
215
- from transformers import AutoTokenizer, AutoModelForCausalLM
216
-
217
- tokenizer = AutoTokenizer.from_pretrained(model_path)
218
- model = AutoModelForCausalLM.from_pretrained(model_path)
219
-
220
- # Test prompt
221
- test_prompt = "Question: dimana lokasi textilindo? Answer:"
222
- inputs = tokenizer(test_prompt, return_tensors="pt")
223
-
224
- with torch.no_grad():
225
- outputs = model.generate(
226
- **inputs,
227
- max_length=inputs.input_ids.shape[1] + 30,
228
- temperature=0.7,
229
- do_sample=True,
230
- pad_token_id=tokenizer.eos_token_id
231
- )
232
-
233
- response = tokenizer.decode(outputs[0], skip_special_tokens=True)
234
-
235
- return {
236
- "success": True,
237
- "test_prompt": test_prompt,
238
- "response": response,
239
- "model_path": model_path
240
- }
241
-
242
- except Exception as e:
243
- return {"error": str(e)}
244
-
245
- @app.get("/debug/hf-api")
246
- async def debug_huggingface_api():
247
- """Debug endpoint to test HuggingFace API directly"""
248
- try:
249
- api_key = os.getenv("HUGGINGFACE_API_KEY")
250
- if not api_key:
251
- return {
252
- "error": "HUGGINGFACE_API_KEY not found in environment",
253
- "available_env_vars": [k for k in os.environ.keys() if "HUGGINGFACE" in k or "API" in k]
254
- }
255
-
256
- from huggingface_hub import InferenceClient
257
- client = InferenceClient(token=api_key)
258
-
259
- # Test with a simple prompt
260
- response = client.text_generation(
261
- "Question: What is the capital of Indonesia? Answer:",
262
- max_new_tokens=50,
263
- temperature=0.7
264
- )
265
-
266
- return {
267
- "success": True,
268
- "api_key_length": len(api_key),
269
- "api_key_prefix": api_key[:10] + "...",
270
- "test_response": response,
271
- "client_initialized": True
272
- }
273
-
274
- except Exception as e:
275
- return {
276
- "error": str(e),
277
- "api_key_present": bool(os.getenv("HUGGINGFACE_API_KEY")),
278
- "api_key_length": len(os.getenv("HUGGINGFACE_API_KEY", "")),
279
- "error_type": type(e).__name__
280
- }
281
-
282
- # Chat API endpoint
283
- @app.post("/chat", response_model=ChatResponse)
284
- async def chat(request: ChatRequest):
285
- """Chat with the AI assistant using real AI model"""
286
- try:
287
- logger.info(f"Chat request: {request.message}")
288
-
289
- # Try to use trained model first
290
- model_path = "./models/textilindo-trained"
291
- if Path(model_path).exists():
292
- logger.info("Using trained model for chat")
293
- response = await generate_ai_response(request.message, model_path)
294
- else:
295
- # Fallback to HuggingFace Inference API
296
- logger.info("Using HuggingFace Inference API for chat")
297
- api_key = os.getenv("HUGGINGFACE_API_KEY")
298
- if not api_key:
299
- logger.warning("HUGGINGFACE_API_KEY not found, using mock response")
300
- response = get_mock_response(request.message)
301
- else:
302
- response = await generate_hf_response(request.message)
303
-
304
- logger.info(f"Chat response: {response[:100]}...")
305
- return ChatResponse(
306
- response=response,
307
- conversation_id=request.conversation_id or "default",
308
- status="success"
309
- )
310
-
311
- except Exception as e:
312
- logger.error(f"Chat error: {e}")
313
- # Fallback to mock response
314
- response = get_mock_response(request.message)
315
- return ChatResponse(
316
- response=response,
317
- conversation_id=request.conversation_id or "default",
318
- status="success"
319
- )
320
-
321
- async def generate_ai_response(message: str, model_path: str) -> str:
322
- """Generate response using trained model"""
323
- try:
324
- from transformers import AutoTokenizer, AutoModelForCausalLM
325
- import torch
326
-
327
- tokenizer = AutoTokenizer.from_pretrained(model_path)
328
- model = AutoModelForCausalLM.from_pretrained(model_path)
329
-
330
- # Create prompt
331
- prompt = f"Question: {message} Answer:"
332
- inputs = tokenizer(prompt, return_tensors="pt")
333
-
334
- with torch.no_grad():
335
- outputs = model.generate(
336
- **inputs,
337
- max_length=inputs.input_ids.shape[1] + 50,
338
- temperature=0.7,
339
- do_sample=True,
340
- pad_token_id=tokenizer.eos_token_id,
341
- eos_token_id=tokenizer.eos_token_id
342
- )
343
-
344
- full_response = tokenizer.decode(outputs[0], skip_special_tokens=True)
345
-
346
- # Extract only the answer part
347
- if "Answer:" in full_response:
348
- answer = full_response.split("Answer:")[-1].strip()
349
- return answer
350
- else:
351
- return full_response
352
-
353
- except Exception as e:
354
- logger.error(f"AI model error: {e}")
355
- return get_mock_response(message)
356
-
357
- async def generate_hf_response(message: str) -> str:
358
- """Generate response using HuggingFace Inference API"""
359
- try:
360
- from huggingface_hub import InferenceClient
361
-
362
- # Get API key from environment
363
- api_key = os.getenv("HUGGINGFACE_API_KEY")
364
- if not api_key:
365
- logger.warning("HUGGINGFACE_API_KEY not found, using mock response")
366
- return get_mock_response(message)
367
-
368
- # Initialize client
369
- client = InferenceClient(token=api_key)
370
-
371
- # Load system prompt from file or use default
372
- system_prompt = load_system_prompt()
373
-
374
- # Create full prompt
375
- full_prompt = f"<|system|>\n{system_prompt}\n<|user|>\n{message}\n<|assistant|>\n"
376
-
377
- # Generate response
378
- response = client.text_generation(
379
- full_prompt,
380
- max_new_tokens=512,
381
- temperature=0.7,
382
- top_p=0.9,
383
- top_k=40,
384
- repetition_penalty=1.1,
385
- stop_sequences=["<|end|>", "<|user|>"]
386
- )
387
-
388
- # Extract only the assistant's response
389
- if "<|assistant|>" in response:
390
- assistant_response = response.split("<|assistant|>")[-1].strip()
391
- assistant_response = assistant_response.replace("<|end|>", "").strip()
392
- return assistant_response
393
- else:
394
- return response
395
-
396
- except Exception as e:
397
- logger.error(f"HuggingFace API error: {e}")
398
- return get_mock_response(message)
399
 
400
- def get_mock_response(message: str) -> str:
401
- """Fallback mock responses"""
402
- mock_responses = {
403
- "dimana lokasi textilindo": "Textilindo berkantor pusat di Jl. Raya Prancis No.39, Kosambi Tim., Kec. Kosambi, Kabupaten Tangerang, Banten 15213",
404
- "jam berapa textilindo beroperasional": "Jam operasional Senin-Jumat 08:00-17:00, Sabtu 08:00-12:00.",
405
- "jam berapa textilindo buka": "Jam operasional Senin-Jumat 08:00-17:00, Sabtu 08:00-12:00.",
406
- "berapa ketentuan pembelian": "Minimal order 1 roll per jenis kain",
407
- "apa ada gratis ongkir": "Gratis ongkir untuk order minimal 5 roll.",
408
- "apa bisa dikirimkan sample": "Hallo kak untuk sampel kita bisa kirimkan gratis ya kak 😊"
409
- }
410
-
411
- # Simple keyword matching
412
- user_lower = message.lower()
413
  for key, mock_response in mock_responses.items():
414
- if any(word in user_lower for word in key.split()):
415
- return mock_response
416
-
417
- return "Halo! Saya adalah asisten AI Textilindo. Bagaimana saya bisa membantu Anda hari ini? 😊"
418
-
419
- def load_system_prompt() -> str:
420
- """Load system prompt from file or return default"""
421
- try:
422
- system_prompt_path = "configs/system_prompt.md"
423
- if Path(system_prompt_path).exists():
424
- with open(system_prompt_path, 'r', encoding='utf-8') as f:
425
- content = f.read()
426
-
427
- # Extract SYSTEM_PROMPT from markdown if it exists
428
- if 'SYSTEM_PROMPT = """' in content:
429
- start = content.find('SYSTEM_PROMPT = """') + len('SYSTEM_PROMPT = """')
430
- end = content.find('"""', start)
431
- system_prompt = content[start:end].strip()
432
- else:
433
- # Use entire content
434
- system_prompt = content.strip()
435
-
436
- return system_prompt
437
- else:
438
- # Default system prompt
439
- return """You are a friendly and helpful AI assistant for Textilindo, a textile company.
440
-
441
- Always respond in Indonesian (Bahasa Indonesia).
442
- Keep responses short and direct.
443
- Be friendly and helpful.
444
- Use exact information from the knowledge base.
445
- The company uses yards for sales.
446
- Minimum purchase is 1 roll (67-70 yards)."""
447
- except Exception as e:
448
- logger.error(f"Error loading system prompt: {e}")
449
- return """You are a friendly and helpful AI assistant for Textilindo, a textile company.
450
-
451
- Always respond in Indonesian (Bahasa Indonesia).
452
- Keep responses short and direct.
453
- Be friendly and helpful."""
454
- async def train_model_async(
455
- model_name: str,
456
- dataset_path: str,
457
- config_path: str,
458
- max_samples: int,
459
- epochs: int,
460
- batch_size: int,
461
- learning_rate: float
462
- ):
463
- """Async training function"""
464
- global training_status
465
 
466
- try:
467
- training_status.update({
468
- "is_training": True,
469
- "status": "starting",
470
- "progress": 0,
471
- "start_time": datetime.now().isoformat(),
472
- "error": None
473
- })
474
-
475
- logger.info("🚀 Starting training...")
476
-
477
- # Import training libraries
478
- from transformers import (
479
- AutoTokenizer,
480
- AutoModelForCausalLM,
481
- TrainingArguments,
482
- Trainer,
483
- DataCollatorForLanguageModeling
484
- )
485
- from datasets import Dataset
486
-
487
- # Check GPU
488
- gpu_available = torch.cuda.is_available()
489
- logger.info(f"GPU available: {gpu_available}")
490
-
491
- # Load model and tokenizer
492
- logger.info(f"📥 Loading model: {model_name}")
493
- tokenizer = AutoTokenizer.from_pretrained(model_name)
494
- if tokenizer.pad_token is None:
495
- tokenizer.pad_token = tokenizer.eos_token
496
-
497
- # Load model
498
- if gpu_available:
499
- model = AutoModelForCausalLM.from_pretrained(
500
- model_name,
501
- torch_dtype=torch.float16,
502
- device_map="auto"
503
- )
504
- else:
505
- model = AutoModelForCausalLM.from_pretrained(model_name)
506
-
507
- logger.info("✅ Model loaded successfully")
508
-
509
- # Load training data
510
- training_data = load_training_data(dataset_path, max_samples)
511
- if not training_data:
512
- raise Exception("No training data loaded")
513
-
514
- # Convert to dataset
515
- dataset = Dataset.from_list(training_data)
516
-
517
- def tokenize_function(examples):
518
- return tokenizer(
519
- examples["text"],
520
- truncation=True,
521
- padding=True,
522
- max_length=256,
523
- return_tensors="pt"
524
- )
525
-
526
- tokenized_dataset = dataset.map(tokenize_function, batched=True)
527
-
528
- # Training arguments
529
- training_args = TrainingArguments(
530
- output_dir="./models/textilindo-trained",
531
- num_train_epochs=epochs,
532
- per_device_train_batch_size=batch_size,
533
- gradient_accumulation_steps=2,
534
- learning_rate=learning_rate,
535
- warmup_steps=5,
536
- save_steps=10,
537
- logging_steps=1,
538
- save_total_limit=1,
539
- prediction_loss_only=True,
540
- remove_unused_columns=False,
541
- fp16=gpu_available,
542
- dataloader_pin_memory=gpu_available,
543
- report_to=None,
544
- )
545
-
546
- # Data collator
547
- data_collator = DataCollatorForLanguageModeling(
548
- tokenizer=tokenizer,
549
- mlm=False,
550
- )
551
-
552
- # Create trainer
553
- trainer = Trainer(
554
- model=model,
555
- args=training_args,
556
- train_dataset=tokenized_dataset,
557
- data_collator=data_collator,
558
- tokenizer=tokenizer,
559
- )
560
-
561
- # Start training
562
- training_status["status"] = "training"
563
- trainer.train()
564
-
565
- # Save model
566
- model.save_pretrained("./models/textilindo-trained")
567
- tokenizer.save_pretrained("./models/textilindo-trained")
568
-
569
- # Update status
570
- training_status.update({
571
- "is_training": False,
572
- "status": "completed",
573
- "progress": 100,
574
- "end_time": datetime.now().isoformat()
575
- })
576
-
577
- logger.info("✅ Training completed successfully!")
578
-
579
- except Exception as e:
580
- logger.error(f"Training failed: {e}")
581
- training_status.update({
582
- "is_training": False,
583
- "status": "failed",
584
- "error": str(e),
585
- "end_time": datetime.now().isoformat()
586
- })
587
-
588
- def load_training_data(dataset_path: str, max_samples: int = 20) -> list:
589
- """Load training data from JSONL file"""
590
- data = []
591
- try:
592
- with open(dataset_path, 'r', encoding='utf-8') as f:
593
- for i, line in enumerate(f):
594
- if i >= max_samples:
595
- break
596
- if line.strip():
597
- item = json.loads(line)
598
- # Create training text
599
- instruction = item.get('instruction', '')
600
- output = item.get('output', '')
601
- text = f"Question: {instruction} Answer: {output}"
602
- data.append({"text": text})
603
- logger.info(f"Loaded {len(data)} training samples")
604
- return data
605
- except Exception as e:
606
- logger.error(f"Error loading data: {e}")
607
- return []
608
 
609
  if __name__ == "__main__":
610
  uvicorn.run(app, host="0.0.0.0", port=7860)
 
1
  #!/usr/bin/env python3
2
  """
3
+ Minimal working version to fix 503 error
 
4
  """
5
 
6
  import os
7
+ from fastapi import FastAPI
 
 
 
 
 
 
8
  from pydantic import BaseModel
9
  import uvicorn
10
 
11
+ app = FastAPI(title="Textilindo AI API")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
 
13
  class ChatRequest(BaseModel):
14
  message: str
 
15
 
16
  class ChatResponse(BaseModel):
17
  response: str
 
18
  status: str = "success"
19
 
 
20
  @app.get("/")
21
+ async def root():
22
+ return {"message": "Textilindo AI API is running", "status": "ok"}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
 
 
24
  @app.get("/health")
25
+ async def health():
26
+ return {"status": "healthy"}
 
 
 
 
 
27
 
28
  @app.get("/debug/env")
29
+ async def debug_env():
 
30
  api_key = os.getenv("HUGGINGFACE_API_KEY")
31
  return {
32
+ "api_key_present": bool(api_key),
33
+ "api_key_length": len(api_key) if api_key else 0
 
 
 
 
34
  }
35
 
36
+ @app.post("/chat")
37
+ async def chat(request: ChatRequest):
38
+ # Simple mock response for now
39
+ mock_responses = {
40
+ "jam berapa textilindo buka": "Jam operasional Senin-Jumat 08:00-17:00, Sabtu 08:00-12:00.",
41
+ "dimana lokasi textilindo": "Textilindo berkantor pusat di Jl. Raya Prancis No.39, Kosambi Tim., Kec. Kosambi, Kabupaten Tangerang, Banten 15213",
42
+ "apa ada gratis ongkir": "Gratis ongkir untuk order minimal 5 roll."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
 
45
+ user_lower = request.message.lower()
46
+ response = "Halo! Saya adalah asisten AI Textilindo. Bagaimana saya bisa membantu Anda hari ini? 😊"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
  for key, mock_response in mock_responses.items():
49
+ if any(word in user_lower for word in key.split()):
50
+ response = mock_response
51
+ break
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
 
53
+ return ChatResponse(response=response)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
 
55
  if __name__ == "__main__":
56
  uvicorn.run(app, host="0.0.0.0", port=7860)