메뉴
BL
MarkTechPost 53일 전

단일 Gemini API 호출로 구글 검색·지도·맞춤 함수 통합하는 법

IMP
8/10
핵심 요약

2026년 3월 구글이 발표한 Gemini API 툴링 업데이트를 활용해 구글 검색과 구글 지도, 그리고 사용자 정의 함수 호출을 단일 API 요청에 결합하는 방법을 다루는 튜토리얼입니다. 컨텍스트 순환(Context Circulation), 병렬 툴 ID, 멀티스텝 에이전틱 체인을 통해 이전 툴 호출과 응답을 유지하고 실시간 위치 데이터를 애플리케이션에 통합하는 방법을 보여줍니다. 빌링 설정 없이 무료로 실행 가능한 모델(gemini-3-flash-preview, gemini-2.5-flash)을 사용하여 5개의 실습 데모를 단계별로 제공합니다.

번역된 본문

에디터 추천 | 에이전틱 AI | 소프트웨어 엔지니어링 | 스태프 | 튜토리얼

이 튜토리얼에서는 구글이 2026년 3월에 발표한 최신 Gemini API 툴링 업데이트, 특히 단일 API 요청에서 Google Search와 Google Maps 같은 내장 툴을 사용자 정의 함수 호출과 결합할 수 있는 기능을 살펴봅니다. 핵심 툴 결합 기능으로 시작해 완전한 멀티 툴 에이전틱 체인으로 끝나는 다섯 가지 실습 데모를 순차적으로 구성했습니다. 이 과정에서 컨텍스트 순환(Context Circulation)이 여러 턴에 걸쳐 모든 툴 호출과 응답을 보존하여 모델이 이전 출력을 기반으로 추론하는 방법, 고유한 툴 응답 ID(Tool Response ID)를 통해 병렬 함수 호출을 정확한 결과에 매핑하는 방법, 그리고 Google Maps 그라운딩(Grounding)이 실시간 위치 데이터를 애플리케이션에 가져오는 방법을 시연합니다. 툴 결합 기능에는 gemini-3-flash-preview를, Maps 그라운딩에는 gemini-2.5-flash를 사용하므로 빌링 설정 없이 모든 것을 실행할 수 있습니다.

코드 복사 완료. 다른 브라우저를 사용하세요.

