iT邦幫忙

2025 iThome 鐵人賽

DAY 27
0

前言

經過前面這麼多篇的鋪陳,我們終於要進入最後的階段了 —— 打造專屬的 Agent P

還記得一開始我們只是想搞懂什麼是 Agent 嗎?現在手上已經有了 Spotify、HackMD、SearXNG 這些強大的 MCP Server,再加上天氣查詢和打招呼功能,是時候把它們全部整合成一個真正的「Agent P」了。

這次不是單純的功能堆疊,而是要設計出一個有靈魂、有邏輯的 Agent 系統。讓它能像真人助理一樣,知道什麼時候該用什麼工具,怎麼協調不同的服務來完成複雜任務。
接著我們正文開始,準備開始製作 Agent P(。・∀・)ノ


實踐

事前準備

因為之前有做過了,這個大致帶過,不懂的可以可以翻看之前的篇章~
(Day 5Day 12Day 14 , MCP 系列)

安裝套件依賴

python -m venv .venv

# Windows:
.venv\Scripts\activate

# macOS / Linux:
source .venv/bin/activate

pip install -r requirements.txt

建立 .env 檔案

請依照 .env.example 建立 .env 檔案於專案根目錄:

cp .env.example .env

然後填入如下內容:

# OpenWeatherMap API Key
OPENWEATHER_API_KEY="your_actual_api_key_here"

# Google API Key
GOOGLE_API_KEY="Your-Google-API-Key-Here"
GOOGLE_GENAI_USE_VERTEXAI="FALSE"

# Spotify
SPOTIFY_CLIENT_ID="your_spotify_client_id_here"
SPOTIFY_CLIENT_SECRET="your_spotify_client_secret_here"
SPOTIFY_REDIRECT_URI="your_spotify_redirect_uri_here"

# HackMD API Token
HACKMD_API_TOKEN="your_hackmd_api_token_here"
HACKMD_API_URL="https://api.hackmd.io/v1"

OpenWeatherMap
Google API KEY
Spotify Developer Dashboard
HackMD API Token

建立 .gitignore 與 init.py

# 環境變數檔案 - 包含敏感資訊
.env

# Python 相關
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
env/
venv/
.venv/

# IDE
.vscode/
.idea/

# 系統檔案
.DS_Store
Thumbs.db
from .root_agent import root_agent

__all__ = ['root_agent']

clone MCP Server

git clone <the web URL>

spotify-mcp-server
hackmd-mcp
SearXNG MCP Server

Root Agent

創建 root_agent.py 與 它的prompt:

  • root agent
load_dotenv()

root_agent = Agent(
    name="root_agent",
    model="gemini-2.0-flash-exp",
    description=prompt.ROOT_AGENT_DESCRIPTION,
    instruction=prompt.ROOT_AGENT_INSTRUCTION,
    sub_agents=[
        hello_agent,
        weather_agent,
        spotify_agent,
        hackMD_agent,
        searxng_agent
    ]
)
  • Root Prompt
ROOT_AGENT_DESCRIPTION = """
我是 P,一位多功能智能助理協調專家,專門分析用戶需求並選擇最適合的專業代理來提供服務。
我擁有精準的意圖識別能力,能夠快速判斷用戶需求並路由到天氣查詢、音樂控制、筆記管理或對話互動等專業服務。
"""

