maitrang04 commited on
Commit
d4171eb
·
verified ·
1 Parent(s): e1c13ba

Update backend/ai/services.py

Browse files
Files changed (1) hide show
  1. backend/ai/services.py +54 -32
backend/ai/services.py CHANGED
@@ -2,43 +2,40 @@ import os
2
  import time
3
  import asyncio
4
  import httpx
5
- import google.genai as genai
6
  from google.genai import types
7
  from dotenv import load_dotenv
8
- from pathlib import Path
9
-
10
- # Load ENV
11
- load_dotenv(".env.local")
12
- load_dotenv(Path(__file__).resolve().parents[3] / ".env")
13
 
 
 
14
 
15
  class AIServices:
16
  def __init__(self):
17
- # Lấy base URL từ .env
18
  self.base_url = os.getenv("VNPT_BASE_URL", "https://api.idg.vnpt.vn")
19
 
20
- # Khởi tạo Gemini Client
21
  gemini_key = os.getenv("GOOGLE_API_KEY")
22
- self.model_id = os.getenv("GEMINI_MODEL_ID", "gemini-3-flash-preview")
23
 
24
  try:
25
  if gemini_key:
26
  self.client = genai.Client(api_key=gemini_key)
27
  else:
28
- print("⚠️ Cảnh báo: Chưa cấu hình GOOGLE_API_KEY trong file .env")
29
  except Exception as e:
30
  print(f"❌ Lỗi khởi tạo Gemini: {e}")
31
 
32
  # --- 1. STT (GEMINI) ---
33
  async def speech_to_text(self, audio_content: bytes) -> str:
 
34
  print(f"🎤 [STT] Size: {len(audio_content)}")
35
  try:
36
- # PROMPT CHUYÊN DỤNG CHO VIỄN THÔNG VNPT
37
  system_prompt = (
38
- "Hãy đóng vai một công cụ Speech-to-Text chính xác. "
39
- "Chỉ được nói về chủ đề viễn thông"
40
- "Nhiệm vụ của bạn là chuyển đổi file âm thanh này thành văn bản tiếng Việt. "
41
- "Chỉ trả về đúng nội dung văn bản khách hàng nói, không thêm bất kỳ lời dẫn, giải thích hay dấu câu dư thừa nào."
 
 
42
  )
43
 
