iT邦幫忙

2026 iThome 鐵人賽

DAY 20
0
AI Engineering

從零打造情感感知 Agentic System:FSM 狀態機與 RAG 的整合實作系列 第 20 篇

Day 20:【Tool Use 整合篇】拔刀吧!結合 Intent Router 與 Function Calling,正式將動畫推薦工具串進 app.py

  • 分享至 

  • xImage
  •  

「在寫程式之前,我先去看一下番好了,掰掰」(Ps 乖孩子們,請勿學習)

前言

前兩天我在 test_tool.py 和 test_tool_1.py 裡打單機測試,雖然能抓到動畫瘋新番,但 Agent 依然活在 Terminal 終端機的小黑框裡,完全沒有互動感。

今天我們把在 Day 08 學到的 Intent Router(意圖分流) 與 Function Calling 結合,正式在 app.py 裡打造了一條流暢的流水線:

  • 當我問:「最近有什麼好看的新番?」 ⇒ 判定需要工具 ⇒ 自動發動「巴哈姆特動畫瘋連線術」 ⇒ 抓回最新新番。
  • 當我只是想跟 Agent 聊廢話:「莉莉我今天好累」 ⇒ Router 判定不需要工具 ⇒ 直接用內建的大腦回覆,省下不必要的 API 耗能與延遲。

今天的重量目標:

  • 整合 tools.py:包含基礎狀態查詢與動畫瘋新番爬蟲工具庫。
  • 爆改 app.py 流程:引入 Intent Router 與 Function Calling 的自動分流機制。
  • Streamlit UI 實裝:讓使用者可以在畫面上直接叫莉莉幫忙查動畫瘋熱門新番!

第一關:整理 tools.py 工具庫

將前兩天的測試成果正式整合起來:

# tools.py
import requests
from bs4 import BeautifulSoup

headers = {"User-Agent": "Mozilla/5.0"}

# --- 使用者狀態查詢(原 tools_0.py)---
def get_user_status(user_id: str = "default_user") -> str:
    """取得指定使用者的當前狀態、等級與即時資訊。

    Args:
        user_id: 使用者的唯一識別碼,預設為 'default_user'
    """
    mock_db = {
        "default_user": "身份:資工系學生,當前狀態:正在寫 Python Code,剩餘精力:30%"
    }
    status = mock_db.get(user_id, "未知使用者")
    return f"【系統狀態回傳】使用者 ID:{user_id} | {status}"

# --- 動畫瘋新番查詢(原 tools_1.py)---
def get_ani_gamer_new_anime() -> str:
    """當使用者詢問最近有什麼動漫新番、巴哈姆特動畫瘋推薦、當季動畫或想找動畫看時呼叫此工具。"""
    url = "https://ani.gamer.com.tw/"

    try:
        res = requests.get(url, headers=headers, timeout=5)
        soup = BeautifulSoup(res.text, "html.parser")

        anime_items = soup.select(".newanime-date-area .anime-name")
        if not anime_items:
            anime_items = soup.select(".theme-list-main .theme-name")

        titles = [item.text.strip() for item in anime_items[:6] if item.text.strip()]

        if titles:
            anime_str = "\n".join([f"• 《{t}》" for t in titles])
            return f"【巴哈姆特動畫瘋情報連線】目前動畫瘋熱門當季新番:\n{anime_str}"
        else:
            return "【巴哈姆特動畫瘋情報連線】目前順利連線,但未抓取到當前新番列表。"

    except Exception as e:
        return f"動畫瘋情報連線失敗:{e}"

# 註冊給 Gemini 的工具清單。
# app.py 直接用 tools.ALL_TOOLS,之後新增工具只要往這裡加一個名字,
# 不必再去改 app.py
ALL_TOOLS = [
    get_user_status,
    get_ani_gamer_new_anime,
]

第二關:改 app.py 實裝工具自動分流與介面

記得先引用 tools 模組:

import tools

在 Streamlit 的對話核心中加入 tools 設定:

# CHAT 路線:走原本的莉莉傲嬌日常聊天
response = st.session_state.chat.send_message(
    user_input,
    config=types.GenerateContentConfig(
        system_instruction=final_instruction,
        tools=tools.ALL_TOOLS,
    )
)

# ------------------------------------------------------------------
# TASK 路線也掛工具:工具的觸發時機交給模型判斷(實測夠準)
response = st.session_state.chat.send_message(
    user_input,
    config=types.GenerateContentConfig(
        system_instruction=(
            "請以極度精準、高效率的方式完成使用者的任務,不要有任何傲嬌廢話或角色扮演。"
            "例外:如果這一輪呼叫了工具取得資料(例如查詢狀態、查詢動畫新番),"
            "請改用莉莉嘴硬的口吻把查到的結果轉述出來,不要變成冷冰冰的資料播報。"
            "純粹的計算、翻譯、寫程式這類任務不適用這個例外,直接給答案就好。"
        ),
        tools=tools.ALL_TOOLS,
    )
)

這裡的設計重點是那個「例外」:TASK 路線本來就是要精準、不演戲,但如果這一輪其實是「幫我查東西」,冷冰冰地播報資料就完全失去陪伴感。所以規則寫成「算數學照樣冷靜,查資料要用她的口吻講」。

第三關:工具測試結果

  • 使用者:「莉莉,最近巴哈姆特動畫瘋有什麼好看的新番推薦?我需要補充宅能量!」

  • 莉莉(觸發動畫瘋連線工具後):

    「(哼!這種正經事就交給我吧)哈?!你這笨蛋下週都要期末考了,居然還有心情在這邊想著要看動漫?真、真是受不了你耶!要是你因為這樣而考不及格,我可不會同情你喔!……好、好啦!看在你這麼有誠意求我的份上,我剛剛只是「順便」幫你留意了一下現在大家都在看什麼啦!才、才不是特地幫你去查的,你可別誤會了!(接著把查到的當季新番一部一部唸出來,還順便吐槽了幾個名字很長的)……好啦,資料都塞給你了!不、不過我警告你喔!只能挑「一集」當作讀書空檔的放鬆,看完就立刻給我滾回書桌前去讀書!」

    https://ithelp.ithome.com.tw/upload/images/20261004/201838774REv6z9FkU.png

注意她連「下週要期末考」都記得,那是 RAG 從長期記憶撈出來的,跟工具查到的新番清單在同一句話裡合體了。

今日心得

  • 「不戰鬥的話,就無法贏得新番資訊!」:從 Terminal 小黑框搬到 Web UI,終於擺脫了單純寫 Script 跑輸出的乾澀感。莉莉不再只是被動回覆的 NPC,而是能隨時為我連線現實世界的實戰型陪伴 Agent!
  • 「這就是 LLM 的自在極意功吧?」:自動分流的感覺真的太爽快了!完全不需要去手動寫一堆死板的 if-else 去判斷使用者有沒有輸入關鍵字。Gemini 只要看懂意圖就直接本能觸發 Tool Call!平常安靜陪伴、關鍵時刻精準開招,如底下那樣。

動漫名:《七龍珠超》


上一篇
Day 19:【Tool Use 實作篇】Agent 連線現實世界!實作「新番」檢索工具
下一篇
Day 21:【多角色篇 1】重構 Router、Evaluator、Persona 與好感度 FSM
系列文
從零打造情感感知 Agentic System:FSM 狀態機與 RAG 的整合實作 共 25 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言