mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-12 08:42:18 +00:00
* fix(gemini): send Google's current Managed Agents fields on create and forward agent_config on interactions * fix(gemini): accept id on managed agent get, delete, and list_versions * fix(gemini): drop unsupported agent fields under drop_params and keep agent_config keyword-only A model interaction drops agent_config under drop_params, agent create drops wrongly typed optional fields under drop_params and otherwise answers 400 naming the field the caller sent, and agent_config on interactions create is keyword-only so positional response settings stay in place * fix(gemini): drop a non-object agent_config under drop_params and name the id alias the caller sent * fix(interactions): reject agent_config on bridged providers and drop a typeless one under drop_params Bridged providers answer 400 for agent_config without drop_params and drop it with drop_params. A Gemini agent run drops an agent_config without a string type under drop_params. The Gemini agents wire test expects Google's current upstream field names. * fix(interactions): honor additional_drop_params for agent_config on bridged providers * fix(agents): drop a clashing instructions alias under drop_params --------- Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
603 lines
23 KiB
Python
603 lines
23 KiB
Python
"""
|
|
Unit tests for litellm/interactions/agents/utils.py and main.py
|
|
focused on the managed agents SDK surface added in the
|
|
"Gemini managed agents support" PR.
|
|
|
|
The tests mock the underlying HTTP handler so they cover the public
|
|
sync + async create/list/get/delete/list_versions entry points and the
|
|
small helper utilities without touching the network.
|
|
"""
|
|
|
|
import asyncio
|
|
import json
|
|
from collections.abc import Callable, Mapping
|
|
from typing import Final
|
|
from unittest.mock import AsyncMock, MagicMock, patch
|
|
|
|
import httpx
|
|
import pytest
|
|
import respx
|
|
|
|
|
|
import litellm
|
|
from litellm.interactions.agents import (
|
|
acreate,
|
|
adelete,
|
|
aget,
|
|
alist,
|
|
alist_versions,
|
|
create,
|
|
delete,
|
|
get,
|
|
list as list_agents,
|
|
list_versions,
|
|
)
|
|
from litellm.interactions.agents.main import (
|
|
_get_agents_api_config,
|
|
_make_logging_obj,
|
|
)
|
|
from litellm.interactions.agents.utils import get_provider_agents_api_config
|
|
from litellm.llms.base_llm.agents.transformation import BaseAgentsAPIConfig
|
|
from litellm.llms.gemini.agents.transformation import GeminiAgentsConfig
|
|
|
|
|
|
_HANDLER_PATH = "litellm.interactions.agents.main.agents_http_handler"
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# utils.get_provider_agents_api_config
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestGetProviderAgentsApiConfig:
|
|
def test_returns_gemini_config_for_gemini(self):
|
|
cfg = get_provider_agents_api_config("gemini")
|
|
assert isinstance(cfg, GeminiAgentsConfig)
|
|
assert isinstance(cfg, BaseAgentsAPIConfig)
|
|
|
|
@pytest.mark.parametrize(
|
|
"provider", ["openai", "anthropic", "bedrock", "vertex_ai", "unknown"]
|
|
)
|
|
def test_returns_none_for_non_gemini(self, provider):
|
|
assert get_provider_agents_api_config(provider) is None
|
|
|
|
def test_returns_none_for_none(self):
|
|
assert get_provider_agents_api_config(None) is None
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# main._get_agents_api_config
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestGetAgentsApiConfig:
|
|
def test_returns_config_for_gemini(self):
|
|
cfg = _get_agents_api_config("gemini")
|
|
assert isinstance(cfg, GeminiAgentsConfig)
|
|
|
|
def test_raises_bad_request_for_unsupported_provider(self):
|
|
with pytest.raises(litellm.BadRequestError) as excinfo:
|
|
_get_agents_api_config("openai")
|
|
assert "does not have a native" in str(excinfo.value)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# main._make_logging_obj
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestMakeLoggingObj:
|
|
def test_calls_update_from_kwargs_and_returns_same_obj(self):
|
|
logging_obj = MagicMock()
|
|
kwargs = {"litellm_logging_obj": logging_obj, "litellm_call_id": "abc-123"}
|
|
|
|
returned = _make_logging_obj(
|
|
kwargs=kwargs,
|
|
model="my-agent",
|
|
custom_llm_provider="gemini",
|
|
call_type="create_agent",
|
|
optional_params={"foo": "bar"},
|
|
)
|
|
|
|
assert returned is logging_obj
|
|
logging_obj.update_from_kwargs.assert_called_once()
|
|
kwargs_call = logging_obj.update_from_kwargs.call_args.kwargs
|
|
assert kwargs_call["model"] == "my-agent"
|
|
assert kwargs_call["optional_params"] == {"foo": "bar"}
|
|
assert kwargs_call["custom_llm_provider"] == "gemini"
|
|
assert kwargs_call["litellm_params"]["litellm_call_id"] == "abc-123"
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Sync entry points: create / list / get / delete / list_versions
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def _stub_handler(return_value):
|
|
"""Build a stub AgentsHTTPHandler whose CRUD methods return *return_value*."""
|
|
handler = MagicMock()
|
|
handler.create_agent.return_value = return_value
|
|
handler.list_agents.return_value = return_value
|
|
handler.get_agent.return_value = return_value
|
|
handler.delete_agent.return_value = return_value
|
|
handler.list_agent_versions.return_value = return_value
|
|
return handler
|
|
|
|
|
|
class TestSyncEntryPoints:
|
|
def test_create_passes_args_to_handler(self):
|
|
sentinel = MagicMock(name="create_response")
|
|
with patch(_HANDLER_PATH, _stub_handler(sentinel)) as handler:
|
|
response = create(
|
|
name="waverunner",
|
|
base_agent="gemini-2.5-flash",
|
|
instructions="be helpful",
|
|
base_environment={"type": "remote"},
|
|
custom_llm_provider="gemini",
|
|
api_key="AIza-test",
|
|
extra_headers={"X-Test": "1"},
|
|
extra_body={"foo": "bar"},
|
|
)
|
|
|
|
assert response is sentinel
|
|
handler.create_agent.assert_called_once()
|
|
kw = handler.create_agent.call_args.kwargs
|
|
assert kw["create_request"].id == "waverunner"
|
|
assert kw["_is_async"] is False
|
|
assert kw["extra_headers"] == {"X-Test": "1"}
|
|
assert kw["extra_body"] == {"foo": "bar"}
|
|
assert isinstance(kw["agents_api_config"], GeminiAgentsConfig)
|
|
|
|
def test_create_defaults_custom_llm_provider_to_gemini(self):
|
|
sentinel = MagicMock(name="create_response")
|
|
with patch(_HANDLER_PATH, _stub_handler(sentinel)) as handler:
|
|
create(name="agent-x", api_key="AIza")
|
|
assert handler.create_agent.call_args.kwargs["_is_async"] is False
|
|
cfg = handler.create_agent.call_args.kwargs["agents_api_config"]
|
|
assert isinstance(cfg, GeminiAgentsConfig)
|
|
|
|
def test_create_raises_for_unsupported_provider(self):
|
|
with pytest.raises(litellm.exceptions.BadRequestError):
|
|
create(name="agent-x", custom_llm_provider="openai", api_key="sk-x")
|
|
|
|
def test_list_passes_args_to_handler(self):
|
|
sentinel = MagicMock(name="list_response")
|
|
with patch(_HANDLER_PATH, _stub_handler(sentinel)) as handler:
|
|
response = list_agents(custom_llm_provider="gemini", api_key="AIza")
|
|
assert response is sentinel
|
|
handler.list_agents.assert_called_once()
|
|
assert handler.list_agents.call_args.kwargs["_is_async"] is False
|
|
|
|
def test_get_passes_args_to_handler(self):
|
|
sentinel = MagicMock(name="get_response")
|
|
with patch(_HANDLER_PATH, _stub_handler(sentinel)) as handler:
|
|
response = get(name="waverunner", api_key="AIza")
|
|
assert response is sentinel
|
|
kw = handler.get_agent.call_args.kwargs
|
|
assert kw["name"] == "waverunner"
|
|
assert kw["_is_async"] is False
|
|
|
|
def test_delete_passes_args_to_handler(self):
|
|
sentinel = MagicMock(name="delete_response")
|
|
with patch(_HANDLER_PATH, _stub_handler(sentinel)) as handler:
|
|
response = delete(name="waverunner", api_key="AIza")
|
|
assert response is sentinel
|
|
kw = handler.delete_agent.call_args.kwargs
|
|
assert kw["name"] == "waverunner"
|
|
assert kw["_is_async"] is False
|
|
|
|
def test_list_versions_passes_args_to_handler(self):
|
|
sentinel = MagicMock(name="versions_response")
|
|
with patch(_HANDLER_PATH, _stub_handler(sentinel)) as handler:
|
|
response = list_versions(name="waverunner", api_key="AIza")
|
|
assert response is sentinel
|
|
kw = handler.list_agent_versions.call_args.kwargs
|
|
assert kw["name"] == "waverunner"
|
|
assert kw["_is_async"] is False
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Async entry points
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestAsyncEntryPoints:
|
|
"""Async entry points delegate to their sync counterparts via run_in_executor."""
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_acreate_dispatches_with_async_flag(self):
|
|
sentinel = MagicMock(name="acreate_response")
|
|
|
|
def fake_create_agent(**kwargs):
|
|
assert kwargs["_is_async"] is True
|
|
assert kwargs["create_request"].id == "waverunner"
|
|
return sentinel
|
|
|
|
handler = MagicMock()
|
|
handler.create_agent.side_effect = fake_create_agent
|
|
|
|
with patch(_HANDLER_PATH, handler):
|
|
response = await acreate(
|
|
name="waverunner",
|
|
base_agent="gemini-2.5-flash",
|
|
api_key="AIza",
|
|
)
|
|
assert response is sentinel
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_acreate_awaits_coroutine_result(self):
|
|
async def _coro():
|
|
return "async-value"
|
|
|
|
handler = MagicMock()
|
|
handler.create_agent.return_value = _coro()
|
|
|
|
with patch(_HANDLER_PATH, handler):
|
|
response = await acreate(name="waverunner", api_key="AIza")
|
|
|
|
assert response == "async-value"
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_alist_dispatches_with_async_flag(self):
|
|
sentinel = MagicMock(name="alist_response")
|
|
|
|
def fake_list_agents(**kwargs):
|
|
assert kwargs["_is_async"] is True
|
|
return sentinel
|
|
|
|
handler = MagicMock()
|
|
handler.list_agents.side_effect = fake_list_agents
|
|
|
|
with patch(_HANDLER_PATH, handler):
|
|
response = await alist(api_key="AIza")
|
|
assert response is sentinel
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_aget_dispatches_with_async_flag(self):
|
|
sentinel = MagicMock(name="aget_response")
|
|
|
|
def fake_get_agent(**kwargs):
|
|
assert kwargs["_is_async"] is True
|
|
assert kwargs["name"] == "waverunner"
|
|
return sentinel
|
|
|
|
handler = MagicMock()
|
|
handler.get_agent.side_effect = fake_get_agent
|
|
|
|
with patch(_HANDLER_PATH, handler):
|
|
response = await aget(name="waverunner", api_key="AIza")
|
|
assert response is sentinel
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_adelete_dispatches_with_async_flag(self):
|
|
sentinel = MagicMock(name="adelete_response")
|
|
|
|
def fake_delete_agent(**kwargs):
|
|
assert kwargs["_is_async"] is True
|
|
assert kwargs["name"] == "waverunner"
|
|
return sentinel
|
|
|
|
handler = MagicMock()
|
|
handler.delete_agent.side_effect = fake_delete_agent
|
|
|
|
with patch(_HANDLER_PATH, handler):
|
|
response = await adelete(name="waverunner", api_key="AIza")
|
|
assert response is sentinel
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_alist_versions_dispatches_with_async_flag(self):
|
|
sentinel = MagicMock(name="alist_versions_response")
|
|
|
|
def fake_versions(**kwargs):
|
|
assert kwargs["_is_async"] is True
|
|
assert kwargs["name"] == "waverunner"
|
|
return sentinel
|
|
|
|
handler = MagicMock()
|
|
handler.list_agent_versions.side_effect = fake_versions
|
|
|
|
with patch(_HANDLER_PATH, handler):
|
|
response = await alist_versions(name="waverunner", api_key="AIza")
|
|
assert response is sentinel
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Async error wrapping: exception_type must be invoked
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestAsyncErrorWrapping:
|
|
"""If the underlying handler raises, async entry points re-raise via
|
|
litellm.exception_type so users get a normalised provider error."""
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_acreate_wraps_exception(self):
|
|
handler = MagicMock()
|
|
handler.create_agent.side_effect = RuntimeError("kaboom")
|
|
|
|
with patch(_HANDLER_PATH, handler):
|
|
with pytest.raises(litellm.APIConnectionError):
|
|
await acreate(name="waverunner", api_key="AIza")
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_aget_wraps_exception(self):
|
|
handler = MagicMock()
|
|
handler.get_agent.side_effect = RuntimeError("kaboom")
|
|
|
|
with patch(_HANDLER_PATH, handler):
|
|
with pytest.raises(litellm.APIConnectionError):
|
|
await aget(name="waverunner", api_key="AIza")
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_alist_wraps_exception(self):
|
|
handler = MagicMock()
|
|
handler.list_agents.side_effect = RuntimeError("kaboom")
|
|
|
|
with patch(_HANDLER_PATH, handler):
|
|
with pytest.raises(litellm.APIConnectionError):
|
|
await alist(api_key="AIza")
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_adelete_wraps_exception(self):
|
|
handler = MagicMock()
|
|
handler.delete_agent.side_effect = RuntimeError("kaboom")
|
|
|
|
with patch(_HANDLER_PATH, handler):
|
|
with pytest.raises(litellm.APIConnectionError):
|
|
await adelete(name="waverunner", api_key="AIza")
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_alist_versions_wraps_exception(self):
|
|
handler = MagicMock()
|
|
handler.list_agent_versions.side_effect = RuntimeError("kaboom")
|
|
|
|
with patch(_HANDLER_PATH, handler):
|
|
with pytest.raises(litellm.APIConnectionError):
|
|
await alist_versions(name="waverunner", api_key="AIza")
|
|
|
|
|
|
_GEMINI_AGENTS_URL: Final = "https://generativelanguage.googleapis.com/v1beta/agents"
|
|
|
|
|
|
def _sent_json(route: respx.Route) -> object:
|
|
return json.loads(route.calls.last.request.content)
|
|
|
|
|
|
class TestCreateRequestBody:
|
|
def test_create_sends_every_current_field_verbatim(self, respx_mock: respx.MockRouter) -> None:
|
|
route: Final = respx_mock.post(_GEMINI_AGENTS_URL).mock(
|
|
return_value=httpx.Response(200, json={"id": "sentinel", "base_agent": "antigravity-preview-09-2026"})
|
|
)
|
|
|
|
response: Final = create(
|
|
id="sentinel",
|
|
base_agent="antigravity-preview-09-2026",
|
|
agent_config={"type": "antigravity", "model": "gemini-3.8-flash", "max_total_tokens": None},
|
|
system_instruction="Always answer in exactly three words.",
|
|
base_environment={"type": "remote", "sources": [{"type": "gcs", "source": "gs://bucket/skill"}]},
|
|
description="Watches the queue.",
|
|
tools=[{"type": "code_execution"}],
|
|
api_key="AIza-test",
|
|
)
|
|
|
|
assert _sent_json(route) == {
|
|
"id": "sentinel",
|
|
"base_agent": "antigravity-preview-09-2026",
|
|
"agent_config": {"type": "antigravity", "model": "gemini-3.8-flash", "max_total_tokens": None},
|
|
"system_instruction": "Always answer in exactly three words.",
|
|
"base_environment": {"type": "remote", "sources": [{"type": "gcs", "source": "gs://bucket/skill"}]},
|
|
"description": "Watches the queue.",
|
|
"tools": [{"type": "code_execution"}],
|
|
}
|
|
assert (response.id, response.name) == ("sentinel", "sentinel")
|
|
|
|
@pytest.mark.parametrize(
|
|
("identifier", "instruction", "expected"),
|
|
[
|
|
pytest.param(
|
|
{"name": "slides"},
|
|
{"instructions": "Make slides."},
|
|
{"id": "slides", "system_instruction": "Make slides."},
|
|
id="preview-aliases",
|
|
),
|
|
pytest.param(
|
|
{"id": "slides", "name": "slides"},
|
|
{"system_instruction": "Make slides.", "instructions": "Make slides."},
|
|
{"id": "slides", "system_instruction": "Make slides."},
|
|
id="same-value-under-both-names",
|
|
),
|
|
],
|
|
)
|
|
def test_create_sends_preview_aliases_under_current_names(
|
|
self,
|
|
respx_mock: respx.MockRouter,
|
|
identifier: Mapping[str, str],
|
|
instruction: Mapping[str, str],
|
|
expected: Mapping[str, str],
|
|
) -> None:
|
|
route: Final = respx_mock.post(_GEMINI_AGENTS_URL).mock(
|
|
return_value=httpx.Response(200, json={"id": "slides"})
|
|
)
|
|
|
|
create(**identifier, **instruction, base_agent="antigravity-preview-09-2026", api_key="AIza-test")
|
|
|
|
assert _sent_json(route) == {**expected, "base_agent": "antigravity-preview-09-2026"}
|
|
|
|
@pytest.mark.parametrize(
|
|
"fields",
|
|
[
|
|
pytest.param({"base_agent": "antigravity-preview-09-2026"}, id="no-identifier"),
|
|
pytest.param({"id": "slides", "name": "decks"}, id="different-id-and-name"),
|
|
pytest.param(
|
|
{"id": "slides", "system_instruction": "Make slides.", "instructions": "Make decks."},
|
|
id="different-system-instruction-and-instructions",
|
|
),
|
|
pytest.param({"id": "slides", "agent_config": "antigravity"}, id="agent-config-not-an-object"),
|
|
],
|
|
)
|
|
def test_create_rejects_a_malformed_identifier_or_instruction(
|
|
self, respx_mock: respx.MockRouter, fields: Mapping[str, str]
|
|
) -> None:
|
|
with pytest.raises(litellm.BadRequestError) as exc_info:
|
|
create(**fields, api_key="AIza-test")
|
|
|
|
assert exc_info.value.status_code == 400
|
|
assert not respx_mock.calls
|
|
|
|
def test_create_with_drop_params_drops_only_the_invalid_optional_fields(
|
|
self, respx_mock: respx.MockRouter
|
|
) -> None:
|
|
route: Final = respx_mock.post(_GEMINI_AGENTS_URL).mock(
|
|
return_value=httpx.Response(200, json={"id": "slides"})
|
|
)
|
|
|
|
create(
|
|
id="slides",
|
|
agent_config="antigravity",
|
|
tools="code_execution",
|
|
instructions="Make slides.",
|
|
drop_params=True,
|
|
api_key="AIza-test",
|
|
)
|
|
|
|
assert _sent_json(route) == {"id": "slides", "system_instruction": "Make slides."}
|
|
|
|
def test_create_with_drop_params_drops_a_clashing_instructions_alias(self, respx_mock: respx.MockRouter) -> None:
|
|
route: Final = respx_mock.post(_GEMINI_AGENTS_URL).mock(
|
|
return_value=httpx.Response(200, json={"id": "slides"})
|
|
)
|
|
|
|
create(
|
|
id="slides",
|
|
system_instruction="Make slides.",
|
|
instructions="Make decks.",
|
|
drop_params=True,
|
|
api_key="AIza-test",
|
|
)
|
|
|
|
assert _sent_json(route) == {"id": "slides", "system_instruction": "Make slides."}
|
|
|
|
def test_create_with_drop_params_still_rejects_a_clashing_identifier(self, respx_mock: respx.MockRouter) -> None:
|
|
with pytest.raises(litellm.BadRequestError) as exc_info:
|
|
create(id="slides", name="decks", drop_params=True, api_key="AIza-test")
|
|
|
|
assert exc_info.value.status_code == 400
|
|
assert not respx_mock.calls
|
|
|
|
@pytest.mark.parametrize(
|
|
("fields", "expected_message"),
|
|
[
|
|
pytest.param(
|
|
{"id": "slides", "instructions": 5, "tools": "code_execution"},
|
|
"Invalid agent create request: 'instructions' must be a string; 'tools' must be a list of objects."
|
|
" Set drop_params to drop invalid optional fields.",
|
|
id="names-the-alias-the-caller-sent",
|
|
),
|
|
pytest.param(
|
|
{"name": 7, "base_agent": "antigravity-preview-09-2026"},
|
|
"Invalid agent create request: 'name' must be a string.",
|
|
id="names-the-id-alias-the-caller-sent",
|
|
),
|
|
pytest.param(
|
|
{"id": 7, "agent_config": "antigravity", "drop_params": True},
|
|
"Invalid agent create request: 'agent_config' must be an object; 'id' must be a string.",
|
|
id="invalid-id-is-never-dropped",
|
|
),
|
|
],
|
|
)
|
|
def test_create_rejects_invalid_fields_by_the_names_the_caller_sent(
|
|
self, respx_mock: respx.MockRouter, fields: Mapping[str, object], expected_message: str
|
|
) -> None:
|
|
with pytest.raises(litellm.BadRequestError) as exc_info:
|
|
create(**fields, api_key="AIza-test")
|
|
|
|
assert exc_info.value.status_code == 400
|
|
assert exc_info.value.message.endswith(expected_message)
|
|
assert not respx_mock.calls
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_acreate_sends_every_current_field_verbatim(
|
|
self, respx_mock: respx.MockRouter, monkeypatch: pytest.MonkeyPatch
|
|
) -> None:
|
|
monkeypatch.setenv("DISABLE_AIOHTTP_TRANSPORT", "True")
|
|
route: Final = respx_mock.post(_GEMINI_AGENTS_URL).mock(
|
|
return_value=httpx.Response(200, json={"id": "sentinel"})
|
|
)
|
|
|
|
response: Final = await acreate(
|
|
id="sentinel",
|
|
agent_config={"type": "antigravity", "model": "gemini-3.8-flash"},
|
|
system_instruction="Always answer in exactly three words.",
|
|
api_key="AIza-test",
|
|
)
|
|
|
|
assert _sent_json(route) == {
|
|
"id": "sentinel",
|
|
"agent_config": {"type": "antigravity", "model": "gemini-3.8-flash"},
|
|
"system_instruction": "Always answer in exactly three words.",
|
|
}
|
|
assert response.id == "sentinel"
|
|
|
|
|
|
class TestAgentIdentifier:
|
|
def test_get_by_id_fetches_that_agent(self, respx_mock: respx.MockRouter) -> None:
|
|
route: Final = respx_mock.get(f"{_GEMINI_AGENTS_URL}/slides").mock(
|
|
return_value=httpx.Response(200, json={"id": "slides", "base_agent": "antigravity-preview-09-2026"})
|
|
)
|
|
|
|
response: Final = get(id="slides", api_key="AIza-test")
|
|
|
|
assert route.call_count == 1
|
|
assert (response.id, response.base_agent) == ("slides", "antigravity-preview-09-2026")
|
|
|
|
def test_delete_by_id_deletes_that_agent(self, respx_mock: respx.MockRouter) -> None:
|
|
route: Final = respx_mock.delete(f"{_GEMINI_AGENTS_URL}/slides").mock(
|
|
return_value=httpx.Response(200, json={})
|
|
)
|
|
|
|
result: Final = delete(id="slides", api_key="AIza-test")
|
|
|
|
assert route.call_count == 1
|
|
assert (result.name, result.deleted) == ("slides", True)
|
|
|
|
def test_list_versions_by_id_lists_that_agents_versions(self, respx_mock: respx.MockRouter) -> None:
|
|
version: Final = {"agent": "slides", "name": "agents/slides/versions/v1"}
|
|
route: Final = respx_mock.get(f"{_GEMINI_AGENTS_URL}/slides/versions").mock(
|
|
return_value=httpx.Response(200, json={"agentVersions": [version]})
|
|
)
|
|
|
|
response: Final = list_versions(id="slides", api_key="AIza-test")
|
|
|
|
assert route.call_count == 1
|
|
assert response.agent_versions == [version]
|
|
|
|
@pytest.mark.parametrize(
|
|
"operation",
|
|
[pytest.param(get, id="get"), pytest.param(delete, id="delete"), pytest.param(list_versions, id="list_versions")],
|
|
)
|
|
@pytest.mark.parametrize(
|
|
"identifier",
|
|
[
|
|
pytest.param({}, id="no-identifier"),
|
|
pytest.param({"id": "slides", "name": "decks"}, id="different-id-and-name"),
|
|
],
|
|
)
|
|
def test_rejects_a_missing_or_conflicting_identifier(
|
|
self,
|
|
respx_mock: respx.MockRouter,
|
|
operation: Callable[..., object],
|
|
identifier: Mapping[str, str],
|
|
) -> None:
|
|
with pytest.raises(litellm.BadRequestError) as exc_info:
|
|
operation(**identifier, api_key="AIza-test")
|
|
|
|
assert exc_info.value.status_code == 400
|
|
assert not respx_mock.calls
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_aget_rejects_a_missing_identifier(self, respx_mock: respx.MockRouter) -> None:
|
|
with pytest.raises(litellm.BadRequestError) as exc_info:
|
|
await aget(api_key="AIza-test")
|
|
|
|
assert exc_info.value.status_code == 400
|
|
assert not respx_mock.calls
|