Spaces:
Sleeping
Sleeping
File size: 12,900 Bytes
30adccc |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 |
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.")
@observe(name="customer_context")
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()
@observe(name="get_customer_context")
def get_customer_context(self) -> pd.DataFrame | None:
return self.current_customer_context
@observe(name="chat")
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 |