Python 88.3%
TypeScript 7.6%
Shell 4.1%
1"""Smoke tests for Gemini tools in generateContent (API Atlas).23Cheap tests (always run, < $0.002 total on gemini-3.5-flash-lite): forced function call (mode ANY) + functionResponse round trip,4thoughtSignature is mandatory on echo (400), parallel calls, parametersJsonSchema, mode NONE, behavior NON_BLOCKING rejected outside5Live (400), code execution parts, URL context metadata, Google Maps grounding chunks.6Expensive / restricted (RUN_EXPENSIVE_TESTS=true): Google Search grounding (429 RESOURCE_EXHAUSTED on this key on 2026-09-18 →7ACCOUNT_RESTRICTED; the test accepts 200 or 429), legacy computerUse on gemini-2.5-computer-use-preview-10-2025 (429 free-tier quota 0).8Reference run: 2026-09-18 (tmp-live/gemini-tools/*.json).9"""10from __future__ import annotations1112import pytest1314MODEL = "gemini-3.5-flash-lite"15WEATHER = {"name": "get_weather", "description": "Get the current weather for a city.",16 "parameters": {"type": "OBJECT", "properties": {"city": {"type": "STRING"}}, "required": ["city"]}}17TIME = {"name": "get_time", "description": "Get the current local time in a city.",18 "parameters": {"type": "OBJECT", "properties": {"city": {"type": "STRING"}}, "required": ["city"]}}192021def _gen(gemini, body, note="", model=MODEL):22 st, r, _ = gemini("POST", f"/v1beta/models/{model}:generateContent", body, note=f"test_tools {note}")23 return st, r242526def parts(r):27 return r["candidates"][0]["content"]["parts"]282930# ----------------------------------------------------------------------------- cheap31def test_forced_function_call_then_response(gemini):32 user = {"role": "user", "parts": [{"text": "Weather in Paris?"}]}33 st, r = _gen(gemini, {"contents": [user], "tools": [{"functionDeclarations": [WEATHER]}],34 "toolConfig": {"functionCallingConfig": {"mode": "ANY", "allowedFunctionNames": ["get_weather"]}}, "generationConfig": {"maxOutputTokens": 64}}, "forced")35 assert st == 20036 fc = [p for p in parts(r) if "functionCall" in p]37 assert len(fc) == 1 and fc[0]["functionCall"]["name"] == "get_weather" and "city" in fc[0]["functionCall"]["args"]38 assert fc[0]["functionCall"]["id"].startswith("call_") and fc[0].get("thoughtSignature")39 model_turn = r["candidates"][0]["content"]40 st2, r2 = _gen(gemini, {"contents": [user, model_turn, {"role": "user", "parts": [{"functionResponse": {"name": "get_weather", "response": {"temperature_c": 18}}}]}],41 "tools": [{"functionDeclarations": [WEATHER]}], "generationConfig": {"maxOutputTokens": 32}}, "functionResponse")42 assert st2 == 200 and any("text" in p for p in parts(r2))43 # dropping the thoughtSignature is rejected44 stripped = {"role": "model", "parts": [{k: v for k, v in p.items() if k != "thoughtSignature"} for p in model_turn["parts"]]}45 st3, r3 = _gen(gemini, {"contents": [user, stripped, {"role": "user", "parts": [{"functionResponse": {"name": "get_weather", "response": {"temperature_c": 18}}}]}],46 "tools": [{"functionDeclarations": [WEATHER]}], "generationConfig": {"maxOutputTokens": 32}}, "no thoughtSignature")47 assert st3 == 400 and "thought_signature" in r3["error"]["message"]484950def test_parallel_function_calls(gemini):51 st, r = _gen(gemini, {"contents": [{"role": "user", "parts": [{"text": "What is the weather in Paris and the time in Tokyo? Use both tools."}]}],52 "tools": [{"functionDeclarations": [WEATHER, TIME]}], "toolConfig": {"functionCallingConfig": {"mode": "ANY"}}, "generationConfig": {"maxOutputTokens": 128}}, "parallel")53 assert st == 20054 names = sorted(p["functionCall"]["name"] for p in parts(r) if "functionCall" in p)55 assert names == ["get_time", "get_weather"]565758def test_parameters_json_schema_and_mode_none(gemini):59 decl = {"name": "get_weather", "parametersJsonSchema": {"type": "object", "properties": {"city": {"type": "string"}, "unit": {"type": "string", "enum": ["C", "F"]}}, "required": ["city"]}}60 st, r = _gen(gemini, {"contents": [{"role": "user", "parts": [{"text": "Weather in Rome in Celsius?"}]}], "tools": [{"functionDeclarations": [decl]}],61 "toolConfig": {"functionCallingConfig": {"mode": "ANY"}}, "generationConfig": {"maxOutputTokens": 64}}, "parametersJsonSchema")62 assert st == 200 and any(p.get("functionCall", {}).get("args", {}).get("city") for p in parts(r))63 st, r = _gen(gemini, {"contents": [{"role": "user", "parts": [{"text": "Weather in Paris? 3 words."}]}], "tools": [{"functionDeclarations": [WEATHER]}],64 "toolConfig": {"functionCallingConfig": {"mode": "NONE"}}, "generationConfig": {"maxOutputTokens": 32}}, "mode NONE")65 assert st == 200 and not any("functionCall" in p for p in parts(r))666768def test_behavior_non_blocking_is_live_only(gemini):69 st, r = _gen(gemini, {"contents": [{"role": "user", "parts": [{"text": "Weather in Paris?"}]}], "tools": [{"functionDeclarations": [dict(WEATHER, behavior="NON_BLOCKING")]}],70 "generationConfig": {"maxOutputTokens": 16}}, "NON_BLOCKING")71 assert st == 400 and "BidiGenerateContent" in r["error"]["message"]727374def test_code_execution_parts(gemini):75 st, r = _gen(gemini, {"contents": [{"parts": [{"text": "Run print(2+2) in Python and report the output."}]}], "tools": [{"codeExecution": {}}], "generationConfig": {"maxOutputTokens": 128}}, "codeExecution")76 assert st == 20077 kinds = [k for p in parts(r) for k in p if k != "thoughtSignature"]78 assert "executableCode" in kinds and "codeExecutionResult" in kinds79 res = next(p["codeExecutionResult"] for p in parts(r) if "codeExecutionResult" in p)80 assert res["outcome"] == "OUTCOME_OK" and "4" in res["output"]81 assert r["usageMetadata"].get("toolUsePromptTokenCount", 0) > 0828384def test_url_context_metadata(gemini):85 st, r = _gen(gemini, {"contents": [{"parts": [{"text": "Summarize https://example.com in 5 words."}]}], "tools": [{"urlContext": {}}], "generationConfig": {"maxOutputTokens": 48}}, "urlContext")86 assert st == 20087 md = r["candidates"][0]["urlContextMetadata"]["urlMetadata"]88 assert md[0]["retrievedUrl"].startswith("https://example.com") and md[0]["urlRetrievalStatus"].startswith("URL_RETRIEVAL_STATUS_")899091def test_google_maps_grounding(gemini):92 st, r = _gen(gemini, {"contents": [{"parts": [{"text": "Name one cafe near me. 5 words."}]}], "tools": [{"googleMaps": {}}],93 "toolConfig": {"retrievalConfig": {"latLng": {"latitude": 43.6532, "longitude": -79.3832}}}, "generationConfig": {"maxOutputTokens": 48}}, "googleMaps")94 assert st == 20095 gm = r["candidates"][0].get("groundingMetadata")96 if not gm or not gm.get("groundingChunks"):97 pytest.skip("model answered without Maps grounding this time (observed intermittently on 2026-09-19)")98 assert "maps" in gm["groundingChunks"][0] and {"uri", "title", "placeId"} <= set(gm["groundingChunks"][0]["maps"])99100101# ----------------------------------------------------------------------------- expensive / restricted102@pytest.mark.run_expensive_tests103def test_google_search_grounding(gemini):104 st, r = _gen(gemini, {"contents": [{"parts": [{"text": "What is today's date in Toronto? 5 words."}]}], "tools": [{"googleSearch": {}}], "generationConfig": {"maxOutputTokens": 48}}, "googleSearch")105 assert st in (200, 429), r106 if st == 429:107 pytest.skip("ACCOUNT_RESTRICTED: Search grounding quota is 0 on this key (RESOURCE_EXHAUSTED, 2026-09-18)")108 gm = r["candidates"][0]["groundingMetadata"]109 assert "webSearchQueries" in gm and gm["groundingChunks"][0]["web"]["uri"]110111112@pytest.mark.run_expensive_tests113def test_legacy_computer_use_model(gemini):114 st, r = _gen(gemini, {"contents": [{"role": "user", "parts": [{"text": "Do nothing."}]}], "tools": [{"computerUse": {"environment": "ENVIRONMENT_BROWSER"}}],115 "generationConfig": {"maxOutputTokens": 32}}, "computerUse legacy", model="gemini-2.5-computer-use-preview-10-2025")116 assert st in (200, 429), r117 if st == 429:118 pytest.skip("ACCOUNT_RESTRICTED: free-tier quota 0 for computer-use-preview (2026-09-18)")119