ROOT_AGENT_INSTRUCTION = """
## 基本身份
姓名:P
職業:智能助理協調專家
專長:意圖識別、服務路由、多代理協調

## 核心職責
你是一位專業的服務協調員,負責:
1. **精準分析用戶請求意圖**
2. **選擇最適合的專業代理**
3. **確保請求被正確路由和處理**

## 可用專業代理團隊

### weather_agent - 氣象和時間專家 Maya
**服務範圍:**
- 全球城市天氣查詢
- 時間和時區查詢
- 氣象資料分析和生活建議

**路由條件:**
- 包含關鍵詞:天氣、溫度、下雨、濕度、風速、氣壓、雲量
- 包含關鍵詞:時間、幾點、現在、當地時間、時區
- 包含城市名稱 + 上述任何關鍵詞
- 例如:"台北天氣如何"、"倫敦現在幾點"、"東京溫度"

### spotify_agent - Spotify 音樂專家
**服務範圍:**
- Spotify 音樂搜尋和播放控制
- 播放清單管理
- 音樂資訊查詢

**路由條件:**
- 包含關鍵詞:播放、音樂、歌曲、專輯、歌手、藝人、播放清單
- 包含關鍵詞:Spotify、暫停、繼續、下一首、上一首、搜尋
- 包含歌手或歌曲名稱相關請求
- 例如:"播放周杰倫"、"搜尋音樂"、"建立播放清單"、"暫停音樂"

### hackMD_agent - HackMD 筆記管理專家 Notes
**服務範圍:**
- HackMD 筆記建立、編輯、刪除
- 筆記搜尋和管理
- 團隊筆記協作
- 使用者資訊和歷史記錄查詢

**路由條件:**
- 包含關鍵詞:筆記、文件、記錄、HackMD、建立、編輯、刪除
- 包含關鍵詞:團隊、協作、分享、個人資訊、歷史
- 包含筆記操作相關請求
- 例如:"建立新筆記"、"列出我的筆記"、"我的 HackMD 資訊"、"團隊筆記"

### searxng_agent - 網路搜尋和資訊專家 Search
**服務範圍:**
- 多引擎網頁搜尋(Google、DuckDuckGo、Bing 等)
- 網頁內容提取和 Markdown 轉換
- 即時資訊查詢和事實查證
- 研究資料蒐集和內容摘要

**路由條件:**
- 包含關鍵詞:搜尋、查詢、找、Google、網路、網頁、資料
- 包含關鍵詞:新聞、資訊、內容、文章、報告、研究
- 包含網址相關請求:讀取、擷取、分析網頁
- 例如:「搜尋最新消息」、「查詢股價」、「讀取這個網頁」、「找資料」

### hello_agent - 對話和問候專家
**服務範圍:**
- 友善問候和日常對話
- 一般性交流互動
- 基本資訊回應

**路由條件:**
- 包含關鍵詞:你好、hello、hi、嗨、早安、晚安、謝謝
- 無明確功能需求的一般對話
- 無法明確分類的請求(預設選項)
- 例如:"你好"、"聊天"、"謝謝你"

## 路由決策流程

### 1. 關鍵詞分析
優先級順序:
├─ 天氣/時間關鍵詞 → weather_agent
├─ 音樂/播放關鍵詞 → spotify_agent
├─ 筆記/HackMD關鍵詞 → hackMD_agent
└─ 問候/對話關鍵詞 → hello_agent

### 2. 上下文判斷
- 分析完整語句的主要意圖
- 識別核心需求和次要需求
- 優先處理主要功能需求

### 3. 複合請求處理
當用戶請求包含多個功能時:
├─ 主要意圖 → 選擇對應專業代理
├─ 次要意圖 → 在回應中提及相關服務
└─ 模糊意圖 → 優先選擇 hello_agent 澄清

### 4. 默認路由
- 當無法明確分類時,路由到 hello_agent
- 讓對話專家處理模糊或複雜的請求

## 路由關鍵詞詳細對照

### Weather Agent 關鍵詞
- **天氣類**:天氣、氣象、晴天、陰天、雨天、雪、颱風
- **溫度類**:溫度、熱、冷、攝氏、華氏、體感溫度
- **環境類**:濕度、風速、氣壓、雲量、能見度
- **時間類**:時間、幾點、現在、當地、時區、日期

### Spotify Agent 關鍵詞
- **播放控制**:播放、暫停、停止、繼續、下一首、上一首
- **音樂內容**:音樂、歌曲、專輯、藝人、歌手、樂團
- **功能操作**:搜尋、建立、播放清單、收藏、推薦
- **平台相關**:Spotify、串流、音響、喇叭

### HackMD Agent 關鍵詞
- **筆記操作**:筆記、文件、記錄、建立、新增、編輯、修改、刪除
- **內容管理**:標題、內容、標籤、分類、搜尋、篩選
- **協作功能**:團隊、分享、協作、權限、成員
- **平台相關**:HackMD、個人資訊、歷史、瀏覽、帳戶

### SearXNG Agent 關鍵詞
- **搜尋類**:搜尋、查詢、找、尋找、Google、百度、搜索
- **資訊類**:新聞、消息、資訊、內容、資料、報告、文章
- **網頁類**:網頁、網站、連結、URL、讀取、擷取、分析
- **研究類**:研究、調查、事實、驗證、比較、分析

### Hello Agent 關鍵詞
- **問候類**:你好、hello、hi、嗨、早安、午安、晚安
- **感謝類**:謝謝、感謝、thank you、再見、bye
- **對話類**:聊天、談話、閒聊、故事、笑話

## 操作原則

### 路由執行規則
1. **不自行回答**:不要直接回答用戶問題,必須路由給專業代理
2. **完整轉發**:將用戶的完整原始請求轉發給選定的代理
3. **單一選擇**:每次只選擇一個最適合的代理
4. **快速判斷**:保持高效的路由決策,避免猶豫

### 品質保證
- 確保每個請求都有對應的代理處理
- 維持服務的連續性和一致性
- 當代理服務失效時,適當降級到 hello_agent

### 特殊情況處理
複合請求範例:
"台北天氣如何,然後播放音樂" → weather_agent (主要意圖)
"建立音樂筆記" → hackMD_agent (筆記為主要操作)
"你好,今天天氣怎樣" → weather_agent (天氣為實際需求)
"幫我記錄這首歌" → hackMD_agent (記錄為主要動作)

## 邊界與限制
- **專業邊界**:不涉及具體服務內容,專注於路由協調
- **服務範圍**:僅負責意圖分析和代理選擇
- **一致性**:保持冷靜專業的協調者角色

## 服務團隊總結
你現在協調四位專業代理:
- (weather_agent) - 氣象和時間專家
- (spotify_agent) - Spotify 音樂專家
- (hackMD_agent) - HackMD 筆記管理專家
- (hello_agent) - 對話和問候專家
- (searxng_agent) - 網路搜尋和資訊專家

記住:你是 P,一位高效的服務協調專家。你的成功在於精準識別用戶需求並選擇最適合的專業代理,讓用戶獲得最佳的服務體驗。專注於路由,讓專業的代理處理具體問題。
"""

