Cuong2004 commited on
Commit
9e98b5a
·
1 Parent(s): 17905e6
app/agent/mmca_agent.py CHANGED
@@ -21,42 +21,18 @@ from app.mcp.tools import mcp_tools
21
  from app.shared.integrations.gemini_client import GeminiClient
22
  from app.shared.integrations.megallm_client import MegaLLMClient
23
  from app.shared.logger import agent_logger, AgentWorkflow, WorkflowStep
 
 
 
 
 
 
24
 
25
 
26
  # Default coordinates for Da Nang (if no location specified)
27
  DANANG_CENTER = (16.0544, 108.2022)
28
 
29
- # System prompt for the agent - balanced for all 3 tools
30
- SYSTEM_PROMPT = """Bạn là trợ lý du lịch thông minh cho Đà Nẵng. Bạn có 3 công cụ tìm kiếm:
31
-
32
- **1. retrieve_context_text** - Tìm kiếm văn bản thông minh
33
- - Khi nào: Hỏi về menu, review, mô tả, đặc điểm, phong cách
34
- - Ví dụ: "Phở ngon giá rẻ", "Quán cafe view đẹp", "Nơi lãng mạn hẹn hò"
35
- - Đặc biệt: Tự động phát hiện category (cafe, pho, seafood...) và boost kết quả
36
-
37
- **2. retrieve_similar_visuals** - Tìm theo hình ảnh
38
- - Khi nào: Người dùng gửi ảnh hoặc mô tả về không gian, decor
39
- - Scene filter: food, interior, exterior, view
40
- - Ví dụ: "Quán có không gian giống ảnh này"
41
-
42
- **3. find_nearby_places** - Tìm theo vị trí
43
- - Khi nào: Hỏi về khoảng cách, "gần đây", "gần X", "quanh Y"
44
- - Ví dụ: "Quán cafe gần Cầu Rồng", "Nhà hàng gần bãi biển Mỹ Khê"
45
- - Đặc biệt: Có thể lấy chi tiết đầy đủ với photos, reviews
46
-
47
- **4. search_social_media** - Tìm kiếm mạng xã hội và tin tức
48
- - Khi nào: Hỏi về "review", "tin hot", "trend", "tiktok", "facebook", "tin mới"
49
- - Ví dụ: "Review quán ăn ngon Đà Nẵng trên TikTok", "Tin hot tuần qua"
50
- - Tham số: query, freshness ("pw": tuần, "pm": tháng), platforms (["tiktok", "facebook", "reddit"])
51
-
52
- **Quy tắc quan trọng:**
53
- 1. Phân tích intent để chọn ĐÚNG tool (không chỉ dùng 1 tool)
54
- 2. Với câu hỏi tổng quát ("quán cafe ngon") → dùng retrieve_context_text
55
- 3. Với câu hỏi vị trí ("gần X", "quanh Y") → dùng find_nearby_places
56
- 4. Với câu hỏi trend/review từ MXH -> dùng search_social_media
57
- 5. Với ảnh → dùng retrieve_similar_visuals
58
- 6. Trả lời tiếng Việt, thân thiện, cung cấp thông tin cụ thể (tên, rating, khoảng cách)
59
- """
60
 
61
 
62
 
@@ -496,19 +472,12 @@ class MMCAAgent:
496
 
497
  # If no tool results (greeting case), return simple response
498
  if not tool_results:
499
- # Build history section if available
500
- history_section = ""
501
- if history:
502
- history_section = f"Lịch sử hội thoại:\n{history}\n\n---\n"
503
-
504
- prompt = f"""{history_section}User nói: "{message}"
505
-
506
- Hãy trả lời thân thiện bằng tiếng Việt. Đây là lời chào hoặc tin nhắn đơn giản, không cần tìm kiếm địa điểm."""
507
 
508
  response = await self.llm_client.generate(
509
  prompt=prompt,
510
  temperature=0.7,
511
- system_instruction="Bạn là LocalMate - trợ lý du lịch thân thiện cho Đà Nẵng. Trả lời ngắn gọn, thân thiện.",
512
  )
513
  return response, []
514
 
@@ -522,32 +491,8 @@ Hãy trả lời thân thiện bằng tiếng Việt. Đây là lời chào ho
522
 
523
  context = "\n\n".join(context_parts)
524
 
525
- # Build history section if available
526
- history_section = ""
527
- if history:
528
- history_section = f"""Lịch sử hội thoại trước đó:
529
- {history}
530
-
531
- ---
532
- """
533
-
534
  # Generate response using LLM with JSON format for place selection
535
- prompt = f"""{history_section}Dựa trên kết quả tìm kiếm sau, hãy trả lời câu hỏi của người dùng.
536
-
537
- Câu hỏi hiện tại: {message}
538
-
539
- {context}
540
-
541
- **QUAN TRỌNG:** Trả lời theo format JSON:
542
- ```json
543
- {{
544
- "response": "Câu trả lời tiếng Việt, thân thiện. Giới thiệu top 2-3 địa điểm phù hợp nhất.",
545
- "selected_place_ids": ["place_id_1", "place_id_2", "place_id_3"]
546
- }}
547
- ```
548
-
549
- Chỉ chọn những place_id xuất hiện trong kết quả tìm kiếm ở trên. Nếu không có địa điểm phù hợp, để mảng rỗng.
550
- Nếu có lịch sử hội thoại, hãy cân nhắc ngữ cảnh trước đó khi trả lời."""
551
 
552
  agent_logger.llm_call(self.provider, self.model or "default", prompt[:100])
553
 
 
21
  from app.shared.integrations.gemini_client import GeminiClient
22
  from app.shared.integrations.megallm_client import MegaLLMClient
23
  from app.shared.logger import agent_logger, AgentWorkflow, WorkflowStep
24
+ from app.shared.prompts import (
25
+ MMCA_SYSTEM_PROMPT as SYSTEM_PROMPT,
26
+ GREETING_SYSTEM_PROMPT,
27
+ build_greeting_prompt,
28
+ build_synthesis_prompt,
29
+ )
30
 
31
 
32
  # Default coordinates for Da Nang (if no location specified)
33
  DANANG_CENTER = (16.0544, 108.2022)
34
 
35
+ # SYSTEM_PROMPT is imported from app.shared.prompts
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
 
37
 
38
 
 
472
 
473
  # If no tool results (greeting case), return simple response
474
  if not tool_results:
