結合內建工具和函式呼叫


Gemini 支援在單一生成內容中,結合內建工具 (例如 google_search) 和函式呼叫 (又稱自訂工具),方法是保留並公開工具呼叫的內容記錄。內建和自訂工具組合可支援複雜的 Agentic Workflows,例如模型可先根據即時網路資料建立基礎,再呼叫特定商業邏輯。

以下範例使用 google_search 和自訂函式 getWeather,啟用內建和自訂工具組合:

Python

from google import genai
from google.genai import types

client = genai.Client()

getWeather = {
    "name": "getWeather",
    "description": "Gets the weather for a requested city.",
    "parameters": {
        "type": "object",
        "properties": {
            "city": {
                "type": "string",
                "description": "The city and state, e.g. Utqiaġvik, Alaska",
            },
        },
        "required": ["city"],
    },
}

# Turn 1: Initial request with Google Search (built-in) and getWeather (custom) tools enabled
response = client.models.generate_content(
    model="gemini-3.8-flash",
    contents="What is the northernmost city in the United States? What's the weather like there today?",
    config=types.GenerateContentConfig(
        tools=[
            types.Tool(
                google_search=types.GoogleSearch(),  # Built-in tool
                function_declarations=[getWeather],  # Custom tool
            ),
        ],
        tool_config=types.ToolConfig(
            include_server_side_tool_invocations=True
        )
    ),
)
function_call_id = None
for part in response.candidates[0].content.parts:
    if part.function_call:
        print(f"Function call: {part.function_call.name} (ID: {part.function_call.id})")
        function_call_id = part.function_call.id

# Turn 2: Manually build history to circulate both tool and function context
history = [
    types.Content(
        role="user",
        parts=[types.Part(text="What is the northernmost city in the United States? What's the weather like there today?")]
    ),
    # Response from Turn 1 includes tool_call, tool_response, and thought_signatures
    response.candidates[0].content,
    # Return the function_response
    types.Content(
        role="user",
        parts=[types.Part(
            function_response=types.FunctionResponse(
                name="getWeather",
                response={"response": "Very cold. 22 degrees Fahrenheit."},
                id=function_call_id # Match the ID from the function_call
            )
        )]
    )
]

response_2 = client.models.generate_content(
    model="gemini-3.8-flash",
    contents=history,
    config=types.GenerateContentConfig(
        tools=[
            types.Tool(
                google_search=types.GoogleSearch(),
                function_declarations=[getWeather]
            ),
        ],
        # This flag needs to be enabled for built-in tool context circulation and tool combination
        tool_config=types.ToolConfig(
            include_server_side_tool_invocations=True
        )
    ),
)

for part in response_2.candidates[0].content.parts:
    if part.text:
        print(part.text)

JavaScript

import { GoogleGenAI } from '@google/genai';

const client = new GoogleGenAI({});

const getWeather = {
    name: "getWeather",
    description: "Get the weather in a given location",
    parameters: {
        type: "OBJECT",
        properties: {
            location: {
                type: "STRING",
                description: "The city and state, e.g. San Francisco, CA"
            }
        },
        required: ["location"]
    }
};

async function run() {
    const tools = [
      { googleSearch: {} },
      { functionDeclarations: [getWeather] }
    ];
    // This flag needs to be enabled for built-in tool context circulation and tool combination
    const toolConfig = { includeServerSideToolInvocations: true };

    // Turn 1: Initial request with Google Search (built-in) and getWeather (custom) tools enabled
    const response1 = await client.models.generateContent({
        model: "gemini-3.8-flash",
        contents: [{role: "user", parts: [{text: "What is the northernmost city in the United States? What's the weather like there today?"}]}],
        config: {
            tools: tools,
            toolConfig: toolConfig,
        },
    });

    for (const part of response1.candidates[0].content.parts) {
        if (part.functionCall) {
            console.log(`Function call: ${part.functionCall.name} (ID: ${part.functionCall.id})`);
        }
    }

   const functionCallId = response1.candidates[0].content.parts.find(p => p.functionCall)?.functionCall?.id;

    // Turn 2: Manually build history to circulate both tool and function context
    const history = [
        {
            role: "user",
            parts:[{text: "What is the northernmost city in the United States? What's the weather like there today?"}]
        },
        // Response from Turn 1 includes tool_call, tool_response, and thought_signatures
        response1.candidates[0].content,
        // Return the function_response
        {
            role: "user",
            parts: [{
                functionResponse: {
                    name: "getWeather",
                    response: {response: "Very cold. 22 degrees Fahrenheit."},
                    id: functionCallId // Match the ID from the function_call
                }
            }]
        }
    ];

    const response2 = await client.models.generateContent({
        model: "gemini-3.8-flash",
        contents: history,
        config: {
            tools: tools,
            toolConfig: toolConfig,
        },
    });

    for (const part of response2.candidates[0].content.parts) {
        if (part.text) {
            console.log(part.text);
        }
    }
}