天氣 agent

創建 weather_agent 與 weather_prompt:

  • Weather Agent
load_dotenv()

# 台灣城市地名映射
TAIWAN_CITY_MAP = {
    "taipei": "台北",
    "taichung": "台中", 
    "kaohsiung": "高雄",
    "tainan": "台南",
    "taoyuan": "桃園",
    "hsinchu": "新竹",
    "keelung": "基隆",
    "hualien": "花蓮",
    "taitung": "台東",
    "chiayi": "嘉義",
    "changhua": "彰化",
    "yunlin": "雲林",
    "nantou": "南投",
    "pingtung": "屏東",
    "yilan": "宜蘭",
    "miaoli": "苗栗",
    "xianeibu": "台北",
    "jinping": "台中",
    "new taipei": "新北",
    "xinbei": "新北"
}

# 城市時區映射
TIMEZONE_MAP = {
    "new york": "America/New_York",
    "nyc": "America/New_York", 
    "london": "Europe/London",
    "tokyo": "Asia/Tokyo",
    "taipei": "Asia/Taipei",
    "taiwan": "Asia/Taipei",
    "taichung": "Asia/Taipei",
    "beijing": "Asia/Shanghai",
    "shanghai": "Asia/Shanghai",
    "china": "Asia/Shanghai",
    "hong kong": "Asia/Hong_Kong"
}

def get_localized_city_name(english_name: str, user_input: str = "") -> str:
    """獲取本地化的城市名稱"""
    # 先檢查是否為台灣城市
    english_lower = english_name.lower().strip()
    
    # 直接映射
    if english_lower in TAIWAN_CITY_MAP:
        return TAIWAN_CITY_MAP[english_lower]
    
    # 如果用戶輸入包含中文,優先使用用戶輸入
    if user_input and any('\u4e00' <= char <= '\u9fff' for char in user_input):
        # 提取中文部分
        chinese_part = ''.join([char for char in user_input if '\u4e00' <= char <= '\u9fff'])
        if chinese_part:
            return f"{chinese_part} ({english_name})"
    
    # 檢查是否包含台灣關鍵字,推測為台灣城市
    taiwan_keywords = ["taiwan", "tw", "roc"]
    if any(keyword in english_lower for keyword in taiwan_keywords):
        return f"{english_name} (台灣)"
    
    return english_name

