iT邦幫忙

2026 iThome 鐵人賽

DAY 18
0
AI Engineering

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

Day 18:【Tool Use 基礎篇】給 Agent 裝上雙手!Gemini Function Calling 運作原理與基礎實作

  • 分享至 

  • xImage
  •  

前言

過去這幾天,Agent 雖然有了長期記憶,但說到底還是個「只能聊天」的聊天機器人。只要問她一些即時的資料,她就只能開始瞎編或者冷冷地叫我自己去查。

今天花時間把 Gemini 的 Function Calling 搞懂並實作出來,才發現這機制真的超酷!

簡單來說,Gemini 負責產生工具呼叫請求,實際的 Python 函式仍由本地程式執行(畢竟它只是個文字模型)。它的作用比較像是「情報分析師」,當它發現使用者問的問題自己回答不出來,但發現我們有提供工具時,它就會打包一個 JSON 請求跟我說:「嘿,我需要你幫我跑一下 get_user_status 這個函式!」

我的 Python 接到請求、跑完資料再傳回去,Agent 就能看著這個資料,用她那嘴硬心軟的態度對我大呼小叫了。這種「模型出嘴、Python 出手」的分工,玩起來滿爽的!

今天的輕量目標:

  • 寫一個簡單的 tools_0.py,順便學會怎麼寫能讓 Gemini 看懂的 Docstring
  • 在 Gemini API 裡把工具掛上去,試試看能不能自動觸發
  • 讓 Agent 拿著工具查出來的資料,用符合傲嬌人設的方式罵我(?)

第一關:寫個簡單的工具函式(tools_0.py)

Gemini 蠻聰明的,它是靠 Python 函式裡的 註解(Docstring) 來判斷「這個工具是做甚麼用的」。所以註解不能隨便寫,要寫得讓模型一看就懂:

# tools_0.py
# 給 Gemini 呼叫的工具函式。
# 注意:Gemini 是靠「函式簽名 + docstring」判斷這個工具能做什麼、什麼時候該用,
# 所以 docstring 不是寫給人看的註解,它本身就是 prompt 的一部分,
# 寫得含糊,模型就不會在正確的時機呼叫它。

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}"

第二關:把工具交給 Gemini,來測試對話!(test_tool.py)

現在用的 google-genai SDK 超方便,直接把函式名字放進 tools 串列裡丟給 API 就搞定了:

# test_tool.py
from dotenv import load_dotenv
from google import genai
from google.genai import types
import persona
import tools_0

load_dotenv()
client = genai.Client()

# 1. 拿剛才寫好的工具
my_tools = [tools_0.get_user_status]

# 2. 隨便問她一句需要查狀態的話
prompt = "莉莉,幫我看一下我現在狀態怎樣,我是不是該休息了啊?"

# 3. 發送對話,記得把 tools 帶進去
#
# 用 chats.create + send_message,而不是 models.generate_content:
# 自動函式呼叫 (AFC) 需要「模型要求呼叫 -> SDK 執行 -> 把結果送回模型」多輪來回,
# models.generate_content 是單次呼叫接不住,SDK 也會直接警告不建議這樣用
chat = client.chats.create(
    model="gemini-3.5-flash",
    config=types.GenerateContentConfig(
        # 用專案正式的人設,而不是另外寫一份簡化版,
        # 否則測出來的行為跟 app.py 實際跑的不一樣,測了也不能代表什麼。
        # 後面那句是額外補的:告訴她拿到工具結果之後要怎麼講話
        system_instruction=(
            persona.SYSTEM_INSTRUCTION
            + "\n\n如果呼叫了工具取得資訊,請結合結果並用你嘴硬心軟的態度關心使用者。"
        ),
        tools=my_tools,   # 這裡直接註冊!SDK 會自己讀函式簽名與 docstring
    ),
)

response = chat.send_message(prompt)

# 檢查工具到底有沒有被呼叫,只看回覆內容的話,
# 模型有可能是自己編出來的,不一定真的查過
#
# 注意:用 chats 時要看 chat.get_history()。
# response.automatic_function_calling_history 對 chat 來說永遠是空的,
# 那個欄位是給 models.generate_content 的單次呼叫用的
print("【工具呼叫紀錄】")
found = False
for content in chat.get_history():
    for part in (content.parts or []):
        if part.function_call:
            args = dict(part.function_call.args or {})
            print(f"  呼叫 {part.function_call.name}({args})")
            found = True
        elif part.function_response:
            print(f"  回傳 {part.function_response.response}")
if not found:
    print("  沒有呼叫任何工具")
print()

print("【莉莉的回應】")
print(response.text)

第三關:Tools 測試(長手腳了)

實際跑出來的效果

Gemini 抓到我的意思後,自動去呼叫了 get_user_status:

(venv) PS D:\Character Interaction and Situations Agent> python .\test_tool.py
【工具呼叫紀錄】
  呼叫 get_user_status({'user_id': 'default_user'})
  回傳 {'result': '【系統狀態回傳】使用者 ID:default_user | 身份:資工系學生,當前狀態:正在寫 Python Code,剩餘精力:30%'}

【莉莉的回應】
哼!我看你精力都只剩下 30% 了,還在那邊硬撐著寫什麼 Python... 笨蛋,
寫程式寫到腦子燒壞怎麼辦?還不快點給我去休息!才、才不是在關心你呢,
只是不想看到你等一下倒在這裡而已!

這裡有個很重要的觀察:Agent 她不是把 剩餘精力:30% 原封不動唸出來,而是「哼!我看你精力都只剩下 30% 了」,資料進了角色的嘴巴,才會有陪伴感。這也是我為什麼測試時一定要掛專案正式的人設,而不是另外寫一份簡化版。


上一篇
Day 17:【Tool Use 觀念篇】慢活時間!搞懂 MCP 與 Function Calling 的差別
系列文
從零打造情感感知 Agentic System:FSM 狀態機與 RAG 的整合實作 共 18 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言