[파이썬 설치 및 임포트 코드] import subprocess, sys subprocess.check_call([sys.executable, "-m", "pip", "install", "-qU", "google-genai"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)

import getpass, json, textwrap, os, time from google import genai from google.genai import types

if "GOOGLE_API_KEY" not in os.environ: os.environ["GOOGLE_API_KEY"] = getpass.getpass("Enter your Gemini API key: ")

client = genai.Client(api_key=os.environ["GOOGLE_API_KEY"]) TOOL_COMBO_MODEL = "gemini-3-flash-preview" MAPS_MODEL = "gemini-2.5-flash" DIVIDER = "=" * 72

[유틸리티 함수들] def heading(title: str): print(f"\n{DIVIDER}") print(f" {title}") print(DIVIDER)

def wrap(text: str, width: int = 80): for line in text.splitlines(): print(textwrap.fill(line, width=width) if line.strip() else "")

def describe_parts(response): parts = response.candidates[0].content.parts fc_ids = {} for i, part in enumerate(parts): prefix = f" Part {i:2d}:" if hasattr(part, "tool_call") and part.tool_call: tc = part.tool_call print(f"{prefix} [toolCall] type={tc.tool_type} id={tc.id}") if hasattr(part, "tool_response") and part.tool_response: tr = part.tool_response print(f"{prefix} [toolResponse] type={tr.tool_type} id={tr.id}") if hasattr(part, "executable_code") and part.executable_code: code = part.executable_code.code[:90].replace("\n", " ↵ ") print(f"{prefix} [executableCode] {code}...") if hasattr(part, "code_execution_result") and part.code_execution_result: out = (part.code_execution_result.output or "")[:90] print(f"{prefix} [codeExecResult] {out}") if hasattr(part, "function_call") and part.function_call: fc = part.function_call fc_ids[fc.name] = fc.id print(f"{prefix} [functionCall] name={fc.name} id={fc.id}") print(f" └─ args: {dict(fc.args)}") if hasattr(part, "text") and part.text: snippet = part.text[:110].replace("\n", " ") print(f"{prefix} [text] {snippet}...") if hasattr(part, "thought_signature") and part.thought_signature: print(f" └─ thought_signature present ✓") return fc_ids

[데모 1 시작] heading("DEMO 1: Combine Google Search + Custom Function in One Request") print(""" 이 데모는 핵심 신규 기능을 보여줍니다: 내장 툴(Google Search)과 사용자 정의 함수 선언을 단일 API 호출에서 함께 전달하는 것입니다. Gemini는 다음과 같이 동작합니다: 턴 1 → 웹에서 실시간 정보를 검색한 후, 날씨 데이터를 가져오기 위해 사용자 정의 함수를 요청합니다. 턴 2 → 우리가 함수 응답을 제공하면, Gemini가 모든 것을 종합합니다.

핵심 포인트: • google_search와 function_declarations가 동일한 Tool 객체에 들어갑니다. • include_server_side_tool_invocations는 반드시 True여야 합니다 (ToolConfig에서). • 후속 턴에서 모든 파트(thought_signatures 포함)를 반환해야 합니다. """)

[함수 선언] get_weather_func = types.FunctionDeclaration( name="getWeather", description="Gets the current weather for a requested city.", parameters=types.Schema( type="OBJECT", properties={ "city": types.Schema( type="STRING", description="The city and state, e.g. Utqiagvik, Alaska", ), }, required=["city"], ), )

print("▶ Turn 1: Google Search + getWeather 툴과 함께 프롬프트 전송 중...\n") response_1 = client.mo...

원문 보기
원문 보기 (영어)
Editors Pick Agentic AI Software Engineering Staff Tutorials In this tutorial, we explore the latest Gemini API tooling updates Google announced in March 2026, specifically the ability to combine built-in tools like Google Search and Google Maps with custom function calls in a single API request . We walk through five hands-on demos that progressively build on each other, starting with the core tool combination feature and ending with a full multi-tool agentic chain. Along the way, we demonstrate how context circulation preserves every tool call and response across turns, enabling the model to reason over prior outputs; how unique tool response IDs let us map parallel function calls to their exact results; and how Grounding with Google Maps brings real-time location data into our applications. We use gemini-3-flash-preview for tool combination features and gemini-2.5-flash for Maps grounding, so everything we build here runs without any billing setup. Copy Code Copied Use a different Browser import subprocess, sys subprocess.check_call( [sys.executable, "-m", "pip", "install", "-qU", "google-genai"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, ) import getpass, json, textwrap, os, time from google import genai from google.genai import types if "GOOGLE_API_KEY" not in os.environ: os.environ["GOOGLE_API_KEY"] = getpass.getpass("Enter your Gemini API key: ") client = genai.Client(api_key=os.environ["GOOGLE_API_KEY"]) TOOL_COMBO_MODEL = "gemini-3-flash-preview" MAPS_MODEL = "gemini-2.5-flash" DIVIDER = "=" * 72 def heading(title: str): print(f"\n{DIVIDER}") print(f" {title}") print(DIVIDER) def wrap(text: str, width: int = 80): for line in text.splitlines(): print(textwrap.fill(line, width=width) if line.strip() else "") def describe_parts(response): parts = response.candidates[0].content.parts fc_ids = {} for i, part in enumerate(parts): prefix = f" Part {i:2d}:" if hasattr(part, "tool_call") and part.tool_call: tc = part.tool_call print(f"{prefix} [toolCall] type={tc.tool_type} id={tc.id}") if hasattr(part, "tool_response") and part.tool_response: tr = part.tool_response print(f"{prefix} [toolResponse] type={tr.tool_type} id={tr.id}") if hasattr(part, "executable_code") and part.executable_code: code = part.executable_code.code[:90].replace("\n", " ↵ ") print(f"{prefix} [executableCode] {code}...") if hasattr(part, "code_execution_result") and part.code_execution_result: out = (part.code_execution_result.output or "")[:90] print(f"{prefix} [codeExecResult] {out}") if hasattr(part, "function_call") and part.function_call: fc = part.function_call fc_ids[fc.name] = fc.id print(f"{prefix} [functionCall] name={fc.name} id={fc.id}") print(f" └─ args: {dict(fc.args)}") if hasattr(part, "text") and part.text: snippet = part.text[:110].replace("\n", " ") print(f"{prefix} [text] {snippet}...") if hasattr(part, "thought_signature") and part.thought_signature: print(f" └─ thought_signature present ✓") return fc_ids heading("DEMO 1: Combine Google Search + Custom Function in One Request") print(""" This demo shows the flagship new feature: passing BOTH a built-in tool (Google Search) and a custom function declaration in a single API call. Gemini will: Turn 1 → Search the web for real-time info, then request our custom function to get weather data. Turn 2 → We supply the function response; Gemini synthesizes everything. Key points: • google_search and function_declarations go in the SAME Tool object • include_server_side_tool_invocations must be True (on ToolConfig) • Return ALL parts (incl. thought_signatures) in subsequent turns """) get_weather_func = types.FunctionDeclaration( name="getWeather", description="Gets the current weather for a requested city.", parameters=types.Schema( type="OBJECT", properties={ "city": types.Schema( type="STRING", description="The city and state, e.g. Utqiagvik, Alaska", ), }, required=["city"], ), ) print("▶ Turn 1: Sending prompt with Google Search + getWeather tools...\n") response_1 = client.models.generate_content( model=TOOL_COMBO_MODEL, 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(), function_declarations=[get_weather_func], ), ], tool_config=types.ToolConfig( include_server_side_tool_invocations=True, ), ), ) print(" Parts returned by the model:\n") fc_ids = describe_parts(response_1) function_call_id = fc_ids.get("getWeather") print(f"\n ✅ Captured function_call id for getWeather: {function_call_id}") print("\n▶ Turn 2: Returning function result & requesting final synthesis...\n") 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_1.candidates[0].content, types.Content( role="user", parts=[ types.Part( function_response=types.FunctionResponse( name="getWeather", response={"response": "Very cold. 22°F / -5.5°C with strong Arctic winds."}, id=function_call_id, ) ) ], ), ] response_2 = client.models.generate_content( model=TOOL_COMBO_MODEL, contents=history, config=types.GenerateContentConfig( tools=[ types.Tool( google_search=types.GoogleSearch(), function_declarations=[get_weather_func], ), ], tool_config=types.ToolConfig( include_server_side_tool_invocations=True, ), ), ) print(" ✅ Final synthesized response:\n") for part in response_2.candidates[0].content.parts: if hasattr(part, "text") and part.text: wrap(part.text) we install the Google GenAI SDK, securely capture our API key, and define the helper functions that power the rest of the tutorial. We then demonstrate the flagship tool combination feature by sending a single request that pairs Google Search with a custom getWeather function, letting Gemini search the web for real-time geographic data and simultaneously request weather information from our custom tool. We complete the two-turn flow by returning our simulated weather response with the matching function call ID and watching Gemini synthesize both data sources into one coherent answer. Copy Code Copied Use a different Browser heading("DEMO 2: Tool Response IDs for Parallel Function Calls") print(""" When Gemini makes multiple function calls in one turn, each gets a unique `id` field. You MUST return each function_response with its matching id so the model maps results correctly. This is critical for parallel calls. """) time.sleep(2) lookup_inventory = types.FunctionDeclaration( name="lookupInventory", description="Check product inventory by SKU.", parameters=types.Schema( type="OBJECT", properties={ "sku": types.Schema(type="STRING", description="Product SKU code"), }, required=["sku"], ), ) get_shipping_estimate = types.FunctionDeclaration( name="getShippingEstimate", description="Get shipping time estimate for a destination zip code.", parameters=types.Schema( type="OBJECT", properties={ "zip_code": types.Schema(type="STRING", description="Destination ZIP code"), "sku": types.Schema(type="STRING", description="Product SKU"), }, required=["zip_code", "sku"], ), ) print("▶ Turn 1: Asking about product availability + shipping...\n") resp_parallel = client.models.generate_content( model=TOOL_COMBO_MODEL, contents=( "I want to buy SKU-A100 (wireless headphones). " "Is it in stock, and how fast can it ship to ZIP 90210?" ), config=types.GenerateContentConfig( tools=[ types.Tool( function_declarations=[lookup_inventory, get_shipping_estimate], ), ], ), ) fc_parts = [] for part in resp_parallel.candidates[0].content.parts: if hasattr(part, "function_call") and part.function_call: fc = part.function_call fc_parts.append(fc) print(f" [functionCall] name={fc.name} id={fc.id} args={dict(fc.args)}") print("\n▶ Turn 2: Returning results with matching IDs...\n") simulated_results = { "lookupInventory": {"in_stock": True, "quantity": 342, "warehouse": "Los Angeles"}, "getShippingEstimate": {"days": 2, "carrier": "FedEx", "cost": "$5.99