SPB Git forge

spb/doc-api

Public
2commits 1branches 0releases
15.7 MBsize
maindefault branch
13 days agolast push
Python 88.3% TypeScript 7.6% Shell 4.1%
3.8 KB · 67 lines python
Raw Blame History
1#!/usr/bin/env python32"""Generic Gemini generateContent tool loop (functionDeclarations → functionCall parts → functionResponse parts).34STATUS: LIVE_VERIFIED 2026-09-18 — run with `.venv/bin/python examples/shared/tool-loop/gemini_tool_loop.py` (gemini-3.5-flash-lite,5one get_weather + get_time parallel round trip, ≈ 300 tokens, < $0.001). Keys come from .env via scripts/live.py; every request is6logged to reports/live-requests.jsonl.78Patterns implemented (see docs/gemini/tool-loop.md):9  * loop until the model turn contains no functionCall part10  * append the model turn VERBATIM (functionCall parts carry `thoughtSignature`; dropping it → HTTP 40011    "Function call is missing a thought_signature"; only the first parallel call carries one)12  * execute ALL calls of a turn, answer each with a functionResponse{name, response} part (the same `id` order); the whole13    functionResponse batch goes in ONE user Content14  * bounded turns; errors are returned to the model as {"error": …} instead of raising15  * Interactions API variant: same loop but statelessly chained with previous_interaction_id (see examples/gemini/interactions/interactions_tools.py)16"""17from __future__ import annotations1819import json20import sys21from pathlib import Path22from typing import Any, Callable2324ROOT = Path(__file__).resolve().parents[3]25sys.path.insert(0, str(ROOT))26from scripts.live import gemini_request  # noqa: E4022728MODEL = "gemini-3.5-flash-lite"293031def run_tool_loop(prompt: str, declarations: list[dict], handlers: dict[str, Callable[[dict], Any]], max_turns: int = 6,32                  system: str | None = None) -> str:33    contents: list[dict] = [{"role": "user", "parts": [{"text": prompt}]}]34    body: dict = {"contents": contents, "tools": [{"functionDeclarations": declarations}], "generationConfig": {"maxOutputTokens": 256}}35    if system:36        body["systemInstruction"] = {"parts": [{"text": system}]}37    for turn in range(max_turns):38        st, r, _ = gemini_request("POST", f"/v1beta/models/{MODEL}:generateContent", body, note=f"gemini_tool_loop turn {turn}")39        if st != 200:40            raise RuntimeError(f"HTTP {st}: {json.dumps(r)[:200]}")41        cand = r["candidates"][0]42        model_turn = cand["content"]                       # keep parts verbatim (thoughtSignature!)43        calls = [p["functionCall"] for p in model_turn.get("parts", []) if "functionCall" in p]44        if not calls:45            return "".join(p.get("text", "") for p in model_turn.get("parts", []))46        responses = []47        for c in calls:48            try:49                out = handlers[c["name"]](c.get("args", {}))50            except Exception as e:  # noqa: BLE00151                out = {"error": str(e)}52            fr = {"name": c["name"], "response": out if isinstance(out, dict) else {"result": out}}53            if c.get("id"):54                fr["id"] = c["id"]55            responses.append({"functionResponse": fr})56            print(f"  turn {turn}: {c['name']}({json.dumps(c.get('args'))}) -> {json.dumps(fr['response'])[:80]}")57        contents.extend([model_turn, {"role": "user", "parts": responses}])58    return "(max turns reached)"596061if __name__ == "__main__":62    decls = [{"name": "get_weather", "description": "Current weather for a city.", "parameters": {"type": "OBJECT", "properties": {"city": {"type": "STRING"}}, "required": ["city"]}},63             {"name": "get_time", "description": "Current local time in a city.", "parameters": {"type": "OBJECT", "properties": {"city": {"type": "STRING"}}, "required": ["city"]}}]64    handlers = {"get_weather": lambda a: {"city": a["city"], "temperature_c": 18, "conditions": "light rain"},65                "get_time": lambda a: {"city": a["city"], "time": "14:05"}}66    print(run_tool_loop("Weather in Paris and time in Tokyo? Use the tools, then answer in one sentence.", decls, handlers))67