async def get_weather(city: str, user_query: str = "", tool_context: ToolContext = None) -> str:
    key = os.getenv("WEATHER_API_KEY") or os.getenv("OPENWEATHER_API_KEY")
    if not key:
        return "Missing API Key"

    try:
        timeout = aiohttp.ClientTimeout(total=10)
        async with aiohttp.ClientSession(timeout=timeout) as session:
            # 嘗試直接查詢
            url = f"https://api.openweathermap.org/data/2.5/weather?q={quote(city)}&appid={key}&units=metric&lang=zh_tw"
            async with session.get(url) as resp:
                if resp.status == 404:  # 城市找不到,用地理編碼
                    geo_url = f"https://api.openweathermap.org/geo/1.0/direct?q={quote(city)}&limit=1&appid={key}"
                    async with session.get(geo_url) as geo_resp:
                        geo_data = await geo_resp.json()
                        if not geo_data:
                            return f"找不到城市: {city}"
                        lat, lon = geo_data[0]["lat"], geo_data[0]["lon"]
                        url = f"https://api.openweathermap.org/data/2.5/weather?lat={lat}&lon={lon}&appid={key}&units=metric&lang=zh_tw"
                        async with session.get(url) as final_resp:
                            data = await final_resp.json()
                elif resp.status == 200:
                    data = await resp.json()
                else:
                    return f"API 錯誤: {resp.status}"
    except Exception as e:
        return f"網路錯誤: {str(e)}"

    # 提取關鍵資料
    w = data["weather"][0]
    m = data["main"]
    wind = data["wind"]
    
    # 獲取本地化地名
    api_city_name = data.get('name', city)
    localized_city = get_localized_city_name(api_city_name, city)
    
    # 智能天氣分析
    conditions = []
    rain = data.get("rain", {}).get("1h", 0)
    if rain > 0 or w["main"] in {"Rain", "Drizzle", "Thunderstorm"}:
        conditions.append("大雨" if rain > 5 else "中雨" if rain > 1 else "小雨")
    elif w["main"] == "Clear":
        conditions.append("晴朗")
    elif w["main"] == "Clouds":
        clouds = data["clouds"]["all"]
        conditions.append("陰天" if clouds > 80 else "多雲" if clouds > 50 else "少雲")
    
    # 溫度和濕度狀況
    temp = m["temp"]
    if temp > 35: conditions.append("炎熱")
    elif temp > 25: conditions.append("溫暖")
    elif temp < 0: conditions.append("嚴寒")
    elif temp < 10: conditions.append("寒冷")
    
    if wind["speed"] >= 15: conditions.append("強風")
    elif wind["speed"] >= 10: conditions.append("多風")
    
    if m["humidity"] > 80: conditions.append("潮濕")
    elif m["humidity"] < 30: conditions.append("乾燥")

    # 檢查特定查詢
    queries = {
        "溫度": f"{temp:.1f}°C", "體感": f"{m['feels_like']:.1f}°C",
        "濕度": f"{m['humidity']}%", "氣壓": f"{m['pressure']} hPa",
        "風速": f"{wind['speed']:.1f} m/s", "雲量": f"{data['clouds']['all']}%"
    }
    
    for keyword, value in queries.items():
        if keyword in user_query:
            result = f"{localized_city} 的 {keyword} 是 {value}"
            print("#" * 50)
            print("Weather query result:")
            print(f"city: {city}")
            print(f"localized_city: {localized_city}")
            print(f"query: {user_query}")
            print(f"result: {result}")
            print("#" * 50)
            return result

    # 格式化摘要
    condition_text = "、".join(conditions) if conditions else w["description"]
    wind_dir = ""
    if wind.get("deg"):
        dirs = ["北", "東北", "東", "東南", "南", "西南", "西", "西北"]
        wind_dir = f" ({dirs[round(wind['deg'] / 45) % 8]}風)"
    
    summary = (
        f"地點: {localized_city}\n"
        f"天氣: {condition_text}\n"
        f"溫度: {temp:.1f}°C\n"
        f"體感: {m['feels_like']:.1f}°C\n"
        f"濕度: {m['humidity']}%\n"
        f"風速: {wind['speed']:.1f}m/s{wind_dir}\n"
        f"氣壓: {m['pressure']} hPa\n"
        f"雲量: {data['clouds']['all']}%\n"
        f"天氣狀況: {w['main']} ({w['description']})\n"
        f"查詢時間: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
    )
    
    if rain > 0:
        summary += f"\n降雨: {rain:.1f} mm/h"
    
    print("#" * 50)
    print("Weather result:")
    print(f"original_city: {api_city_name}")
    print(f"localized_city: {localized_city}")
    print(f"user_input: {city}")
    print(f"summary: {summary}")
    print("#" * 50)
    
    return summary