44
  response = await self.client.aio.models.generate_content(
@@ -54,18 +51,18 @@ class AIServices:
54
  print(f"❌ STT Error: {e}")
55
  return ""
56
 
57
- # --- 2. TTS (VNPT) ---
58
  async def text_to_speech(self, text: str) -> bytes:
59
  if not text: return None
60
- print(f"🔊 [VNPT TTS] Tạo: {text[:20]}...")
 
61
 
62
- # Lấy Key từ .env
63
  VNPT_ID = os.getenv("VNPT_TTS_TOKEN_ID")
64
  VNPT_KEY = os.getenv("VNPT_TTS_TOKEN_KEY")
65
  VNPT_ACCESS = os.getenv("VNPT_TTS_ACCESS_TOKEN")
66
 
67
  if not all([VNPT_ID, VNPT_KEY, VNPT_ACCESS]):
68
- print("❌ Lỗi: Thiếu cấu hình VNPT TTS trong file .env")
69
  return None
70
 
71
  url = f"{self.base_url}/tts-service/v1/standard"
@@ -75,42 +72,52 @@ class AIServices:
75
 
76
  async with httpx.AsyncClient() as client:
77
  try:
78
- res = await client.post(url, headers=headers, json=payload, timeout=10.0)
 
79
  if res.status_code != 200:
80
  print(f"❌ VNPT Error: {res.text}")
81
  return None
82
  tid = res.json().get("object", {}).get("text_id")
83
  if not tid: return None
84
 
85
- for _ in range(25): # Tăng thời gian chờ
86
- await asyncio.sleep(0.5)
87
- r = await client.post(chk_url, headers=headers, json={"text_id": tid}, timeout=10.0)
 
 
 
 
 
 
 
88
  if r.status_code == 200:
89
  d = r.json()
 
 
 
90
  if d.get("object", {}).get("code") == "success":
91
  link = d["object"]["playlist"][0]["audio_link"]
92
- dl = await client.get(link, timeout=20.0)
93
  return dl.content
 
 
 
 
94
  except Exception as e: print(f"❌ TTS Ex: {e}")
95
  return None
96
 
97
  # --- 3. SMARTBOT ---
98
  async def chat_smartbot(self, user_text: str, session_id: str = None) -> str:
99
- # Lấy Key từ .env
100
  SB_URL = os.getenv("SMARTBOT_URL")
101
  SB_TOK = os.getenv("SMARTBOT_ACCESS_TOKEN")
102
  SB_ID = os.getenv("SMARTBOT_TOKEN_ID")
103
  SB_KEY = os.getenv("SMARTBOT_TOKEN_KEY")
104
  SB_BOT = os.getenv("SMARTBOT_BOT_ID")
105
 
106
- if not all([SB_URL, SB_TOK, SB_ID, SB_KEY, SB_BOT]):
107
- print("❌ Lỗi: Thiếu cấu hình SmartBot trong file .env")
108
- return None
109
 
110
  headers = { "Authorization": SB_TOK, "Content-Type": "application/json", "Token-Id": SB_ID, "Token-Key": SB_KEY }
111
- # Nếu không có session_id thì tự tạo
112
  real_sid = session_id if session_id else f"s{int(time.time())}"
113
-
114
  payload = { "bot_id": SB_BOT, "text": user_text, "type": "text", "session_id": real_sid, "user_id": "guest" }
115
 
116
  async with httpx.AsyncClient() as client:
@@ -123,11 +130,26 @@ class AIServices:
123
  except: pass
124
  return None
125
 
126
- # --- 4. FALLBACK GEMINI ---
127
  async def chat_gemini_fallback(self, prompt: str) -> str:
128
  try:
129
  response = await self.client.aio.models.generate_content(
130
  model=self.model_id, contents=prompt
131
  )
132
  return response.text.strip() if response.text else "Dạ em nghe ạ."
133
- except: return "Dạ em xin ghi nhận ạ."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
  import time
3
  import asyncio
4
  import httpx
5
+ from google import genai
6
  from google.genai import types
7
  from dotenv import load_dotenv
 
 
 
 
 
8
 
9
+ # Tải biến môi trường
10
+ load_dotenv()
11
 
12
  class AIServices:
13
  def __init__(self):
 
14
  self.base_url = os.getenv("VNPT_BASE_URL", "https://api.idg.vnpt.vn")
15
 
 
16
  gemini_key = os.getenv("GOOGLE_API_KEY")
17
+ self.model_id = os.getenv("GEMINI_MODEL_ID", "gemini-3.0-flash-exp")
18
 
19
  try:
20
  if gemini_key:
21
  self.client = genai.Client(api_key=gemini_key)
22
  else:
23
+ print("⚠️ Cảnh báo: Chưa cấu hình GOOGLE_API_KEY")
24
  except Exception as e:
25
  print(f"❌ Lỗi khởi tạo Gemini: {e}")
26
 
27
  # --- 1. STT (GEMINI) ---
28
  async def speech_to_text(self, audio_content: bytes) -> str:
29
+ if not audio_content or len(audio_content) < 1000: return ""
30
  print(f"🎤 [STT] Size: {len(audio_content)}")
31
  try:
 
32
  system_prompt = (
33
+ "Bạn là công cụ Speech-to-Text chính xác cho viễn thông VNPT. "
34
+ "Nhiệm vụ: Chuyển đổi âm thanh thành văn bản tiếng Việt. "
35
+ "YÊU CẦU QUAN TRỌNG: "
36
+ "1. Chỉ trả về văn bản khách hàng nói. "
37
+ "2. Nếu âm thanh chỉ là tiếng ồn, tiếng thở, khoảng lặng hoặc không rõ lời: HÃY TRẢ VỀ CHUỖI RỖNG (EMPTY STRING). "
38
+ "3. Tuyệt đối KHÔNG tự bịa ra câu hỏi hoặc nội dung nếu không nghe thấy gì."
39
  )
40
 
41
  response = await self.client.aio.models.generate_content(
 
51
  print(f"❌ STT Error: {e}")
52
  return ""
53
 
54
+ # --- 2. TTS (VNPT - TỐI ƯU SMART POLLING) ---
55
  async def text_to_speech(self, text: str) -> bytes:
56
  if not text: return None
57
+ # Log ngắn gọn
58
+ print(f"🔊 [VNPT TTS] Request: {text[:30]}...")
59
 
 
60
  VNPT_ID = os.getenv("VNPT_TTS_TOKEN_ID")
61
  VNPT_KEY = os.getenv("VNPT_TTS_TOKEN_KEY")
62
  VNPT_ACCESS = os.getenv("VNPT_TTS_ACCESS_TOKEN")
63
 
64
  if not all([VNPT_ID, VNPT_KEY, VNPT_ACCESS]):
65
+ print("❌ Thiếu cấu hình VNPT TTS")
66
  return None
67
 
68
  url = f"{self.base_url}/tts-service/v1/standard"
 
72
 
73
  async with httpx.AsyncClient() as client:
74
  try:
75
+ # 1. Gửi request tạo file (Timeout ngắn 5s để fail fast)
76
+ res = await client.post(url, headers=headers, json=payload, timeout=5.0)
77
  if res.status_code != 200:
78
  print(f"❌ VNPT Error: {res.text}")
79
  return None
80
  tid = res.json().get("object", {}).get("text_id")
81
  if not tid: return None
82
 
83
+ # 2. SMART POLLING (Check nhanh cho câu ngắn để giảm độ trễ)
84
+ # Câu ngắn (< 30 ký tự) -> check mỗi 0.1s
85
+ # Câu dài -> check mỗi 0.3s
86
+ sleep_time = 0.1 if len(text) < 30 else 0.3
87
+
88
+ # Loop tối đa 30 lần (khoảng 3-9s tùy độ dài)
89
+ for _ in range(30):
90
+ await asyncio.sleep(sleep_time)
91
+
92
+ r = await client.post(chk_url, headers=headers, json={"text_id": tid}, timeout=5.0)
93
  if r.status_code == 200:
94
  d = r.json()
95
+ status = d.get("object", {}).get("status", "")
96
+
97
+ # Thành công -> Tải file
98
  if d.get("object", {}).get("code") == "success":
99
  link = d["object"]["playlist"][0]["audio_link"]
100
+ dl = await client.get(link, timeout=15.0)
101
  return dl.content
102
+
103
+ # Thất bại -> Dừng ngay
104
+ if status == "failed": break
105
+
106
  except Exception as e: print(f"❌ TTS Ex: {e}")
107
  return None
108
 
109
  # --- 3. SMARTBOT ---
110
  async def chat_smartbot(self, user_text: str, session_id: str = None) -> str:
 
111
  SB_URL = os.getenv("SMARTBOT_URL")
112
  SB_TOK = os.getenv("SMARTBOT_ACCESS_TOKEN")
113
  SB_ID = os.getenv("SMARTBOT_TOKEN_ID")
114
  SB_KEY = os.getenv("SMARTBOT_TOKEN_KEY")
115
  SB_BOT = os.getenv("SMARTBOT_BOT_ID")
116
 
117
+ if not all([SB_URL, SB_TOK, SB_ID, SB_KEY, SB_BOT]): return None
 
 
118
 
119
  headers = { "Authorization": SB_TOK, "Content-Type": "application/json", "Token-Id": SB_ID, "Token-Key": SB_KEY }
 
120
  real_sid = session_id if session_id else f"s{int(time.time())}"
 
121
  payload = { "bot_id": SB_BOT, "text": user_text, "type": "text", "session_id": real_sid, "user_id": "guest" }
122
 
123
  async with httpx.AsyncClient() as client:
 
130
  except: pass
131
  return None
132
 
133
+ # --- 4. FALLBACK GEMINI (BLOCKING) ---
134
  async def chat_gemini_fallback(self, prompt: str) -> str:
135
  try:
136
  response = await self.client.aio.models.generate_content(
137
  model=self.model_id, contents=prompt
138
  )
139
  return response.text.strip() if response.text else "Dạ em nghe ạ."
140
+ except: return "Dạ em xin ghi nhận ạ."
141
+
142
+ # --- [QUAN TRỌNG] 5. GEMINI STREAMING (CHO PIPELINE) ---
143
+ # Hàm này bắt buộc phải có để Logic Flow gọi được
144
+ async def chat_gemini_stream(self, prompt: str):
145
+ try:
146
+ # Dùng generate_content_stream để trả về Generator
147
+ async for chunk in await self.client.aio.models.generate_content_stream(
148
+ model=self.model_id,
149
+ contents=prompt
150
+ ):
151
+ if chunk.text:
152
+ yield chunk.text
153
+ except Exception as e:
154
+ print(f"❌ Gemini Stream Error: {e}")
155
+ yield ""