"""测试 nano_banana — Router 调用 Gemini 图模(HTTP generateContent) 前提: - data/registry.json + data/sources.json 已注册 tool_id=nano_banana - tools/local/nano_banana 已提供 POST /generate,且 .env 中配置 GEMINI_API_KEY 用法: 1. uv run python -m tool_agent 2. uv run python tests/test_nano_banana.py 模型切换(任选其一): - 不传 NANO_BANANA_MODEL:请求体不含 model,由工具侧默认(如 gemini-2.5-flash-image / 环境变量 GEMINI_IMAGE_MODEL) - 显式切换预览图模: NANO_BANANA_MODEL=gemini-3.1-flash-image-preview uv run python tests/test_nano_banana.py 环境变量: TOOL_AGENT_ROUTER_URL 默认 http://127.0.0.1:8001 NANO_BANANA_TOOL_ID 默认 nano_banana NANO_BANANA_TEST_PROMPT 覆盖默认短提示词 NANO_BANANA_MODEL 非空时作为 params["model"] 传给 /run_tool """ import io import os import sys from typing import Any if sys.platform == "win32": _out = sys.stdout if isinstance(_out, io.TextIOWrapper): _out.reconfigure(encoding="utf-8") import httpx ROUTER_URL = os.environ.get("TOOL_AGENT_ROUTER_URL", "http://127.0.0.1:8001") TOOL_ID = os.environ.get("NANO_BANANA_TOOL_ID", "nano_banana") NANO_BANANA_MODEL = os.environ.get("NANO_BANANA_MODEL", "").strip() TEST_PROMPT = os.environ.get( "NANO_BANANA_TEST_PROMPT", "A minimal flat icon of a yellow banana on white background, no text", ) def run_tool(params: dict[str, Any], timeout: float = 180.0) -> dict[str, Any]: resp = httpx.post( f"{ROUTER_URL}/run_tool", json={"tool_id": TOOL_ID, "params": params}, timeout=timeout, ) resp.raise_for_status() body = resp.json() if body.get("status") != "success": raise RuntimeError(body.get("error") or str(body)) result = body.get("result") if isinstance(result, dict) and result.get("status") == "error": raise RuntimeError(result.get("error", str(result))) return result if isinstance(result, dict) else {} def _has_image_payload(data: dict[str, Any]) -> bool: if not data: return False if data.get("images"): return True if data.get("image") and isinstance(data["image"], str) and len(data["image"]) > 100: return True if data.get("image_base64"): return True cands = data.get("candidates") if isinstance(cands, list) and cands: parts = cands[0].get("content", {}).get("parts", []) for p in parts: if isinstance(p, dict) and (p.get("inlineData") or p.get("inline_data")): return True return False def main(): print("=" * 50) print("测试 nano_banana(Gemini 图模,可切换 model)") print("=" * 50) print(f"ROUTER_URL: {ROUTER_URL}") print(f"tool_id: {TOOL_ID}") if NANO_BANANA_MODEL: print(f"model: {NANO_BANANA_MODEL}(经 params 传入)") else: print("model: (未传,使用工具默认 / GEMINI_IMAGE_MODEL)") try: r = httpx.get(f"{ROUTER_URL}/health", timeout=3) print(f"Router 状态: {r.json()}") except httpx.ConnectError: print(f"无法连接 Router ({ROUTER_URL}),请先: uv run python -m tool_agent") sys.exit(1) print("\n--- 校验工具已注册 ---") tr = httpx.get(f"{ROUTER_URL}/tools", timeout=30) tr.raise_for_status() tools = tr.json().get("tools", []) ids = {t["tool_id"] for t in tools} if TOOL_ID not in ids: print(f"错误: {TOOL_ID!r} 不在 GET /tools 中。当前示例: {sorted(ids)[:15]}...") sys.exit(1) meta = next(t for t in tools if t["tool_id"] == TOOL_ID) print(f" {TOOL_ID}: {meta.get('name', '')} (state={meta.get('state')})") props = (meta.get("input_schema") or {}).get("properties") or {} if "model" in props: print(" input_schema 已声明 model(注册与实现应对齐)") else: print(" 提示: input_schema 尚无 model 字段,注册表宜补充以便编排知晓可切换模型") params: dict[str, Any] = {"prompt": TEST_PROMPT} if NANO_BANANA_MODEL: params["model"] = NANO_BANANA_MODEL print("\n--- 调用生图 ---") print(f"prompt: {TEST_PROMPT[:80]}{'...' if len(TEST_PROMPT) > 80 else ''}") try: data = run_tool(params, timeout=180.0) except (RuntimeError, httpx.HTTPError) as e: print(f"错误: {e}") sys.exit(1) print(f"\n下游返回 keys: {list(data.keys())[:20]}") if rm := data.get("model"): print(f"下游报告 model: {rm}") if NANO_BANANA_MODEL and rm != NANO_BANANA_MODEL: print( f"警告: 请求 model={NANO_BANANA_MODEL!r} 与返回 model={rm!r} 不一致(若工具会规范化 ID 可忽略)" ) if _has_image_payload(data): print("\n检测到图片相关字段,测试通过!") return print("\n未识别到常见图片字段(images / image / candidates[].inlineData 等)。") print(f"完整结果(截断): {str(data)[:800]}") sys.exit(1) if __name__ == "__main__": main()