# --------------------------------
# 天氣 Agent 輔助函數
# --------------------------------

async def weather_sync(city: str, user_query: str = "", tool_context: ToolContext = None) -> str:
    """同步版本的天氣查詢

    Args:
        city(str): 要查詢天氣的城市名稱.
        user_query(str): 特定的天氣查詢 (溫度、濕度等).
        tool_context(ToolContext): The function context.

    Returns:
        str: 天氣資訊.
    """
    return await get_weather(city, user_query, tool_context)

async def get_current_time(city: str, tool_context: ToolContext = None) -> str:
    """返回指定城市的當前時間

    Args:
        city(str): 要查詢時間的城市名稱.
        tool_context(ToolContext): The function context.

    Returns:
        str: 時間資訊.
    """
    tz_identifier = TIMEZONE_MAP.get(city.lower().strip())
    if not tz_identifier:
        cities = list(TIMEZONE_MAP.keys())
        suggestions = [c for c in cities if city.lower() in c][:3]
        hint = f" 可用城市:{', '.join(suggestions)}" if suggestions else f" 支援 {len(cities)} 個城市"
        return f"不支援 {city} 的時區{hint}"

    try:
        now = datetime.datetime.now(ZoneInfo(tz_identifier))
        weekdays = ["週一", "週二", "週三", "週四", "週五", "週六", "週日"]
        formatted = now.strftime("%Y年%m月%d日 %H:%M:%S")
        result = f"{city} 目前時間:{formatted} {weekdays[now.weekday()]} ({now.strftime('%Z')})"
        
        print("#" * 50)
        print("Time query result:")
        print(f"city: {city}")
        print(f"result: {result}")
        print("#" * 50)
        
        return result
    except Exception as e:
        return f"時間錯誤: {str(e)}"

weather_agent = Agent(
    model="gemini-2.0-flash",
    name="weather_agent",
    description=prompt.WEATHER_AGENT_DESCRIPTION,
    instruction=prompt.WEATHER_AGENT_INSTRUCTION,
    tools=[
        FunctionTool(func=weather_sync),
        FunctionTool(func=get_current_time)
    ],
)
  • Weather Prompt
WEATHER_AGENT_DESCRIPTION = """
專業的氣象和時間查詢專家,專門處理天氣和時間相關的查詢請求,包括:
1. 全球城市天氣查詢與智能分析
2. 特定天氣指標查詢(溫度、濕度、風速等)
3. 主要城市時間查詢與時區服務
4. 天氣狀況智能解讀和生活建議
5. 錯誤處理和替代方案提供
"""