475
+ prompt = build_greeting_prompt(message, history)
 
 
 
 
 
 
 
476
 
477
  response = await self.llm_client.generate(
478
  prompt=prompt,
479
  temperature=0.7,
480
+ system_instruction=GREETING_SYSTEM_PROMPT,
481
  )
482
  return response, []
483
 
 
491
 
492
  context = "\n\n".join(context_parts)
493
 
 
 
 
 
 
 
 
 
 
494
  # Generate response using LLM with JSON format for place selection
495
+ prompt = build_synthesis_prompt(message, context, history)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
496
 
497
  agent_logger.llm_call(self.provider, self.model or "default", prompt[:100])
498
 
app/agent/react_agent.py CHANGED
@@ -26,6 +26,10 @@ from app.mcp.tools import mcp_tools
26
  from app.shared.integrations.gemini_client import GeminiClient
27
  from app.shared.integrations.megallm_client import MegaLLMClient
28
  from app.shared.logger import agent_logger, AgentWorkflow, WorkflowStep
 
 
 
 
29
 
30
 
31
  # Default coordinates for Da Nang
@@ -327,41 +331,23 @@ class ReActAgent:
327
 
328
  context = "\n\n".join(context_parts) if context_parts else "Không có kết quả."
329
 
330
- # Build history section
331
- history_section = ""
332
- if history:
333
- history_section = f"Lịch sử hội thoại:\n{history}\n\n---\n"
334
-
335
  # Build steps summary
336
  steps_summary = "\n".join([
337
  f"- Bước {s.step_number}: {s.thought[:60]}... → {get_tool_purpose(s.action)}"
338
  for s in state.steps
339
  ])
340
 
341
- prompt = f"""{history_section}Dựa trên các bước suy luận và tìm kiếm sau:
342
-
343
- {steps_summary}
344
-
345
- Và kết quả thu thập được:
346
- {context}
347
-
348
- Hãy trả lời câu hỏi của user một cách tự nhiên và hữu ích:
349
- "{state.query}"
350
-
351
- **QUAN TRỌNG:** Trả lời theo format JSON:
352
- ```json
353
- {{
354
- "response": "Câu trả lời tiếng Việt, thân thiện. Giới thiệu top 2-3 địa điểm phù hợp nhất.",
355
- "selected_place_ids": ["place_id_1", "place_id_2", "place_id_3"]
356
- }}
357
- ```
358
-
359
- Chỉ chọn những place_id xuất hiện trong kết quả tìm kiếm ở trên. Nếu không có địa điểm, để mảng rỗng."""
360
 
361
  response = await self.llm_client.generate(
362
  prompt=prompt,
363
  temperature=0.7,
364
- system_instruction="Bạn là trợ lý du lịch thông minh cho Đà Nẵng. Trả lời format JSON.",
365
  )
366
 
367
  # Parse JSON response
 
26
  from app.shared.integrations.gemini_client import GeminiClient
27
  from app.shared.integrations.megallm_client import MegaLLMClient
28
  from app.shared.logger import agent_logger, AgentWorkflow, WorkflowStep
29
+ from app.shared.prompts import (
30
+ SYNTHESIS_SYSTEM_PROMPT,
31
+ build_synthesis_prompt,
32
+ )
33
 
34
 
35
  # Default coordinates for Da Nang
 
331
 
332
  context = "\n\n".join(context_parts) if context_parts else "Không có kết quả."
333
 
 
 
 
 
 
334
  # Build steps summary
335
  steps_summary = "\n".join([
336
  f"- Bước {s.step_number}: {s.thought[:60]}... → {get_tool_purpose(s.action)}"
337
  for s in state.steps
338
  ])
339
 
