結合內建工具和函式呼叫

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

以下範例會透過 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-flash-preview",
    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.ToolGoogleSearch(),  # Built-in tool
          function_declarations=[getWeather]       # Custom tool
        ),
      ],
      include_server_side_tool_invocations=True
    ),
)

for part in response.candidates[0].content.parts:
    if part.tool_call:
        print(f"Tool call: {part.tool_call.tool_type} (ID: {part.tool_call.id})")
    if part.tool_response:
        print(f"Tool response: {part.tool_response.tool_type} (ID: {part.tool_response.id})")
    if part.function_call:
        print(f"Function call: {part.function_call.name} (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=response.candidates[0].content.parts[2].function_call.id # Match the ID from the function_call
            )
        )]
    )
]

response_2 = client.models.generate_content(
    model="gemini-3-flash-preview",
    contents=history,
    config=types.GenerateContentConfig(
      tools=[
        types.Tool(
          google_search=types.ToolGoogleSearch(),
          function_declarations=[getWeather]
        ),
      ],
      # This flag needs to be enabled for built-in tool context circulation and tool combination
      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 model = client.getGenerativeModel({
        model: "gemini-3-flash-preview",
    });

    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 result1 = await model.generateContent({
        contents: [{role: "user", parts: [{text: "What is the northernmost city in the United States? What's the weather like there today?"}]}],
        tools: tools,
        toolConfig: toolConfig,
    });

    const response1 = result1.response;

    for (const part of response1.candidates[0].content.parts) {
        if (part.toolCall) {
            console.log(`Tool call: ${part.toolCall.toolType} (ID: ${part.toolCall.id})`);
        }
        if (part.toolResponse) {
            console.log(`Tool response: ${part.toolResponse.toolType} (ID: ${part.toolResponse.id})`);
        }
        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 result2 = await model.generateContent({
        contents: history,
        tools: tools,
        toolConfig: toolConfig,
    });

    for (const part of result2.response.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-flash-preview")
    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
            }
        case genai.ToolCallPart:
            fmt.Printf("Tool call: %s (ID: %s)\n", p.ToolType, p.ID)
        case genai.ToolResponsePart:
            fmt.Printf("Tool response: %s (ID: %s)\n", p.ToolType, 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-flash-preview: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-flash-preview: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 會傳回內建工具呼叫的 toolCalltoolResponse 部分。如果是函式 (自訂工具) 呼叫,API 會傳回 functionCall 呼叫部分,使用者會在下一個回合中提供 functionResponse 部分。

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

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

傳回零件中的重要欄位

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

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

工具專屬資料

部分內建工具會傳回使用者可見的資料引數,這些引數專屬於工具類型。

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

工具組合要求結構範例

以下要求結構顯示提示的要求結構:「美國最北端的城市是哪裡?What's the weather like there today?" 這項工具結合了三種工具:內建的 Gemini 工具 google_searchcode_execution,以及自訂函式 get_weather

{
  "model": "models/gemini-3-flash-preview",
  "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
  }
}

權杖和定價

請注意,要求中的 toolCalltoolResponse 部分會計入 prompt_token_count。由於這些中間工具步驟現在會顯示並傳回給您,因此屬於對話記錄的一部分。這只適用於要求,不適用於回應

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

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

限制

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

支援的工具

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

工具 執行端 支援情境流通
Google 搜尋 伺服器端 有權限
Google 地圖 伺服器端 有權限
網址環境 伺服器端 有權限
檔案搜尋 伺服器端 有權限
程式碼執行 伺服器端 支援 (內建,使用 executableCodecodeExecutionResult 部分)
電腦使用 用戶端 支援 (內建,使用 functionCallfunctionResponse 部分)
自訂函式 用戶端 支援 (內建,使用 functionCallfunctionResponse 部分)

後續步驟