Spaces:
Sleeping
Sleeping
import os | |
import google.generativeai as genai | |
import pandas as pd | |
from google.generativeai.types import StopCandidateException | |
import uuid | |
from langfuse import observe, get_client | |
# import asyncio | |
from .tools import ( | |
search_general_knowledge, | |
query_customer_policy, | |
get_new_customer_recommendation, | |
recommend_for_existing_customer | |
) | |
class RabbitLifeAgent: | |
def __init__(self): | |
self.session_id = str(uuid.uuid4()) | |
# print(f"Session ID: {self.session_id}") | |
self.current_customer_context: pd.DataFrame | None = None | |
# --- tools --- | |
self.tools = [ | |
search_general_knowledge, | |
query_customer_policy, | |
get_new_customer_recommendation, | |
recommend_for_existing_customer, | |
] | |
self.model = genai.GenerativeModel( | |
model_name='gemini-2.5-flash', | |
tools=self.tools, | |
system_instruction=""" | |
**//-- Core Persona & Identity --//** | |
You are a smart, professional, and friendly AI assistant from "Rabbit Life" insurance. | |
- **Language:** You MUST respond in Thai ONLY, using polite female particles like "ค่ะ" and "นะคะ". | |
- **Greeting:** When the user greets you for the first time, you MUST respond with: "สวัสดีค่ะ ให้ Rabbit Life ช่วยดูแลคุณนะคะ เราเป็นผู้ช่วย AI ที่พร้อมให้ข้อมูลผลิตภัณฑ์เพื่อช่วยคุณมองหาแผนประกันที่ใช่ ไม่ทราบว่าสนใจสอบถามข้อมูลด้านใดเป็นพิเศษคะ" | |
- **Slogan:** You must embody the company's slogan, "ให้ Rabbit Life ช่วยดูแลคุณ" (Let Rabbit Life take care of you). Weave this sentiment into your caring and helpful responses, especially at the end of a successful interaction. | |
- **Pronouns:** Refer to yourself as "เรา" or "ทางเรา". Strictly avoid "ดิฉัน" and "ฉัน". | |
**//-- Formatting Rules (CRITICAL) --//** | |
- **Use Bullet Points:** You MUST use Markdown bullet points (using `•` or `-`) to present lists of features, benefits, products, or any series of items. This is essential for readability. | |
- **Clarity & Tone:** Use clear headings (e.g., **หัวข้อความคุ้มครองหลัก:**) and a caring closing statement. | |
**//-- Primary Directive: Use Your Tools Efficiently --//** | |
Your main goal is to understand the user's need and immediately use the appropriate tool. | |
- **Be Direct:** If a question matches a tool, use it. Do not ask unnecessary clarifying questions. | |
- **Conversational Tone:** For greetings, thanks, or small talk not covered by a tool, maintain a friendly and helpful conversation. | |
**//-- Authoritative Knowledge Scope (Your Product List) --//** | |
You have information about these Rabbit Life products. Recognize them when a user mentions them. | |
- **ประกันคุ้มครองชีวิต:** Chai Leoy 99/5, Chai Leoy 99/10, Chai Leoy 99/20, High Protect 3/3, Smart Term Bronze 5, Smart Term Bronze 10, Smart Wellness 90/15 | |
- **ประกันเพื่อการลงทุน:** Jai Jai 15/6, Jai Jai 12/6, Jai Jai 25/9, Sabai Jai 14/5, Protection Plus 18/9 | |
- **ประกันสุขภาพ:** Health Protect, Health Smile, Worry Free Cancer, HIB 365, OPD, Mental Health, Worry Free 50 Critical Illness | |
- **ประกันอุบัติเหตุ:** PA MAX, PA PROMPT, ADD, ADB | |
**//-- Tool Usage Guide (CRITICAL) --//** | |
- **`search_general_knowledge`:** Use for questions about products in the list above or general insurance topics (e.g., "how to claim?", "tax deduction?"). | |
- **`query_customer_policy`:** | |
- **Trigger:** Use when a user wants to check their own policy information (e.g., "check my policy status", "what is my premium?"). | |
- **Action:** This tool requires a `customer_identifier`. If the user has not provided it, your job is to ask for it. | |
- **Identifier Question Rule (IMPORTANT):** When you need to ask for the identifier, you MUST ask for ONLY "ชื่อ-นามสกุล" (full name) OR "เลขบัตรประชาชน 13 หลัก" (13-digit national ID). Do not ask for other information like policy number. | |
- **Example of a correct identifier question:** "ได้เลยค่ะ เพื่อตรวจสอบข้อมูล รบกวนขอชื่อ-นามสกุล หรือเลขบัตรประชาชน 13 หลักได้ไหมคะ" | |
- **`get_new_customer_recommendation`:** | |
- **Trigger:** Use for new users asking for a recommendation (e.g., "recommend a plan for me", "I want to buy insurance"). | |
- **Action:** You MUST ask for `age`, `gender`, and `salary` one by one if they are missing. | |
- **Salary Question Rule (IMPORTANT):** When you need to ask for the user's salary, your question MUST specifically ask for **"รายได้ต่อเดือน" (monthly income)**. Do not ask for yearly income. | |
- **Example of a correct salary question:** "สุดท้ายนี้ ขออนุญาตสอบถามรายได้ต่อเดือนเพื่อประกอบการแนะนำได้ไหมคะ" | |
- **`recommend_for_existing_customer`:** | |
- **Trigger:** Use for a KNOWN, REMEMBERED customer asking for a recommendation. | |
- **Action:** If the customer specifies an interest (e.g., "add accident insurance", "what about investment plans?"), pass this interest into the `interest` parameter. Otherwise, you can call the tool without parameters. | |
""", | |
generation_config={"temperature": 0.0} | |
) | |
self.chat = self.model.start_chat(history=[]) # , metadata={"session_id": self.session_id}) # self.chat = self.model.start_chat() | |
print("✅ Agent is ready.") | |
def set_customer_context(self, customer_df: pd.DataFrame): | |
self.current_customer_context = customer_df | |
# if customer_df is not None and not customer_df.empty: | |
# customer_name = f"{customer_df.iloc[0].get('insured_firstname', '')} {customer_df.iloc[0].get('insured_lastname', '')}".strip() | |
def get_customer_context(self) -> pd.DataFrame | None: | |
return self.current_customer_context | |
def start_chat_session(self, user_input: str) -> str: | |
langfuse = get_client() | |
langfuse.update_current_trace(session_id=self.session_id) | |
if not user_input: | |
return "กรุณาพิมพ์ข้อความที่ต้องการสอบถามค่ะ" | |
try: | |
response = self.chat.send_message(user_input) | |
# print(f"==========") | |
# print(f"response: {response}") | |
# print(f"==========") | |
# --- Loop สำหรับจัดการ Tool Calling จะทำงานถ้า Gemini ตัดสินใจว่าต้องเรียกใช้เครื่องมือ | |
while response.candidates and response.candidates[0].content.parts[0].function_call: | |
function_call = response.candidates[0].content.parts[0].function_call | |
tool_name = function_call.name | |
tool_args = {key: value for key, value in function_call.args.items()} | |
# print(f"🔩 LLM wants to call Tool: {tool_name} with args: {tool_args}") | |
# หาฟังก์ชันของ tool จากชื่อ | |
tool_map = {t.__name__: t for t in self.tools} | |
tool_function = tool_map.get(tool_name) | |
if tool_function: | |
# ใส่ agent_instance เข้าไปใน arguments เพื่อให้ tool เข้าถึง context ได้ | |
tool_args['agent_instance'] = self | |
# เรียกใช้ฟังก์ชัน tool และแปลงผลลัพธ์เป็น string | |
tool_response_content = str(tool_function(**tool_args)) | |
# print(f"🔧 Tool Response: {tool_response_content[:200]}...") | |
# ส่งผลลัพธ์จาก tool กลับไปให้ Gemini เพื่อสรุปเป็นคำตอบ | |
response = self.chat.send_message( | |
[{"function_response": { | |
"name": tool_name, | |
"response": {"content": tool_response_content} | |
}}] | |
) | |
else: | |
# กรณีที่ LLM เรียก tool ที่ไม่มี | |
error_msg = f"ขออภัยค่ะ ไม่พบเครื่องมือ {tool_name}" | |
print(f"❌ Error: {error_msg}") | |
return error_msg | |
# --- final response --- | |
ai_response = response.text | |
ai_response = ai_response.replace("ดิฉัน", "เรา").replace("ฉัน", "เรา") | |
# print(f"🤖 AI: {ai_response}") | |
return ai_response | |
except StopCandidateException as e: | |
error_msg = f"‼️ API Error Detected: {e.finish_reason}. Responding gracefully." | |
print(error_msg) # หรือใช้ logging.error(error_msg) | |
# ตรวจสอบสาเหตุของ Error และ return คำตอบที่เหมาะสมออกไปเลย | |
if e.finish_reason == "MALFORMED_FUNCTION_CALL": | |
return "ขออภัยค่ะ ดูเหมือนจะมีปัญหาในการประมวลผลคำถาม รบกวนลองถามอีกครั้งด้วยประโยคที่สมบูรณ์ขึ้นได้ไหมคะ" | |
else: | |
# สำหรับ Error อื่นๆ เช่น SAFETY, RECITATION etc. | |
return "ขออภัยค่ะ เกิดข้อผิดพลาดในการสื่อสารกับระบบ โปรดลองอีกครั้งค่ะ" | |
# --- For checking chat history --- | |
def view_flow_history(self): | |
if not self.chat.history: | |
print("History is empty.") | |
else: | |
# [print(f"[{m.role.upper()}]") or [print(p) for p in m.parts] for m in self.chat.history] | |
return self.chat.history | |
# # --- | |
# agent = RabbitLifeAgent() | |
# async def chat_wrapper(user_input: str) -> str: | |
# return await agent.start_chat_session(user_input) | |
# if __name__ == '__main__': | |
# agent = RabbitLifeAgent() | |
# # --- เริ่ม Session การแชทใน Terminal --- | |
# print("-" * 50) | |
# print("🤖 สวัสดีค่ะ ให้ Rabbit Life ช่วยดูแลนะคะ (พิมพ์ 'exit' เพื่อจบการสนทนา)") | |
# print("-" * 50) | |
# while True: | |
# try: | |
# user_input = input("👤 คุณ: ") | |
# if user_input.lower() == 'exit': | |
# print("ขอบคุณที่ใช้บริการนะคะ สวัสดีค่ะ") | |
# break | |
# if not user_input: | |
# continue | |
# ai_response = agent.start_chat_session(user_input) | |
# # print(f"🤖 AI: {ai_response}") | |
# except (KeyboardInterrupt, EOFError): # ดัก Ctrl+C หรือ Ctrl+D | |
# print("\nขอบคุณที่ใช้บริการนะคะ สวัสดีค่ะ") | |
# break | |
# except Exception as e: | |
# print(f"เกิดข้อผิดพลาดร้ายแรง: {e}") | |
# import traceback | |
# traceback.print_exc() | |
# break |