run();

Go

package main

import (
    "context"
    "fmt"
    "log"
    "os"

    "github.com/google/generative-ai-go/genai"
    "google.golang.org/api/option"
)

func main() {
    ctx := context.Background()
    client, err := genai.NewClient(ctx, option.WithAPIKey(os.Getenv("GEMINI_API_KEY")))
    if err != nil {
        log.Exit(err)
    }
    defer client.Close()

    getWeather := &genai.FunctionDeclaration{
        Name:        "getWeather",
        Description: "Get the weather in a given location",
        Parameters: &genai.Schema{
            Type: genai.Object,
            Properties: map[string]*genai.Schema{
                "location": {
                    Type:        genai.String,
                    Description: "The city and state, e.g. San Francisco, CA",
                },
            },
            Required: []string{"location"},
        },
    }

    model := client.GenerativeModel("gemini-3.8-flash")
    model.Tools = []*genai.Tool{
        {GoogleSearch: &genai.GoogleSearch{}}, // Built-in tool
        {FunctionDeclarations: []*genai.FunctionDeclaration{getWeather}}, // Custom tool
    }
    ist := true
    model.ToolConfig = &genai.ToolConfig{
        IncludeServerSideToolInvocations: &ist, // This flag needs to be enabled for built-in tool context circulation and tool combination
    }

    chat := model.StartChat()

    // Turn 1: Initial request with Google Search (built-in) and getWeather (custom) tools enabled
    prompt := genai.Text("What is the northernmost city in the United States? What's the weather like there today?")
    resp1, err := chat.SendMessage(ctx, prompt)
    if err != nil {
        log.Exitf("SendMessage failed: %v", err)
    }

    if resp1 == nil || len(resp1.Candidates) == 0 || resp1.Candidates[0].Content == nil {
        log.Exit("empty response from model")
    }

    var functionCallID string
    for _, part := range resp1.Candidates[0].Content.Parts {
        switch p := part.(type) {
        case genai.FunctionCall:
            fmt.Printf("Function call: %s (ID: %s)\n", p.Name, p.ID)
            if p.Name == "getWeather" {
                functionCallID = p.ID
            }
        }
    }

    if functionCallID == "" {
        log.Exit("no getWeather function call in response")
    }

    // Turn 2: Provide function result back to model.
    // Chat history automatically includes tool_call, tool_response, and thought_signatures from Turn 1.
    fr := genai.FunctionResponse{
        Name: "getWeather",
        ID:   functionCallID,
        Response: map[string]any{
            "response": "Very cold. 22 degrees Fahrenheit.",
        },
    }

    resp2, err := chat.SendMessage(ctx, fr)
    if err != nil {
        log.Exitf("SendMessage for turn 2 failed: %v", err)
    }

    if resp2 == nil || len(resp2.Candidates) == 0 || resp2.Candidates[0].Content == nil {
        log.Exit("empty response from model in turn 2")
    }

    for _, part := range resp2.Candidates[0].Content.Parts {
        if txt, ok := part.(genai.Text); ok {
            fmt.Println(string(txt))
        }
    }
}

REST

# Turn 1: Initial request with Google Search (built-in) and getWeather (custom) tools enabled
curl -X POST "https://generativelanguage.googleapis.com/v1beta/models/gemini-3.8-flash:generateContent" \
-H "Content-Type: application/json" \
-H "x-goog-api-key: $GEMINI_API_KEY" \
-d '{
  "contents": [{
    "role": "user",
    "parts": [{
      "text": "What is the northernmost city in the United States? What'\''s the weather like there today?"
    }]
  }],
  "tools": [{
    "googleSearch": {}
  }, {
    "functionDeclarations": [{
      "name": "getWeather",
      "description": "Get the weather in a given location",
      "parameters": {
          "type": "OBJECT",
          "properties": {
              "location": {
                  "type": "STRING",
                  "description": "The city and state, e.g. San Francisco, CA"
              }
          },
          "required": ["location"]
      }
    }]
  }],
  "toolConfig": {
    "includeServerSideToolInvocations": true
  }
}'