340
+ prompt = build_synthesis_prompt(
341
+ message=state.query,
342
+ context=context,
343
+ history=history,
344
+ include_steps=steps_summary,
345
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
346
 
347
  response = await self.llm_client.generate(
348
  prompt=prompt,
349
  temperature=0.7,
350
+ system_instruction=SYNTHESIS_SYSTEM_PROMPT,
351
  )
352
 
353
  # Parse JSON response
app/agent/reasoning.py CHANGED
@@ -6,54 +6,13 @@ from dataclasses import dataclass
6
  from typing import Any
7
 
8
  from app.shared.logger import agent_logger
 
 
 
 
9
 
10
-
11
- # System prompt for ReAct mode
12
- REACT_SYSTEM_PROMPT = """Bạn là agent du lịch thông minh cho Đà Nẵng với khả năng suy luận multi-step.
13
-
14
- **Tools có sẵn:**
15
- 1. `get_location_coordinates` - Lấy tọa độ từ tên địa điểm
16
- - Input: {"location_name": "Dragon Bridge"}
17
- - Output: {"lat": 16.06, "lng": 108.22}
18
-
19
- 2. `find_nearby_places` - Tìm địa điểm gần vị trí
20
- - Input: {"lat": 16.06, "lng": 108.22, "category": "cafe", "max_distance_km": 3}
21
- - Output: [{name, category, distance_km, rating}]
22
-
23
- 3. `retrieve_context_text` - Tìm semantic trong reviews/descriptions
24
- - Input: {"query": "cafe view đẹp", "limit": 5}
25
- - Output: [{name, category, rating, source_text}]
26
-
27
- 4. `retrieve_similar_visuals` - Tìm địa điểm có hình ảnh tương tự
28
- - Input: {"image_url": "..."}
29
- - Output: [{name, similarity, image_url}]
30
-
31
- 5. `search_social_media` - Tìm kiếm mạng xã hội và tin tức
32
- - Input: {"query": "review quán ăn", "freshness": "pw", "platforms": ["tiktok"]}
33
- - Output: [{title, url, age, platform}]
34
-
35
- **Quy trình:**
36
- Với mỗi bước, bạn phải:
37
- 1. **Thought**: Suy nghĩ về bước tiếp theo cần làm
38
- 2. **Action**: Chọn tool hoặc "finish" nếu đủ thông tin
39
- 3. **Action Input**: JSON parameters cho tool
40
-
41
- **Trả lời CHÍNH XÁC theo format JSON:**
42
- ```json
43
- {
44
- "thought": "Suy nghĩ của bạn...",
45
- "action": "tool_name hoặc finish",
46
- "action_input": {"param1": "value1"}
47
- }
48
- ```
49
-
50
- **Quan trọng:**
51
- - Nếu cần biết vị trí → dùng get_location_coordinates trước
52
- - Nếu tìm theo khoảng cách → dùng find_nearby_places
53
- - Nếu tìm review/trend MXH → dùng search_social_media
54
- - Nếu cần lọc theo đặc điểm (view, không gian, giá) → dùng retrieve_context_text
55
- - Khi đủ thông tin → action = "finish"
56
- """
57
 
58
 
59
 
@@ -202,13 +161,4 @@ Trả lời theo format JSON:
202
 
203
  def get_tool_purpose(action: str) -> str:
204
  """Get human-readable purpose for a tool."""
205
- purposes = {
206
- "get_location_coordinates": "Lấy tọa độ địa điểm",
207
- "find_nearby_places": "Tìm địa điểm gần vị trí",
208
- "retrieve_context_text": "Tìm theo văn bản (reviews, mô tả)",
209
- "retrieve_similar_visuals": "Tìm theo hình ảnh tương tự",
210
- "search_social_media": "Tìm kiếm mạng xã hội và tin tức",
211
- "finish": "Hoàn thành và tổng hợp kết quả",
212
- }
213
-
214
- return purposes.get(action, action)
 
6
  from typing import Any
7
 
8
  from app.shared.logger import agent_logger
9
+ from app.shared.prompts import (
10
+ REACT_SYSTEM_PROMPT,
11
+ TOOL_PURPOSES,
12
+ )
13
 
14
+ # Re-export for backward compatibility
15
+ __all__ = ["REACT_SYSTEM_PROMPT", "ReasoningResult", "parse_reasoning_response", "build_reasoning_prompt", "get_tool_purpose"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
 
17
 
18
 
 
161
 
162
  def get_tool_purpose(action: str) -> str:
163
  """Get human-readable purpose for a tool."""
164
+ return TOOL_PURPOSES.get(action, action)
 
 
 
 
 
 
 
 
 
app/mcp/tools/__init__.py CHANGED
@@ -37,7 +37,7 @@ from app.mcp.tools.graph_tool import (
37
  from app.mcp.tools.social_tool import (
38
  SocialSearchResult,
39
  search_social_media,
40
- # TODO: Define TOOL_DEFINITION in social_tool if needed for agent JSON schema
41
  )
42
 
43
 
@@ -47,6 +47,7 @@ TOOL_DEFINITIONS = [
47
  TEXT_TOOL_DEFINITION,
48
  VISUAL_TOOL_DEFINITION,
49
  GRAPH_TOOL_DEFINITION,
 
50
  ]
51
 
52
 
 
37
  from app.mcp.tools.social_tool import (
38
  SocialSearchResult,
39
  search_social_media,
40
+ TOOL_DEFINITION as SOCIAL_TOOL_DEFINITION,
41
  )
42
 
43
 
 
47
  TEXT_TOOL_DEFINITION,
48
  VISUAL_TOOL_DEFINITION,
49
  GRAPH_TOOL_DEFINITION,
50
+ SOCIAL_TOOL_DEFINITION,
51
  ]
52
 
53
 
app/mcp/tools/graph_tool.py CHANGED
@@ -73,49 +73,12 @@ class PlaceDetails:
73
  same_category: list[dict[str, Any]] = field(default_factory=list)
74
 
75
 
76
- # Available categories in Neo4j
77
- AVAILABLE_CATEGORIES = [
78
- "Asian restaurant", "Athletic club", "Badminton court", "Bakery", "Bar",
79
- "Bistro", "Board game club", "Breakfast restaurant", "Cafe",
80
- "Cantonese restaurant", "Chicken restaurant", "Chinese restaurant",
81
- "Cocktail bar", "Coffee shop", "Country food restaurant", "Deli",
82
- "Dessert shop", "Disco club", "Dumpling restaurant", "Espresso bar",
83
- "Family restaurant", "Fine dining restaurant", "Fitness center",
84
- "Food court", "French restaurant", "Game store", "Gym",
85
- "Hamburger restaurant", "Holiday apartment rental", "Hot pot restaurant",
86
- "Hotel", "Ice cream shop", "Indian restaurant", "Irish pub",
87
- "Italian restaurant", "Izakaya restaurant", "Japanese restaurant",
88
- "Korean barbecue restaurant", "Korean restaurant", "Live music bar",
89
- "Malaysian restaurant", "Mexican restaurant", "Movie theater",
90
- "Musical club", "Noodle shop", "Pho restaurant", "Pickleball court",
91
- "Pizza restaurant", "Ramen restaurant", "Restaurant", "Restaurant or cafe",
92
- "Rice cake shop", "Sandwich shop", "Seafood restaurant", "Soccer field",
93
- "Soup shop", "Sports bar", "Sports club", "Sports complex", "Steak house",
94
- "Sushi restaurant", "Takeout Restaurant", "Tennis court", "Tiffin center",
95
- "Udon noodle restaurant", "Vegan restaurant", "Vegetarian restaurant",
96
- "Vietnamese restaurant",
97
- ]
98
-
99
-
100
- # Tool definition for agent
101
- TOOL_DEFINITION = {
102
- "name": "find_nearby_places",
103
- "description": """Tìm địa điểm gần một vị trí hoặc lấy chi tiết địa điểm.
104
-
105
- Dùng khi:
106
- - Người dùng hỏi về vị trí, khoảng cách, "gần đây", "gần X"
107
- - Cần tìm quán xung quanh một landmark (Cầu Rồng, Mỹ Khê, Bà Nà)
108
- - Lấy chi tiết đầy đủ về một địa điểm cụ thể
109
-
110
- Categories: Restaurant, Coffee shop, Cafe, Bar, Hotel, Seafood restaurant,
111
- Japanese restaurant, Korean restaurant, Gym, Fitness center, v.v.""",
112
- "parameters": {
113
- "location": "Tên địa điểm trung tâm (VD: 'Bãi biển Mỹ Khê', 'Cầu Rồng')",
114
- "category": "Loại địa điểm: restaurant, coffee, hotel, bar, seafood, gym, etc.",
115
- "max_distance_km": "Khoảng cách tối đa tính theo km (mặc định 5)",
116
- "limit": "Số kết quả tối đa (mặc định 10)",
117
- },
118
- }
119
 
120
 
121
  async def find_nearby_places(
 
73
  same_category: list[dict[str, Any]] = field(default_factory=list)
74
 
75
 
76
+ # Available categories in Neo4j - imported from centralized prompts
77
+ from app.shared.prompts import AVAILABLE_CATEGORIES
78
+
79
+
80
+ # Tool definition for agent - imported from centralized prompts
81
+ from app.shared.prompts import FIND_NEARBY_PLACES_TOOL as TOOL_DEFINITION
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
82
 
83
 
84
  async def find_nearby_places(
app/mcp/tools/social_tool.py CHANGED
@@ -7,6 +7,10 @@ from typing import Optional, List, Dict, Any
7
  from dotenv import load_dotenv
8
  load_dotenv() # Ensure .env is loaded before os.getenv()
9
 
 
 
 
 
10
  @dataclass
11
  class SocialSearchResult:
12
  title: str
 
7
  from dotenv import load_dotenv
8
  load_dotenv() # Ensure .env is loaded before os.getenv()
9
 
10
+ # Tool definition for agent - imported from centralized prompts
11
+ from app.shared.prompts import SEARCH_SOCIAL_MEDIA_TOOL as TOOL_DEFINITION
12
+
13
+
14
  @dataclass
15
  class SocialSearchResult:
16
  title: str
app/mcp/tools/text_tool.py CHANGED
@@ -27,49 +27,12 @@ class TextSearchResult:
27
  source_text: str = ""
28
  content_type: str = ""
29
 
 
 
30
 
31
- # Category keywords for intent detection
32
- CATEGORY_KEYWORDS = {
33
- 'cafe': ['cafe', 'cà phê', 'coffee', 'caphe', 'caphê'],
34
- 'pho': ['phở', 'pho'],
35
- 'banh_mi': ['bánh mì', 'banh mi', 'bread'],
36
- 'seafood': ['hải sản', 'hai san', 'seafood', 'cá', 'tôm', 'cua'],
37
- 'restaurant': ['nhà hàng', 'restaurant', 'quán ăn', 'ăn'],
38
- 'bar': ['bar', 'pub', 'cocktail', 'beer', 'bia'],
39
- 'hotel': ['hotel', 'khách sạn', 'resort', 'villa'],
40
- 'japanese': ['nhật', 'japan', 'sushi', 'ramen'],
41
- 'korean': ['hàn', 'korea', 'bbq'],
42
- }
43
-
44
- CATEGORY_TO_DB = {
45
- 'cafe': ['Coffee shop', 'Cafe', 'Coffee house', 'Espresso bar'],
46
- 'pho': ['Pho restaurant', 'Bistro', 'Restaurant', 'Vietnamese restaurant'],
47
- 'banh_mi': ['Bakery', 'Tiffin center', 'Restaurant'],
48
- 'seafood': ['Seafood restaurant', 'Restaurant', 'Asian restaurant'],
49
- 'restaurant': ['Restaurant', 'Vietnamese restaurant', 'Asian restaurant'],
50
- 'bar': ['Bar', 'Cocktail bar', 'Pub', 'Night club', 'Live music bar'],
51
- 'hotel': ['Hotel', 'Resort', 'Apartment', 'Villa', 'Holiday apartment rental'],
52
- 'japanese': ['Japanese restaurant', 'Sushi restaurant', 'Ramen restaurant'],
53
- 'korean': ['Korean restaurant', 'Korean barbecue restaurant'],
54
- }
55
-
56
-
57
- # Tool definition for agent
58
- TOOL_DEFINITION = {
59
- "name": "retrieve_context_text",
60
- "description": """Tìm kiếm thông tin địa điểm dựa trên văn bản, mô tả, đánh giá.
61
-
62
- Dùng khi:
63
- - Người dùng hỏi về menu, review, mô tả địa điểm
64
- - Tìm kiếm theo đặc điểm: "quán cafe view đẹp", "phở ngon giá rẻ"
65
- - Tìm theo không khí: "nơi lãng mạn", "chỗ yên tĩnh làm việc"
66
-
67
- Hỗ trợ: Vietnamese + English""",
68
- "parameters": {
69
- "query": "Câu query tìm kiếm tự nhiên (VD: 'quán phở nước dùng đậm đà')",
70
- "limit": "Số kết quả tối đa (mặc định 10)",
71
- },
72
- }
73
 
74
 
75
  def detect_category_intent(query: str) -> Optional[str]:
 
27
  source_text: str = ""
28
  content_type: str = ""
29
 
30
+ # Category constants - imported from centralized prompts
31
+ from app.shared.prompts import CATEGORY_KEYWORDS, CATEGORY_TO_DB
32
 
33
+
34
+ # Tool definition for agent - imported from centralized prompts
35
+ from app.shared.prompts import RETRIEVE_CONTEXT_TEXT_TOOL as TOOL_DEFINITION
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
 
37
 
38
  def detect_category_intent(query: str) -> Optional[str]:
app/mcp/tools/visual_tool.py CHANGED
@@ -27,22 +27,8 @@ class ImageSearchResult:
27
  matched_images: int = 1
28
  image_url: str = ""
29
 
30
-
31
- # Tool definition for agent
32
- TOOL_DEFINITION = {
33
- "name": "retrieve_similar_visuals",
34
- "description": """Tìm địa điểm có hình ảnh tương tự.
35
-
36
- Dùng khi:
37
- - Người dùng gửi ảnh và muốn tìm nơi tương tự
38
- - Mô tả về không gian, decor, view
39
- - "Tìm quán có view như này", "Nơi nào có không gian giống ảnh này"
40
- """,
41
- "parameters": {
42
- "image_url": "URL của ảnh cần tìm kiếm tương tự",
43
- "limit": "Số kết quả tối đa (mặc định 10)",
44
- },
45
- }
46
 
47
 
48
  async def retrieve_similar_visuals(
 
27
  matched_images: int = 1
28
  image_url: str = ""
29
 
30
+ # Tool definition for agent - imported from centralized prompts
31
+ from app.shared.prompts import RETRIEVE_SIMILAR_VISUALS_TOOL as TOOL_DEFINITION
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
 
33
 
34
  async def retrieve_similar_visuals(
app/shared/integrations/megallm_client.py CHANGED
@@ -67,8 +67,11 @@ class MegaLLMClient:
67
  last_error = None
68
  rate_limit_retries = 0
69
 
70
- for attempt in range(max_retries + 1):
71
- # Get rotated API key (get new key on each attempt for rate limit issues)
 
 
 
72
  api_key = self._get_api_key()
73
 
74
  try:
@@ -96,9 +99,10 @@ class MegaLLMClient:
96
  f"after {wait_time}s"
97
  )
98
  await asyncio.sleep(wait_time)
99
- continue
100
  else:
101
- # Max retries exceeded, raise the error
 
102
  response.raise_for_status()
103
 
104
  response.raise_for_status()
@@ -110,16 +114,20 @@ class MegaLLMClient:
110
  if attempt < max_retries:
111
  logger.warning(f"[MegaLLM] Timeout, retry {attempt + 1}/{max_retries}")
112
  continue
 
113
  raise
114
- except httpx.HTTPStatusError as e:
115
  # Re-raise HTTP errors (including 429 after max retries)
 
116
  raise
117
  except Exception as e:
118
  last_error = e
119
  raise
120
 
121
- # This shouldn't be reached, but just in case
122
- raise last_error if last_error else RuntimeError("Unknown error")
 
 
123
 
124
  async def chat(
125
  self,
 
67
  last_error = None
68
  rate_limit_retries = 0
69
 
70
+ # Total attempts = max_retries + 1 (for timeout) + MAX_429_RETRIES (for rate limits)
71
+ total_attempts = max_retries + 1 + MAX_429_RETRIES
72
+
73
+ for attempt in range(total_attempts):
74
+ # Get rotated API key (get new key on each attempt)
75
  api_key = self._get_api_key()
76
 
77
  try:
 
99
  f"after {wait_time}s"
100
  )
101
  await asyncio.sleep(wait_time)
102
+ continue # Try again with a new key
103
  else:
104
+ # Max 429 retries exceeded - raise HTTPStatusError
105
+ logger.error(f"[MegaLLM] Max 429 retries exceeded ({MAX_429_RETRIES})")
106
  response.raise_for_status()
107
 
108
  response.raise_for_status()
 
114
  if attempt < max_retries:
115
  logger.warning(f"[MegaLLM] Timeout, retry {attempt + 1}/{max_retries}")
116
  continue
117
+ # After max timeout retries, raise the error
118
  raise
119
+ except httpx.HTTPStatusError:
120
  # Re-raise HTTP errors (including 429 after max retries)
121
+ # This allows react_agent to catch and handle gracefully
122
  raise
123
  except Exception as e:
124
  last_error = e
125
  raise
126
 
127
+ # If we exit the loop without returning, something went wrong
128
+ if last_error:
129
+ raise last_error
130
+ raise RuntimeError("Max retries exceeded")
131
 
132
  async def chat(
133
  self,
app/shared/prompts/__init__.py ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Prompts package for LocalMate Agent."""
2
+
3
+ from app.shared.prompts.prompts import (
4
+ MMCA_SYSTEM_PROMPT,
5
+ REACT_SYSTEM_PROMPT,
6
+ GREETING_SYSTEM_PROMPT,
7
+ SYNTHESIS_SYSTEM_PROMPT,
8
+ TOOL_DEFINITIONS,
9
+ TOOL_PURPOSES,
10
+ # Tool-specific definitions
11
+ FIND_NEARBY_PLACES_TOOL,
12
+ RETRIEVE_CONTEXT_TEXT_TOOL,
13
+ RETRIEVE_SIMILAR_VISUALS_TOOL,
14
+ SEARCH_SOCIAL_MEDIA_TOOL,
15
+ # Database constants
16
+ AVAILABLE_CATEGORIES,
17
+ CATEGORY_KEYWORDS,
18
+ CATEGORY_TO_DB,
19
+ # Prompt builders
20
+ build_greeting_prompt,
21
+ build_synthesis_prompt,
22
+ build_reasoning_prompt,
23
+ )
24
+
25
+ __all__ = [
26
+ "MMCA_SYSTEM_PROMPT",
27
+ "REACT_SYSTEM_PROMPT",
28
+ "GREETING_SYSTEM_PROMPT",
29
+ "SYNTHESIS_SYSTEM_PROMPT",
30
+ "TOOL_DEFINITIONS",
31
+ "TOOL_PURPOSES",
32
+ "FIND_NEARBY_PLACES_TOOL",
33
+ "RETRIEVE_CONTEXT_TEXT_TOOL",
34
+ "RETRIEVE_SIMILAR_VISUALS_TOOL",
35
+ "SEARCH_SOCIAL_MEDIA_TOOL",
36
+ "AVAILABLE_CATEGORIES",
37
+ "CATEGORY_KEYWORDS",
38
+ "CATEGORY_TO_DB",
39
+ "build_greeting_prompt",
40
+ "build_synthesis_prompt",
41
+ "build_reasoning_prompt",
42
+ ]
app/shared/prompts/prompts.py ADDED
@@ -0,0 +1,418 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Centralized Prompts for LocalMate Agent.
3
+
4
+ This file contains all system prompts and prompt templates used across the agent.
5
+ Import prompts from this file to ensure consistency and easy maintenance.
6
+
7
+ Usage:
8
+ from app.shared.prompts.prompts import (
9
+ MMCA_SYSTEM_PROMPT,
10
+ REACT_SYSTEM_PROMPT,
11
+ build_synthesis_prompt,
12
+ build_greeting_prompt,
13
+ build_reasoning_prompt,
14
+ )
15
+ """
16
+
17
+ # =============================================================================
18
+ # TOOL DEFINITIONS
19
+ # =============================================================================
20
+
21
+ TOOL_DEFINITIONS = """
22
+ **Tools có sẵn:**
23
+ 1. `get_location_coordinates` - Lấy tọa độ từ tên địa điểm
24
+ - Input: {"location_name": "Dragon Bridge"}
25
+ - Output: {"lat": 16.06, "lng": 108.22}
26
+
27
+ 2. `find_nearby_places` - Tìm địa điểm gần vị trí
28
+ - Input: {"lat": 16.06, "lng": 108.22, "category": "cafe", "max_distance_km": 3}
29
+ - Output: [{name, category, distance_km, rating}]
30
+
31
+ 3. `retrieve_context_text` - Tìm semantic trong reviews/descriptions
32
+ - Input: {"query": "cafe view đẹp", "limit": 5}
33
+ - Output: [{name, category, rating, source_text}]
34
+
35
+ 4. `retrieve_similar_visuals` - Tìm địa điểm có hình ảnh tương tự
36
+ - Input: {"image_url": "..."}
37
+ - Output: [{name, similarity, image_url}]
38
+
39
+ 5. `search_social_media` - Tìm kiếm mạng xã hội và tin tức
40
+ - Input: {"query": "review quán ăn", "freshness": "pw", "platforms": ["tiktok"]}
41
+ - Output: [{title, url, age, platform}]
42
+ """
43
+
44
+ TOOL_PURPOSES = {
45
+ "get_location_coordinates": "Lấy tọa độ địa điểm",
46
+ "find_nearby_places": "Tìm địa điểm gần vị trí",
47
+ "retrieve_context_text": "Tìm theo văn bản (reviews, mô tả)",
48
+ "retrieve_similar_visuals": "Tìm theo hình ảnh tương tự",
49
+ "search_social_media": "Tìm kiếm mạng xã hội và tin tức",
50
+ "finish": "Hoàn thành và tổng hợp kết quả",
51
+ }
52
+
53
+
54
+ # =============================================================================
55
+ # SYSTEM PROMPTS
56
+ # =============================================================================
57
+
58
+ MMCA_SYSTEM_PROMPT = """Bạn là trợ lý du lịch thông minh cho Đà Nẵng. Bạn có 3 công cụ tìm kiếm:
59
+
60
+ **1. retrieve_context_text** - Tìm kiếm văn bản thông minh
61
+ - Khi nào: Hỏi về menu, review, mô tả, đặc điểm, phong cách
62
+ - Ví dụ: "Phở ngon giá rẻ", "Quán cafe view đẹp", "Nơi lãng mạn hẹn hò"
63
+ - Đặc biệt: Tự động phát hiện category (cafe, pho, seafood...) và boost kết quả
64
+
65
+ **2. retrieve_similar_visuals** - Tìm theo hình ảnh
66
+ - Khi nào: Người dùng gửi ảnh hoặc mô tả về không gian, decor
67
+ - Scene filter: food, interior, exterior, view
68
+ - Ví dụ: "Quán có không gian giống ảnh này"
69
+
70
+ **3. find_nearby_places** - Tìm theo vị trí
71
+ - Khi nào: Hỏi về khoảng cách, "gần đây", "gần X", "quanh Y"
72
+ - Ví dụ: "Quán cafe gần Cầu Rồng", "Nhà hàng gần bãi biển Mỹ Khê"
73
+ - Đặc biệt: Có thể lấy chi tiết đầy đủ với photos, reviews
74
+
75
+ **4. search_social_media** - Tìm kiếm mạng xã hội và tin tức
76
+ - Khi nào: Hỏi về "review", "tin hot", "trend", "tiktok", "facebook", "tin mới"
77
+ - Ví dụ: "Review quán ăn ngon Đà Nẵng trên TikTok", "Tin hot tuần qua"
78
+ - Tham số: query, freshness ("pw": tuần, "pm": tháng), platforms (["tiktok", "facebook", "reddit"])
79
+
80
+ **Quy tắc quan trọng:**
81
+ 1. Phân tích intent để chọn ĐÚNG tool (không chỉ dùng 1 tool)
82
+ 2. Với câu hỏi tổng quát ("quán cafe ngon") → dùng retrieve_context_text
83
+ 3. Với câu hỏi vị trí ("gần X", "quanh Y") → dùng find_nearby_places
84
+ 4. Với câu hỏi trend/review từ MXH -> dùng search_social_media
85
+ 5. Với ảnh → dùng retrieve_similar_visuals
86
+ 6. Trả lời tiếng Việt, thân thiện, cung cấp thông tin cụ thể (tên, rating, khoảng cách)
87
+ """
88
+
89
+
90
+ REACT_SYSTEM_PROMPT = f"""Bạn là agent du lịch thông minh cho Đà Nẵng với khả năng suy luận multi-step.
91
+
92
+ {TOOL_DEFINITIONS}
93
+
94
+ **Quy trình:**
95
+ Với mỗi bước, bạn phải:
96
+ 1. **Thought**: Suy nghĩ về bước tiếp theo cần làm
97
+ 2. **Action**: Chọn tool hoặc "finish" nếu đủ thông tin
98
+ 3. **Action Input**: JSON parameters cho tool
99
+
100
+ **Trả lời CHÍNH XÁC theo format JSON:**
101
+ ```json
102
+ {{
103
+ "thought": "Suy nghĩ của bạn...",
104
+ "action": "tool_name hoặc finish",
105
+ "action_input": {{"param1": "value1"}}
106
+ }}
107
+ ```
108
+
109
+ **Quan trọng:**
110
+ - Nếu cần biết vị trí → dùng get_location_coordinates trước
111
+ - Nếu tìm theo khoảng cách → dùng find_nearby_places
112
+ - Nếu tìm review/trend MXH → dùng search_social_media
113
+ - Nếu cần lọc theo đặc điểm (view, không gian, giá) → dùng retrieve_context_text
114
+ - Khi đủ thông tin → action = "finish"
115
+ """
116
+
117
+
118
+ GREETING_SYSTEM_PROMPT = "Bạn là LocalMate - trợ lý du lịch thân thiện cho Đà Nẵng. Trả lời ngắn gọn, thân thiện."
119
+
120
+
121
+ SYNTHESIS_SYSTEM_PROMPT = "Bạn là trợ lý du lịch thông minh cho Đà Nẵng. Trả lời format JSON."
122
+
123
+
124
+ # =============================================================================
125
+ # PROMPT TEMPLATES
126
+ # =============================================================================
127
+
128
+ def build_greeting_prompt(message: str, history: str | None = None) -> str:
129
+ """
130
+ Build prompt for greeting/simple message response.
131
+
132
+ Args:
133
+ message: User's message
134
+ history: Optional conversation history
135
+
136
+ Returns:
137
+ Formatted prompt string
138
+ """
139
+ history_section = ""
140
+ if history:
141
+ history_section = f"Lịch sử hội thoại:\n{history}\n\n---\n"
142
+
143
+ return f"""{history_section}User nói: "{message}"
144
+
145
+ Hãy trả lời thân thiện bằng tiếng Việt. Đây là lời chào hoặc tin nhắn đơn giản, không cần tìm kiếm địa điểm."""
146
+
147
+
148
+ def build_synthesis_prompt(
149
+ message: str,
150
+ context: str,
151
+ history: str | None = None,
152
+ include_steps: str | None = None,
153
+ ) -> str:
154
+ """
155
+ Build prompt for synthesizing response from tool results.
156
+
157
+ Args:
158
+ message: User's query
159
+ context: Tool results as formatted string
160
+ history: Optional conversation history
161
+ include_steps: Optional steps summary (for ReAct mode)
162
+
163
+ Returns:
164
+ Formatted prompt string
165
+ """
166
+ history_section = ""
167
+ if history:
168
+ history_section = f"""Lịch sử hội thoại trước đó:
169
+ {history}
170
+
171
+ ---
172
+ """
173
+
174
+ steps_section = ""
175
+ if include_steps:
176
+ steps_section = f"""Dựa trên các bước suy luận và tìm kiếm sau:
177
+
178
+ {include_steps}
179
+
180
+ Và kết quả thu thập được:
181
+ """
182
+
183
+ return f"""{history_section}{steps_section}Dựa trên kết quả tìm kiếm sau, hãy trả lời câu hỏi của người dùng.
184
+
185
+ Câu hỏi hiện tại: {message}
186
+
187
+ {context}
188
+
189
+ **QUAN TRỌNG:** Trả lời theo format JSON:
190
+ ```json
191
+ {{
192
+ "response": "Câu trả lời tiếng Việt, thân thiện. Giới thiệu top 2-3 địa điểm phù hợp nhất.",
193
+ "selected_place_ids": ["place_id_1", "place_id_2", "place_id_3"]
194
+ }}
195
+ ```
196
+
197
+ Chỉ chọn những place_id xuất hiện trong kết quả tìm kiếm ở trên. Nếu không có địa điểm phù hợp, để mảng rỗng.
198
+ Nếu có lịch sử hội thoại, hãy cân nhắc ngữ cảnh trước đó khi trả lời."""
199
+
200
+
201
+ def build_reasoning_prompt(
202
+ query: str,
203
+ context_summary: str | None = None,
204
+ previous_steps: list[dict] | None = None,
205
+ image_url: str | None = None,
206
+ ) -> str:
207
+ """
208
+ Build prompt for ReAct reasoning step.
209
+
210
+ Args:
211
+ query: User's query
212
+ context_summary: Summary of context gathered so far
213
+ previous_steps: List of previous reasoning steps
214
+ image_url: Optional image URL if user provided image
215
+
216
+ Returns:
217
+ Formatted prompt string
218
+ """
219
+ # Context section
220
+ context_text = ""
221
+ if context_summary:
222
+ context_text = f"\n{context_summary}\n"
223
+
224
+ # Previous steps section
225
+ steps_text = ""
226
+ if previous_steps:
227
+ steps_text = "\nKết quả từ các bước trước:\n"
228
+ for step in previous_steps:
229
+ tool = step.get("action", "N/A")
230
+ observation = step.get("observation", None)
231
+ steps_text += f"- {TOOL_PURPOSES.get(tool, tool)}: "
232
+ if observation:
233
+ result_count = len(observation) if isinstance(observation, list) else 1
234
+ steps_text += f"✅ {result_count} items\n"
235
+ else:
236
+ steps_text += "❌ Không có kết quả\n"
237
+
238
+ steps_text += "\n**⚠️ QUAN TRỌNG:** Nếu đã có đủ thông tin từ các bước trên → action = 'finish'\n"
239
+ else:
240
+ steps_text = "\nChưa có kết quả từ các tools trước đó.\n"
241
+
242
+ # Image context
243
+ image_text = ""
244
+ if image_url:
245
+ image_text = "\n**Lưu ý:** User đã gửi kèm ảnh. Có thể dùng retrieve_similar_visuals nếu cần.\n"
246
+
247
+ return f"""**Câu hỏi của user:** {query}
248
+ {image_text}
249
+ {context_text}
250
+ {steps_text}
251
+ **Bước tiếp theo là gì?**
252
+
253
+ Trả lời theo format JSON:
254
+ ```json
255
+ {{
256
+ "thought": "...",
257
+ "action": "tool_name hoặc finish",
258
+ "action_input": {{...}}
259
+ }}
260
+ ```"""
261
+
262
+
263
+
264
+ # =============================================================================
265
+ # TOOL DEFINITIONS FOR MCP TOOLS
266
+ # =============================================================================
267
+
268
+ FIND_NEARBY_PLACES_TOOL = {
269
+ "name": "find_nearby_places",
270
+ "description": """Tìm địa điểm gần một vị trí hoặc lấy chi tiết địa điểm.
271
+
272
+ Dùng khi:
273
+ - Người dùng hỏi về vị trí, khoảng cách, "gần đây", "gần X"
274
+ - Cần tìm quán xung quanh một landmark (Cầu Rồng, Mỹ Khê, Bà Nà)
275
+ - Lấy chi tiết đầy đủ về một địa điểm cụ thể
276
+
277
+ Categories: Restaurant, Coffee shop, Cafe, Bar, Hotel, Seafood restaurant,
278
+ Japanese restaurant, Korean restaurant, Gym, Fitness center, v.v.""",
279
+ "parameters": {
280
+ "location": "Tên địa điểm trung tâm (VD: 'Bãi biển Mỹ Khê', 'Cầu Rồng')",
281
+ "category": "Loại địa điểm: restaurant, coffee, hotel, bar, seafood, gym, etc.",
282
+ "max_distance_km": "Khoảng cách tối đa tính theo km (mặc định 5)",
283
+ "limit": "Số kết quả tối đa (mặc định 10)",
284
+ },
285
+ }
286
+
287
+
288
+ RETRIEVE_CONTEXT_TEXT_TOOL = {
289
+ "name": "retrieve_context_text",
290
+ "description": """Tìm kiếm thông tin địa điểm dựa trên văn bản, mô tả, đánh giá.
291
+
292
+ Dùng khi:
293
+ - Người dùng hỏi về menu, review, mô tả địa điểm
294
+ - Tìm kiếm theo đặc điểm: "quán cafe view đẹp", "phở ngon giá rẻ"
295
+ - Tìm theo không khí: "nơi lãng mạn", "chỗ yên tĩnh làm việc"
296
+
297
+ Hỗ trợ: Vietnamese + English""",
298
+ "parameters": {
299
+ "query": "Câu query tìm kiếm tự nhiên (VD: 'quán phở nước dùng đậm đà')",
300
+ "limit": "Số kết quả tối đa (mặc định 10)",
301
+ },
302
+ }
303
+
304
+
305
+ RETRIEVE_SIMILAR_VISUALS_TOOL = {
306
+ "name": "retrieve_similar_visuals",
307
+ "description": """Tìm địa điểm có hình ảnh tương tự.
308
+
309
+ Dùng khi:
310
+ - Người dùng gửi ảnh và muốn tìm nơi tương tự
311
+ - Mô tả về không gian, decor, view
312
+ - "Tìm quán có view như này", "Nơi nào có không gian giống ảnh này"
313
+ """,
314
+ "parameters": {
315
+ "image_url": "URL của ảnh cần tìm kiếm tương tự",
316
+ "limit": "Số kết quả tối đa (mặc định 10)",
317
+ },
318
+ }
319
+
320
+
321
+ SEARCH_SOCIAL_MEDIA_TOOL = {
322
+ "name": "search_social_media",
323
+ "description": """Tìm kiếm review, tin tức, trend trên mạng xã hội.
324
+
325
+ Dùng khi:
326
+ - Hỏi về "review", "tin hot", "trend", "viral"
327
+ - Tìm thông tin từ TikTok, Facebook, Reddit
328
+ - "Review quán ăn ngon Đà Nẵng trên TikTok"
329
+ """,
330
+ "parameters": {
331
+ "query": "Câu query tìm kiếm",
332
+ "freshness": "pw: tuần qua, pm: tháng qua (mặc định: pw)",
333
+ "platforms": "Danh sách platform: tiktok, facebook, reddit",
334
+ },
335
+ }
336
+
337
+
338
+ # =============================================================================
339
+ # DATABASE CONSTANTS (Categories, Keywords mapping)
340
+ # =============================================================================
341
+
342
+ # Available categories in Neo4j database
343
+ AVAILABLE_CATEGORIES = [
344
+ "Asian restaurant", "Athletic club", "Badminton court", "Bakery", "Bar",
345
+ "Bistro", "Board game club", "Breakfast restaurant", "Cafe",
346
+ "Cantonese restaurant", "Chicken restaurant", "Chinese restaurant",
347
+ "Cocktail bar", "Coffee shop", "Country food restaurant", "Deli",
348
+ "Dessert shop", "Disco club", "Dumpling restaurant", "Espresso bar",
349
+ "Family restaurant", "Fine dining restaurant", "Fitness center",
350
+ "Food court", "French restaurant", "Game store", "Gym",
351
+ "Hamburger restaurant", "Holiday apartment rental", "Hot pot restaurant",
352
+ "Hotel", "Ice cream shop", "Indian restaurant", "Irish pub",
353
+ "Italian restaurant", "Izakaya restaurant", "Japanese restaurant",
354
+ "Korean barbecue restaurant", "Korean restaurant", "Live music bar",
355
+ "Malaysian restaurant", "Mexican restaurant", "Movie theater",
356
+ "Musical club", "Noodle shop", "Pho restaurant", "Pickleball court",
357
+ "Pizza restaurant", "Ramen restaurant", "Restaurant", "Restaurant or cafe",
358
+ "Rice cake shop", "Sandwich shop", "Seafood restaurant", "Soccer field",
359
+ "Soup shop", "Sports bar", "Sports club", "Sports complex", "Steak house",
360
+ "Sushi restaurant", "Takeout Restaurant", "Tennis court", "Tiffin center",
361
+ "Udon noodle restaurant", "Vegan restaurant", "Vegetarian restaurant",
362
+ "Vietnamese restaurant",
363
+ ]
364
+
365
+ # Category keywords for intent detection (user query -> category)
366
+ CATEGORY_KEYWORDS = {
367
+ 'cafe': ['cafe', 'cà phê', 'coffee', 'caphe', 'caphê'],
368
+ 'pho': ['phở', 'pho'],
369
+ 'banh_mi': ['bánh mì', 'banh mi', 'bread'],
370
+ 'seafood': ['hải sản', 'hai san', 'seafood', 'cá', 'tôm', 'cua'],
371
+ 'restaurant': ['nhà hàng', 'restaurant', 'quán ăn', 'ăn'],
372
+ 'bar': ['bar', 'pub', 'cocktail', 'beer', 'bia'],
373
+ 'hotel': ['hotel', 'khách sạn', 'resort', 'villa'],
374
+ 'japanese': ['nhật', 'japan', 'sushi', 'ramen'],
375
+ 'korean': ['hàn', 'korea', 'bbq'],
376
+ }
377
+
378
+ # Category keyword -> Database category name mapping
379
+ CATEGORY_TO_DB = {
380
+ 'cafe': ['Coffee shop', 'Cafe', 'Coffee house', 'Espresso bar'],
381
+ 'pho': ['Pho restaurant', 'Bistro', 'Restaurant', 'Vietnamese restaurant'],
382
+ 'banh_mi': ['Bakery', 'Tiffin center', 'Restaurant'],
383
+ 'seafood': ['Seafood restaurant', 'Restaurant', 'Asian restaurant'],
384
+ 'restaurant': ['Restaurant', 'Vietnamese restaurant', 'Asian restaurant'],
385
+ 'bar': ['Bar', 'Cocktail bar', 'Pub', 'Night club', 'Live music bar'],
386
+ 'hotel': ['Hotel', 'Resort', 'Apartment', 'Villa', 'Holiday apartment rental'],
387
+ 'japanese': ['Japanese restaurant', 'Sushi restaurant', 'Ramen restaurant'],
388
+ 'korean': ['Korean restaurant', 'Korean barbecue restaurant'],
389
+ }
390
+
391
+
392
+ # =============================================================================
393
+ # EXPORTS
394
+ # =============================================================================
395
+
396
+ __all__ = [
397
+ # System prompts
398
+ "MMCA_SYSTEM_PROMPT",
399
+ "REACT_SYSTEM_PROMPT",
400
+ "GREETING_SYSTEM_PROMPT",
401
+ "SYNTHESIS_SYSTEM_PROMPT",
402
+ # Tool definitions
403
+ "TOOL_DEFINITIONS",
404
+ "TOOL_PURPOSES",
405
+ # Tool-specific definitions
406
+ "FIND_NEARBY_PLACES_TOOL",
407
+ "RETRIEVE_CONTEXT_TEXT_TOOL",
408
+ "RETRIEVE_SIMILAR_VISUALS_TOOL",
409
+ "SEARCH_SOCIAL_MEDIA_TOOL",
410
+ # Database constants
411
+ "AVAILABLE_CATEGORIES",
412
+ "CATEGORY_KEYWORDS",
413
+ "CATEGORY_TO_DB",
414
+ # Prompt builders
415
+ "build_greeting_prompt",
416
+ "build_synthesis_prompt",
417
+ "build_reasoning_prompt",
418
+ ]