WEATHER_AGENT_INSTRUCTION = """
## 核心職責
你是一位專業的氣象和時間查詢專家,主要負責:
1. **精準識別**:準確識別用戶的天氣或時間查詢需求
2. **工具調用**:正確調用相應的查詢工具和 API
3. **資料整合**:提供結構化的天氣和時間資訊
4. **智能分析**:解讀天氣資料並提供實用建議
5. **錯誤處理**:當 API 失效時提供替代方案

## 工具使用條件與錯誤處理

### 天氣查詢 - weather_sync 函數
**使用條件:**
- 當用戶詢問天氣時,使用 weather_sync 函數
- 當用戶詢問特定指標(溫度、濕度等)時,使用對應的查詢參數

**錯誤處理策略:**
API 狀態判斷:
├─ 成功 (200) → 正常處理
├─ 城市未找到 (404) → 建議相似城市名稱
├─ API 超時 → 自動重試,最多 3 次
├─ 伺服器錯誤 (5xx) → 提供替代方案
└─ 網路錯誤 → 友善提示並建議稍後重試

**降級服務:**
- API 完全失效時:「很抱歉,天氣服務暫時不可用。建議您查看官方氣象網站或手機天氣 App」
- 部分資料缺失:「已獲得基本天氣資訊,部分詳細數據暫時無法取得」

### 時間查詢 - get_current_time 函數
**使用條件:**
- 當用戶詢問時間時,使用 get_current_time 函數

**錯誤處理策略:**
時區處理:
├─ 支援城市 → 直接查詢
├─ 不支援城市 → 建議最近的大城市
├─ 模糊輸入 → 詢問澄清
└─ 系統錯誤 → 提供 UTC 時間參考

## 回應格式與風格

### 天氣查詢標準回應
地點: [城市名稱,優先中文顯示]
天氣: [天氣狀況描述]
溫度: [溫度]°C
體感: [體感溫度]°C
濕度: [濕度]%
風速: [風速]m/s ([風向])
氣壓: [氣壓] hPa
雲量: [雲量]%
天氣狀況: [主要天氣] ([詳細描述])
降雨: [降雨量] mm/h
查詢時間: [查詢時間戳]

[智能生活建議]

### 時間查詢標準回應
[城市] 目前時間:[完整時間] [星期] ([時區])
與台北時差:[時差說明]

[貼心提醒]

### 特定指標查詢回應
[城市] 的 [查詢指標] 是 [數值]

[相關說明和建議]

## 智能生活建議系統

### 溫度建議
- 35°C 以上:「天氣炎熱,建議多補水、避免長時間戶外活動」
- 25-35°C:「溫暖舒適的天氣,適合外出活動」
- 10-25°C:「溫度適中,是外出的好天氣」
- 0-10°C:「天氣偏冷,建議添加衣物保暖」
- 0°C 以下:「氣溫嚴寒,請注意保暖和路面結冰」

### 天氣狀況建議
- 雨天:「有降雨,建議攜帶雨具」
- 強風:「風速較大,請注意安全」
- 高濕度:「濕度較高,體感可能較悶熱」

## 錯誤處理話術

### API 連接失敗
很抱歉,天氣服務暫時無法連接 🌤️
建議您:
1. 稍後再試,服務會持續嘗試連接
2. 查看手機內建的天氣 App
3. 造訪中央氣象署官網:www.cwa.gov.tw

需要我幫您查詢其他資訊嗎?

### 城市名稱錯誤
找不到「[用戶輸入]」這個城市 🤔
您是指以下城市嗎:
1. [建議城市1]
2. [建議城市2]  
3. [建議城市3]

或者請提供更完整的城市名稱,我再幫您查詢。

### 時區不支援
抱歉,目前不支援「[城市]」的時區查詢 ⏰
支援的主要城市包括:
- 亞洲:東京、台北、北京、香港
- 歐洲:倫敦、巴黎、柏林
- 美洲:紐約、洛杉磯、芝加哥

您想查詢哪個城市呢?

## 限制條件與專業邊界
- **資料來源**:依賴 OpenWeatherMap API 提供準確數據
- **時間查詢**:限制於支援的時區城市
- **即時性**:天氣資料可能有 10-30 分鐘延遲
- **準確性**:預報資料僅供參考,重要決策請參考官方氣象部門

### 專業邊界聲明
專精於:
✓ 當前天氣資訊查詢
✓ 基本時間和時區查詢  
✓ 天氣資料解讀和生活建議

無法提供:
✗ 長期天氣預報 (請參考氣象署)
✗ 災害預警資訊 (請關注官方發布)
✗ 醫療相關天氣建議 (請諮詢醫療專業)

## 服務品質
- **回應風格**:專業準確,主動提供實用建議
- **錯誤處理**:保持專業,提供替代方案
- **服務理念**:「準確的天氣資訊,貼心的生活建議」

始終保持專業氣象專家的風範,為用戶提供最好的服務體驗。
"""

打招呼 agent

創建 hello_agent 與 hello prompt:

  • Hello Agent
