HUY2612 commited on
Commit
c7e4ba9
·
verified ·
1 Parent(s): b4cef93

Update a.py

Browse files
Files changed (1) hide show
  1. a.py +303 -0
a.py CHANGED
@@ -0,0 +1,303 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pathlib import Path
2
+ from dotenv import load_dotenv
3
+ from typing import Annotated
4
+ import logging
5
+ import json
6
+ import asyncio
7
+
8
+ from livekit import agents
9
+ from livekit.agents import AgentSession, Agent, RoomInputOptions, function_tool, get_job_context
10
+ from livekit import api
11
+ from livekit.agents import ConversationItemAddedEvent
12
+ from livekit.plugins import (
13
+ silero,
14
+ google,
15
+ noise_cancellation,
16
+ )
17
+ from pydantic import Field
18
+ # from livekit.plugins.turn_detector.multilingual import MultilingualModel
19
+ from utils import compare_cars_md
20
+ from pipelinev2 import RAGAgent, RAGConfig, llm
21
+
22
+ load_dotenv()
23
+
24
+ # we will store the transcripts and token usage in a json
25
+ log_dir = Path("logs")
26
+ log_dir.mkdir(exist_ok=True) # creates a folder if it didn't exist
27
+ transcript_path = log_dir / "transcript.json"
28
+ metrics_path = log_dir / "metrics.json"
29
+
30
+ # initialize files
31
+ transcript_path.write_text("[]", encoding='utf-8')
32
+ metrics_path.write_text("{}", encoding='utf-8')
33
+
34
+ # Setup logging
35
+ logger = logging.getLogger(__name__)
36
+ logger.setLevel(logging.DEBUG)
37
+
38
+ # Initialize RAG Agent
39
+ config = RAGConfig(
40
+ use_hybrid=False,
41
+ qna_file_path="QnA.json",
42
+ )
43
+ rag_agent = RAGAgent(llm=llm, config=config)
44
+ logger.info("✅ RAG Agent initialized")
45
+
46
+
47
+ @function_tool()
48
+ async def compare_two_cars(
49
+ car1: Annotated[str, Field(description="Tên xe thứ nhất cần so sánh (ví dụ: 'Honda City', 'Toyota Vios', 'Lynk & Co 05')")],
50
+ car2: Annotated[str, Field(description="Tên xe thứ hai cần so sánh (ví dụ: 'Mazda 3', 'Hyundai Accent', 'Lynk & Co 01')")],
51
+ ) -> str:
52
+ """Gọi tool này khi khách hàng muốn so sánh hai mẫu xe
53
+ - Chỉ hỏi rõ lại 2 mẫu xe khách hàng quan tâm nếu chưa rõ yêu cầu
54
+ - Sử dụng tool compare_two_cars để lấy thông tin so sánh chi tiết
55
+ - Phân tích ngắn gọn dưới 2 câu và tư vấn dựa trên kết quả so sánh
56
+ """
57
+ logger.info(f"🔧 TOOL CALLED: compare_two_cars")
58
+ logger.info(f" 📌 Car 1: {car1}")
59
+ logger.info(f" 📌 Car 2: {car2}")
60
+
61
+ try:
62
+ result = compare_cars_md(car1, car2)
63
+ logger.info(f" ✅ Comparison successful, result length: {len(result)} chars")
64
+ return result
65
+ except Exception as e:
66
+ logger.error(f" ❌ Error comparing cars: {str(e)}")
67
+ return f"Xin lỗi, đã có lỗi khi so sánh hai xe: {str(e)}"
68
+
69
+
70
+ @function_tool()
71
+ async def query_car_information(
72
+ question: Annotated[str, Field(description="Câu hỏi về thông tin xe Lynk & Co, Volvo, Geely")]
73
+ ) -> str:
74
+ """Truy vấn thông tin chi tiết về xe từ cơ sở dữ liệu.
75
+
76
+ Sử dụng khi khách hàng hỏi về:
77
+ - Thông tin các dòng xe Lynk & Co (01, 03+, 05, 06, 08, 09)
78
+ - Thông tin xe Volvo
79
+ - Thông tin xe Geely
80
+ - Đặc điểm kỹ thuật, tính năng, giá cả
81
+ - Câu hỏi chung về các thương hiệu
82
+
83
+ VÍ DỤ:
84
+ - "Lynk & Co 01 có những tính năng gì?"
85
+ - "Giá xe Volvo bao nhiêu?"
86
+ - "So sánh động cơ các dòng Lynk & Co"
87
+ """
88
+ logger.info(f"🔍 TOOL CALLED: query_car_information")
89
+ logger.info(f" 📌 Question: {question}")
90
+
91
+ try:
92
+ # Gọi RAG agent để truy vấn thông tin
93
+ result = rag_agent.invoke(question)
94
+ logger.info(f" ✅ Query successful, result length: {len(result)} chars")
95
+ return result
96
+ except Exception as e:
97
+ logger.error(f" ❌ Error querying car information: {str(e)}")
98
+ return f"Xin lỗi, đã có lỗi khi tìm kiếm thông tin: {str(e)}"
99
+
100
+
101
+ @function_tool()
102
+ async def handle_off_topic(
103
+ question: Annotated[str, Field(description="Câu hỏi không liên quan đến ô tô mà khách hàng đặt ra")]
104
+ ) -> str:
105
+ """GỌI TOOL NÀY khi khách hỏi về các vấn đề KHÔNG liên quan đến ô tô, xe hơi, mua bán xe.
106
+
107
+ SAU KHI gọi tool này, hãy:
108
+ - Trả lời câu hỏi một cách NGẮN GỌN trong 1 câu theo phong cách HÀI HƯỚC
109
+ - Khéo léo chuyển hướng về tư vấn xe
110
+ - Giữ phong cách thân thiện, chuyên nghiệp"""
111
+ logger.info(f"🎭 TOOL CALLED: handle_off_topic")
112
+ logger.info(f" 📌 Question: {question}")
113
+ logger.info(f" ✅ Letting Gemini handle off-topic response creatively")
114
+
115
+ return f"Đây là câu hỏi không liên quan đến xe. Hãy trả lời ngắn gọn, hài hước và chuyển hướng về tư vấn xe."
116
+
117
+
118
+ @function_tool()
119
+ async def navigate_to_screen(
120
+ route: Annotated[str, Field(description="Route: /home_page, /car_detail, /tourist_show_room_page, /compare_cars, /web_view_360_car_page")],
121
+ product_name: Annotated[str | None, Field(description="Tên xe. VD: 'Lynk & Co 01'")] = None,
122
+ color_type: Annotated[str | None, Field(description="Màu xe. VD: 'Đỏ'")] = None,
123
+ variant_type: Annotated[str | None, Field(description="Phiên bản. VD: 'RS'")] = None,
124
+ title: Annotated[str | None, Field(description="Tiêu đề trang")] = None,
125
+ url: Annotated[str | None, Field(description="URL 360")] = None,
126
+ is_interior: Annotated[bool | None, Field(description="True=nội thất, False=ngoại thất")] = None,
127
+ show_header: Annotated[bool, Field(description="Hiện header")] = True,
128
+ ) -> str:
129
+ """Điều hướng đến các trang trong app.
130
+
131
+ VÍ DỤ:
132
+ - Về trang chủ → route="/home_page"
133
+ - Xem xe Lynk & Co 01 → route="/car_detail", product_name="Lynk & Co 01"
134
+ - Xem showroom → route="/tourist_show_room_page"
135
+ - So sánh xe → route="/compare_cars"
136
+ - Xem nội thất xe Lynk & Co 01, màu Đỏ, phiên bản RS →
137
+ route="/web_view_360_car_page", product_name="Lynk & Co 01", color_type="Đỏ", variant_type="RS", is_interior=True
138
+ """
139
+ logger.info(f"🧭 TOOL CALLED: navigate_to_screen")
140
+ logger.info(f" 📌 Route: {route}")
141
+ logger.info(f" 📌 Product: {product_name}")
142
+ logger.info(f" 📌 Color: {color_type}")
143
+ logger.info(f" 📌 Variant: {variant_type}")
144
+
145
+ # Tạo params dict, chỉ thêm các tham số không None
146
+ params = {}
147
+ if product_name:
148
+ params["productName"] = product_name
149
+ if color_type:
150
+ params["colorType"] = color_type
151
+ if variant_type:
152
+ params["variantType"] = variant_type
153
+ if title:
154
+ params["title"] = title
155
+ if url:
156
+ params["url"] = url
157
+ if is_interior is not None:
158
+ params["isInterior"] = is_interior
159
+ if show_header is not None:
160
+ params["showHeader"] = show_header
161
+
162
+ navigation_data = {
163
+ "route": route,
164
+ "params": params
165
+ }
166
+
167
+ logger.info(f" 📱 Navigation data: {json.dumps(navigation_data, ensure_ascii=False)}")
168
+ logger.info(f" ✅ Navigation command prepared")
169
+
170
+ # Trả về JSON để app xử lý
171
+ return json.dumps(navigation_data, ensure_ascii=False)
172
+
173
+
174
+ @function_tool()
175
+ async def end_call() -> str:
176
+ """GỌI TOOL NÀY khi khách hàng nói TẠM BIỆT hoặc MUỐN KẾT THÚC cuộc gọi.
177
+
178
+ TRƯỚC KHI gọi tool này:
179
+ - Nếu khách hàng tích cực: Nói "Cảm ơn quý khách đã quan tâm. Chúc quý khách một ngày tốt lành!"
180
+ - Nếu khách hàng tiêu cực: Nói "Xin lỗi quý khách. Mong được phục vụ quý khách tốt hơn lần sau!"
181
+
182
+ SAU ĐÓ MỚI gọi tool này để kết thúc cuộc gọi."""
183
+ logger.info("📞 TOOL CALLED: end_call - Ending conversation")
184
+
185
+ try:
186
+ job_ctx = get_job_context()
187
+ await job_ctx.api.room.delete_room(api.DeleteRoomRequest(room=job_ctx.room.name))
188
+ logger.info("✅ Room deleted successfully")
189
+ return "Cuộc trò chuyện đã kết thúc. Cảm ơn quý khách!"
190
+ except Exception as e:
191
+ logger.error(f"❌ Error ending call: {str(e)}")
192
+ return "Đã kết thúc cuộc trò chuyện"
193
+
194
+
195
+ class Assistant(Agent):
196
+ def __init__(self) -> None:
197
+ logger.info("🤖 Initializing Assistant Agent with tools")
198
+ super().__init__(
199
+ instructions='''Bạn là trợ lý tư vấn xe tại VETC.
200
+
201
+ QUAN TRỌNG - Sử dụng tools:
202
+
203
+ 1. query_car_information: Khi khách hỏi về thông tin xe (tính năng, giá, kỹ thuật)
204
+ VD: "Lynk & Co 01 có gì?", "Giá xe Volvo?"
205
+
206
+ 2. navigate_to_screen: Khi khách muốn xem trang/màn hình
207
+ - "về trang chủ" → navigate_to_screen(route="/home_page")
208
+ - "xem xe [tên]" → navigate_to_screen(route="/car_detail", product_name="tên")
209
+ - "xem showroom" → navigate_to_screen(route="/tourist_show_room_page")
210
+ - "so sánh xe" → navigate_to_screen(route="/compare_cars")
211
+
212
+ 3. compare_two_cars: Khi khách muốn so sánh 2 xe cụ thể
213
+ VD: "So sánh Lynk & Co 01 và 05"
214
+
215
+ 4. handle_off_topic: Khi câu hỏi không liên quan xe
216
+
217
+ Phong cách:
218
+ - Xưng hô "quý khách"
219
+ - Thân thiện, chuyên nghiệp
220
+ - Trả lời ngắn gọn, súc tích''',
221
+ tools=[query_car_information, compare_two_cars, handle_off_topic, navigate_to_screen, end_call]
222
+ )
223
+ logger.info(f"✅ Agent initialized with {len(self.tools)} tool(s)")
224
+
225
+
226
+ async def log_transcription(event: ConversationItemAddedEvent):
227
+ """Lưu transcript vào file JSON"""
228
+ try:
229
+ item = event.item
230
+ role = item.role
231
+ text = item.text_content
232
+
233
+ # Đọc transcript hiện tại
234
+ transcript = json.loads(transcript_path.read_text(encoding='utf-8'))
235
+
236
+ # Thêm entry mới
237
+ transcript.append({
238
+ "role": role,
239
+ "text": text,
240
+ "timestamp": str(asyncio.get_event_loop().time())
241
+ })
242
+
243
+ # Lưu chỉ 100 message gần nhất
244
+ transcript_path.write_text(json.dumps(transcript[-100:], indent=2, ensure_ascii=False), encoding='utf-8')
245
+
246
+ logger.info(f"📝 Transcript logged: {role} - {text[:50]}...")
247
+ except Exception as e:
248
+ logger.error(f"❌ Error logging transcript: {str(e)}")
249
+
250
+
251
+ async def entrypoint(ctx: agents.JobContext):
252
+ logger.info("🚀 Starting entrypoint...")
253
+
254
+ session = AgentSession(
255
+ llm=google.beta.realtime.RealtimeModel(
256
+ model="gemini-2.0-flash-live-001",
257
+ voice="Aoede",
258
+ temperature=0.8,
259
+ instructions="You are a helpful assistant",
260
+ language="vi-VN",
261
+ input_audio_transcription={},
262
+ output_audio_transcription={},
263
+ ),
264
+ vad=silero.VAD.load(),
265
+ # turn_detection=MultilingualModel(),
266
+ )
267
+
268
+ # Đăng ký event handler cho transcript logging
269
+ @session.on("conversation_item_added")
270
+ def _on_conversation_item(event: ConversationItemAddedEvent):
271
+ asyncio.create_task(log_transcription(event))
272
+
273
+ # Đăng ký event handler cho function calls
274
+ @session.on("function_calls_collected")
275
+ def _on_function_calls(event):
276
+ logger.info(f"🔔 FUNCTION CALLS COLLECTED EVENT")
277
+ logger.info(f" Function calls: {event}")
278
+
279
+ @session.on("function_calls_finished")
280
+ def _on_function_calls_finished(event):
281
+ logger.info(f"✅ FUNCTION CALLS FINISHED EVENT")
282
+ logger.info(f" Results: {event}")
283
+
284
+ logger.info("📡 Starting session...")
285
+
286
+ await session.start(
287
+ room=ctx.room,
288
+ agent=Assistant(),
289
+ room_input_options=RoomInputOptions(
290
+ # LiveKit Cloud enhanced noise cancellation
291
+ # - If self-hosting, omit this parameter
292
+ # - For telephony applications, use `BVCTelephony` for best results
293
+ noise_cancellation=noise_cancellation.BVC(),
294
+ ),
295
+ )
296
+
297
+ await session.generate_reply(
298
+ instructions="Chào bạn, tôi có thể giúp gì cho bạn?"
299
+ )
300
+
301
+
302
+ if __name__ == "__main__":
303
+ agents.cli.run_app(agents.WorkerOptions(entrypoint_fnc=entrypoint))