# Turn 2: Manually build history to circulate both tool and function context
# The following request assumes you have captured candidates[0].content from Turn 1 response,
# and extracted function_call.id for getWeather.
# Replace FUNCTION_CALL_ID and insert candidate content from turn 1.
curl -X POST "https://generativelanguage.googleapis.com/v1beta/models/gemini-3.8-flash:generateContent" \
-H "Content-Type: application/json" \
-H "x-goog-api-key: $GEMINI_API_KEY" \
-d '{
  "contents": [
    {
      "role": "user",
      "parts": [{"text": "What is the northernmost city in the United States? What'\''s the weather like there today?"}]
    },
    YOUR_CANDIDATE_CONTENT_FROM_TURN_1_RESPONSE,
    {
      "role": "user",
      "parts": [{
        "functionResponse": {
          "name": "getWeather",
          "id": "FUNCTION_CALL_ID",
          "response": {"response": "Very cold. 22 degrees Fahrenheit."}
        }
      }]
    }
  ],
  "tools": [{
    "googleSearch": {}
  }, {
    "functionDeclarations": [{
      "name": "getWeather",
      "description": "Get the weather in a given location",
      "parameters": {
          "type": "OBJECT",
          "properties": {
              "location": {
                  "type": "STRING",
                  "description": "The city and state, e.g. San Francisco, CA"
              }
          },
          "required": ["location"]
      }
    }]
  }],
  "toolConfig": {
    "includeServerSideToolInvocations": true
  }
}'

運作方式

Gemini 3 模型會使用工具脈絡循環,啟用內建和自訂工具組合。工具內容循環功能可保留及公開內建工具的內容,並在同一呼叫中與自訂工具共用,在不同回合間傳遞。

啟用工具組合

  • 您必須將 include_server_side_tool_invocations 旗標設為 true,才能啟用工具內容循環。
  • 加入 function_declarations 和要使用的內建工具,即可觸發組合行為。
    • 如果不加入 function_declarations,只要設定旗標,工具內容循環仍會對內建工具生效。

API 會傳回零件

API 會在單一回應中,傳回內建工具呼叫的 toolCall 和 toolResponse 部分。如果是函式 (自訂工具) 呼叫,API 會傳回 functionCall 呼叫部分,使用者則會在下一個回合提供 functionResponse 部分。

  • toolCall 和 toolResponse:API 會傳回這些部分,以保留在伺服器端執行的工具內容,以及這些工具的執行結果,供下一個回合使用。
  • functionCall 和 functionResponse:API 會將函式呼叫傳送給使用者填寫,使用者則會在函式回應中傳回結果 (這些部分是 Gemini API 中所有函式呼叫的標準做法,並非工具組合功能獨有)。
  • (僅限程式碼執行工具) executableCode 和 codeExecutionResult: 使用程式碼執行工具時,API 會傳回 executableCode (模型生成、要執行的程式碼) 和 codeExecutionResult (可執行程式碼的結果),而非 functionCall 和 functionResponse。

您必須在每個回合將所有部分 (包括所含的所有欄位) 傳回模型,以維持脈絡並啟用工具組合。

退回零件的重要欄位

API 傳回的特定部分會包含 id、tool_type 和 thought_signature 欄位。這些欄位對於維護工具情境至關重要 (因此對工具組合也至關重要);您需要在後續要求中,傳回回應中提供的所有部分。

  • id:將呼叫對應至回應的專屬 ID。無論工具內容是否循環,id 都會在所有函式呼叫回應中設定。您必須在函式回應中提供與 API 在函式呼叫中提供的 id 相同的 。內建工具會自動在工具呼叫和工具回應之間共用 id。
    • 出現在所有與工具相關的部分:toolCall、toolResponse、functionCall、functionResponse、executableCode、codeExecutionResult
  • tool_type:識別使用的特定工具;內建工具的字面值或 (例如 URL_CONTEXT) 或函式 (例如 getWeather) 名稱。
    • 在 toolCall 和 toolResponse 部分中找到。
  • thought_signature:嵌入 API 傳回的每個部分中的實際加密內容。如果沒有思考簽章,就無法重建背景資訊。如果您未在每個回合中傳回所有部分的思考簽章,模型就會發生錯誤。
    • 在所有部位都可找到。