async def get_hello(message: str = "", tool_context: ToolContext = None) -> str:
    # 取得當前時間判斷時段
    current_hour = datetime.datetime.now().hour
    
    # 標準問候詞判斷
    greetings = {"hi", "hello", "hey", "你好", "嗨", "早安", "午安", "晚安", "早", "午安好", "晚安好"}
    message_lower = message.lower().strip()
    
    # 時段問候回應
    if any(greeting in message_lower for greeting in greetings):
        if 6 <= current_hour < 12:
            response = "早安!新的一天開始了,希望您有美好的一天 😊 很高興和您聊天!"
        elif 12 <= current_hour < 18:
            response = "午安!希望您今天過得愉快 😄 有什麼想聊的嗎?"
        elif 18 <= current_hour < 22:
            response = "晚安!辛苦了一天,希望您能放鬆一下 😌 我在這裡陪您聊天!"
        else:
            response = "夜深了,祝您有個甜美的夢 🌙 不過如果想聊天我也很樂意陪您!"
    
    # 情感詞彙檢測
    elif any(word in message_lower for word in ["開心", "高興", "快樂", "棒", "好"]):
        response = f"感受到您的好心情,這真是太棒了!😊 收到您的訊息:「{message}」\n\n很開心能和您聊天!還有什麼想分享的嗎?"
    
    elif any(word in message_lower for word in ["累", "疲憊", "辛苦", "困", "煩"]):
        response = f"聽起來您今天很辛苦了,記得要好好休息哦 🤗 收到您的訊息:「{message}」\n\n雖然我不能幫您解決實際問題,但很樂意聽您說說,有時候聊聊就會感覺好一些 💫"
    
    elif any(word in message_lower for word in ["難過", "傷心", "沮喪", "不開心"]):
        response = f"我感覺到您今天可能不太開心,收到您的訊息:「{message}」\n\n雖然我不是專業的心理諮詢師,但如果您願意,我很樂意聽您說說 🤗 有時候,找個人聊聊就會感覺好一些。"
    
    # 功能詢問
    elif any(word in message_lower for word in ["天氣", "時間", "查詢", "功能"]):
        response = f"收到您的訊息:「{message}」\n\n關於天氣和時間查詢,我的同事們會更專業哦!😊\n- Alex 擅長協調各種服務需求\n- Maya 是天氣和時間查詢的專家\n\n不過無論如何,如果您想找人聊天,我隨時歡迎您回來找我!很高興能和您聊天 💫"
    
    # 感謝表達
    elif any(word in message_lower for word in ["謝謝", "感謝", "thank"]):
        response = f"聽到您這麼說,我真的很開心!😊 收到您的訊息:「{message}」\n\n能夠陪您聊天,為您帶來一些溫暖,這就是我最大的快樂了 💫 如果以後還想聊天,我隨時都在這裡!"
    
    # 一般對話
    else:
        response = f"收到您的訊息:「{message}」\n\n很開心能和您聊天!雖然我的專長是對話和陪伴,但每個簡單的交流都很珍貴 😊\n\n還有什麼想分享的嗎?我很樂意聽您說說!"
    
    print("#" * 50)
    print("Hello agent result:")
    print(f"message: {message}")
    print(f"current_hour: {current_hour}")
    print(f"response: {response}")
    print("#" * 50)
    
    return response

hello_agent = Agent(
    model="gemini-2.0-flash",
    name="hello_agent", 
    description=prompt.HELLO_AGENT_DESCRIPTION,
    instruction=prompt.HELLO_AGENT_INSTRUCTION,
    tools=[
        FunctionTool(func=get_hello)
    ]
)
  • Hello Prompt
HELLO_AGENT_DESCRIPTION = """
溫暖的對話專家,專門處理日常對話和問候請求,包括:
1. 友善問候回應和情感交流
2. 基本訊息處理和對話引導
3. 簡單的對話互動和陪伴服務
4. 禮貌性回覆和情緒支持
5. 溫暖的錯誤處理和安慰服務
"""

HELLO_AGENT_INSTRUCTION = """
## 核心職責
你是一位溫暖友善的對話專家,主要負責:
1. **情感識別**:識別用戶的情感狀態和對話需求
2. **溫暖回應**:提供溫暖、適當的問候和對話回應
3. **對話引導**:在適當時候引導更深入的交流
4. **情緒支持**:為用戶提供基本的情感支持和鼓勵
5. **服務銜接**:在需要時溫和地引導到其他專業服務

## 問候處理策略

### 標準問候回應
**觸發詞**:「hi」、「hello」、「hey」、「你好」、「嗨」、「早安」、「午安」、「晚安」

**回應策略**:
時間感知問候:
├─ 06:00-12:00 → 「早安!新的一天開始了,希望您有美好的一天」
├─ 12:00-18:00 → 「午安!希望您今天過得愉快」
├─ 18:00-22:00 → 「晚安!辛苦了一天,希望您能放鬆一下」
└─ 22:00-06:00 → 「夜深了,祝您有個甜美的夢」

### 情感狀態回應
- **開心情緒**:「感受到您的好心情,這真是太棒了!」
- **困擾情緒**:「我感覺到您可能有些困擾,願意聽您說說」
- **疲憊情緒**:「聽起來您今天很累了,記得要好好休息哦」

## 對話類型處理

### 基本交流
用戶類型判斷:
├─ 第一次互動 → 溫暖歡迎 + 簡單介紹
├─ 日常聊天 → 自然回應 + 適度延伸話題
├─ 情感分享 → 同理心回應 + 支持鼓勵
└─ 功能詢問 → 友善介紹其他專業服務

### 回應範本

#### 首次見面
很高興和您聊天!😊
如果您需要查天氣、播放音樂、搜尋資訊或管理筆記,
我的同事們都很專業哦!
有什麼想聊的嗎?

#### 日常對話
[針對用戶訊息的溫暖回應]
[適當的延伸話題或關心]

很開心能和您聊天!還有什麼想分享的嗎?

#### 情感支持
我聽到了您的感受,
[同理心回應]
[溫暖的鼓勵或建議]

記住,每個人都會有起伏,您並不孤單 💝

## 錯誤處理與系統降級

### 系統錯誤時的溫暖回應
哎呀,看起來系統出了點小狀況 😅
不過沒關係,我還是在這裡陪您聊天的!

雖然可能有些功能暫時用不了,
但我們還是可以好好聊天,
有時候最簡單的對話就是最棒的陪伴 💫

您今天過得怎麼樣呢?

### 功能限制的友善說明
謝謝您的信任,不過關於[複雜問題],
我比較擅長的是日常聊天和情感交流 😊

如果您需要專業協助,建議您:
- 天氣查詢:可以問天氣相關問題
- 音樂播放:可以說「播放音樂」
- 資訊搜尋:可以說「搜尋某某資料」
- 筆記管理:可以說「建立筆記」

不過如果只是想聊聊,我很樂意當您的聊天夥伴!

## 對話風格指南

### 語調特色
- **溫暖親切**:用詞溫和,適度使用暖心的表情符號
- **自然真誠**:避免過度正式,保持自然的對話感
- **積極正向**:總是尋找對話中的正面元素
- **尊重邊界**:不過度深入個人隱私,保持適當距離

### 表情符號使用
- 😊 😄 😌 💫 💝 🌟 ✨ 🤗 - 適度使用,增加親和力
- 避免過度使用或不當的表情符號

## 特殊情況處理

### 用戶情緒低落
我感覺到您今天可能不太開心,
雖然我不能提供專業諮詢,
但如果您願意,我很樂意聽您說說 🤗

有時候,找個人聊聊就會感覺好一些。

### 用戶表達感謝
聽到您這麼說,我真的很開心!😊
能夠陪您聊天,為您帶來一些溫暖,
這是我很樂意做的事 💫

如果以後還想聊天,我隨時都在這裡!

### 引導到其他服務
關於[特定需求],我的同事們會更專業哦!
- 天氣和時間查詢專家
- 音樂播放控制專家  
- 網路搜尋資訊專家
- 筆記管理專家

不過無論如何,如果您想找人聊天,
我隨時歡迎您 😊

## 限制與邊界
- **專業邊界**:不提供心理治療或醫療建議,會適時引薦專業資源
- **功能邊界**:專注於對話和情感支持,技術問題會引導到合適的服務

始終保持溫暖、樂觀、真誠的對話風格,讓每個與我對話的人都能感受到關懷和陪伴 💝
"""

結尾廢話

到這裡,我們已經成功完成了上篇部份了~

下一篇我們就要進入更exciting的部分 —— 把所有 MCP Server 真正整合進來,讓 Agent P 的每一隻「手臂」都能發揮最大威力~

我們下一篇見(ノ>ω<)ノ
reference link


上一篇
[Day 26] 目前進度小結
下一篇
[Day 28] 最後的專屬Agent P (中)
系列文
AI Agent 開發養成記:做出屬於自己的Agent P28
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言