工具專屬資料

部分內建工具會傳回使用者可見的資料引數,具體取決於工具類型。

工具 使用者可見的工具呼叫引數 (如有) 使用者可見的工具回覆 (如有)
GOOGLE_SEARCH queries search_suggestions
GOOGLE_MAPS queries places
google_maps_widget_context_token
URL_CONTEXT urls
要瀏覽的網址
url_metadata
retrieved_url:瀏覽的網址
url_retrieval_status:瀏覽狀態
FILE_SEARCH 無 無

工具組合要求結構範例

下列要求結構顯示提示詞的要求結構:「What is the northernmost city in the United States? 「今天那裡的天氣如何?」這個代理結合了三種工具:內建 Gemini 工具 google_search 和 code_execution,以及自訂函式 get_weather。

{
  "model": "models/gemini-3.8-flash",
  "contents": [{
    "parts": [{
      "text": "What is the northernmost city in the United States? What's the weather like there today?"
    }],
    "role": "user"
  }, {
    "parts": [{
      "thoughtSignature": "...",
      "toolCall": {
        "toolType": "GOOGLE_SEARCH_WEB",
        "args": {
          "queries": ["northernmost city in the United States"]
        },
        "id": "a7b3k9p2"
      }
    }, {
      "thoughtSignature": "...",
      "toolResponse": {
        "toolType": "GOOGLE_SEARCH_WEB",
        "response": {
          "search_suggestions": "..."
        },
        "id": "a7b3k9p2"
      }
    }, {
      "functionCall": {
        "name": "getWeather",
        "args": {
          "city": "Utqiaġvik, Alaska"
        },
        "id": "m4q8z1v6"
      },
      "thoughtSignature": "..."
    }],
    "role": "model"
  }, {
    "parts": [{
      "functionResponse": {
        "name": "getWeather",
        "response": {
          "response": "Very cold. 22 degrees Fahrenheit."
        },
        "id": "m4q8z1v6"
      }
    }],
    "role": "user"
  }],
  "tools": [{
    "functionDeclarations": [{
      "name": "getWeather"
    }]
  }, {
    "googleSearch": {
    }
  }, {
    "codeExecution": {
    }
  }],
  "toolConfig": {
    "includeServerSideToolInvocations": true
  }
}

代幣和價格

請注意,要求中的 toolCall 和 toolResponse 部分會計入 prompt_token_count。由於這些中間工具步驟現在會顯示並傳回給您,因此會成為對話記錄的一部分。這項規則僅適用於要求,不適用於回應。

Google 搜尋工具不在此限。Google 搜尋已在查詢層級套用自己的定價模式,因此不會重複收取權杖費用 (請參閱「定價」頁面)。

詳情請參閱「權杖」頁面。

限制

  • 啟用 include_server_side_tool_invocations 旗標時,預設為 VALIDATED 模式 (不支援 AUTO 模式)
  • google_search 等內建工具會依據位置和目前時間資訊運作,因此如果 system_instruction 或 function_declaration.description 的位置和時間資訊不一致,工具組合功能可能無法正常運作。

支援的工具

標準工具脈絡流通適用於伺服器端 (內建) 工具。 執行程式碼也是伺服器端工具,但有內建的脈絡循環解決方案。電腦使用和函式呼叫是用戶端工具,也內建解決方案,可處理內容循環。

工具 執行端 背景資訊循環支援
Google 搜尋 伺服器端 是否支援
Google 地圖 伺服器端 是否支援
網址背景資訊 伺服器端 是否支援
檔案搜尋 伺服器端 是否支援
執行程式碼 伺服器端 支援 (內建,使用 executableCode 和 codeExecutionResult 部分)
電腦使用 用戶端 支援 (內建,使用 functionCall 和 functionResponse 部分)
自訂函式 用戶端 支援 (內建,使用 functionCall 和 functionResponse 部分)

後續步驟