mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-24 00:52:24 +00:00
Merge pull request #42067 from BerriAI/litellm_genai_adapter_response_schema_tool_params
fix(google_genai): forward response schema and tool parameters through the generateContent adapter
This commit is contained in:
commit
b4447096e4
2 changed files with 440 additions and 28 deletions
|
|
@ -1,12 +1,17 @@
|
|||
import json
|
||||
from collections.abc import AsyncIterator, Callable, Iterator, Mapping, Sequence
|
||||
from types import MappingProxyType
|
||||
from typing import Any, Final, TypeAlias, cast
|
||||
from typing import Any, Final, TypeAlias, TypeVar, cast
|
||||
|
||||
from pydantic import JsonValue, TypeAdapter, ValidationError
|
||||
from typing_extensions import ReadOnly, TypedDict
|
||||
|
||||
from litellm import verbose_logger
|
||||
from litellm.litellm_core_utils.json_validation_rule import normalize_tool_schema
|
||||
from litellm.exceptions import BadRequestError
|
||||
from litellm.litellm_core_utils.get_llm_provider_logic import get_llm_provider
|
||||
from litellm.litellm_core_utils.get_supported_openai_params import get_supported_openai_params
|
||||
from litellm.litellm_core_utils.json_validation_rule import normalize_json_schema_types, normalize_tool_schema
|
||||
from litellm.litellm_core_utils.prompt_templates.common_utils import filter_value_from_dict
|
||||
from litellm.types.llms.openai import (
|
||||
AllMessageValues,
|
||||
ChatCompletionAssistantMessage,
|
||||
|
|
@ -75,6 +80,7 @@ class _GenAIContentPart(TypedDict, total=False):
|
|||
class _GenAIFunctionDeclaration(TypedDict, total=False):
|
||||
name: ReadOnly[str]
|
||||
description: ReadOnly[str]
|
||||
parameters: ReadOnly[object]
|
||||
parametersJsonSchema: ReadOnly[object]
|
||||
|
||||
|
||||
|
|
@ -95,6 +101,48 @@ class _GenAISystemInstruction(TypedDict, total=False):
|
|||
|
||||
|
||||
_EMPTY_STR_MAPPING: Final[Mapping[str, str]] = MappingProxyType({})
|
||||
_RESPONSE_MIME_TYPE_KEYS: Final = ("responseMimeType", "response_mime_type")
|
||||
_RESPONSE_SCHEMA_KEYS: Final = ("responseJsonSchema", "response_json_schema", "responseSchema", "response_schema")
|
||||
_TOOL_PARAMETERS_KEYS: Final = ("parametersJsonSchema", "parameters")
|
||||
_JSON_MIME_TYPE: Final = "application/json"
|
||||
_GEMINI_ONLY_SCHEMA_KEYS: Final = frozenset({"propertyOrdering", "property_ordering"})
|
||||
_CONFIG_FIELDS: Final = TypeAdapter(Mapping[str, object])
|
||||
_JSON_OBJECT_SCHEMA: Final = TypeAdapter(dict[str, JsonValue])
|
||||
_Validated: Final = TypeVar("_Validated")
|
||||
|
||||
|
||||
def _first_present(config: Mapping[str, object], keys: Sequence[str]) -> object | None:
|
||||
return next((config[key] for key in keys if config.get(key) is not None), None)
|
||||
|
||||
|
||||
def _validated(adapter: TypeAdapter[_Validated], value: object) -> _Validated | None:
|
||||
try:
|
||||
return adapter.validate_python(value)
|
||||
except ValidationError:
|
||||
return None
|
||||
|
||||
|
||||
def _translate_response_format(config: object) -> Mapping[str, object] | None:
|
||||
fields: Final = _validated(_CONFIG_FIELDS, config)
|
||||
if fields is None or _first_present(fields, _RESPONSE_MIME_TYPE_KEYS) not in (None, _JSON_MIME_TYPE):
|
||||
return None
|
||||
schema: Final = _validated(
|
||||
_JSON_OBJECT_SCHEMA, normalize_json_schema_types(_first_present(fields, _RESPONSE_SCHEMA_KEYS))
|
||||
)
|
||||
if schema is None or schema.get("type") != "object":
|
||||
return None
|
||||
for key in _GEMINI_ONLY_SCHEMA_KEYS:
|
||||
filter_value_from_dict(schema, key)
|
||||
return {"type": "json_schema", "json_schema": {"name": "response", "schema": schema}}
|
||||
|
||||
|
||||
def _deployment_supports_response_format(model: str, custom_llm_provider: str | None) -> bool:
|
||||
try:
|
||||
provider_model, provider, _, _ = get_llm_provider(model=model, custom_llm_provider=custom_llm_provider)
|
||||
except BadRequestError:
|
||||
return True
|
||||
supported_params: Final = get_supported_openai_params(model=provider_model, custom_llm_provider=provider)
|
||||
return supported_params is None or "response_format" in supported_params
|
||||
|
||||
|
||||
class GoogleGenAIStreamWrapper(AdapterCompletionStreamWrapper):
|
||||
|
|
@ -314,6 +362,11 @@ class GoogleGenAIAdapter:
|
|||
pass
|
||||
if "stopSequences" in config:
|
||||
completion_request["stop"] = config["stopSequences"]
|
||||
response_format: Final = _translate_response_format(config)
|
||||
if response_format is not None and _deployment_supports_response_format(
|
||||
model, litellm_params.custom_llm_provider if litellm_params else None
|
||||
):
|
||||
completion_request["response_format"] = response_format
|
||||
|
||||
# Handle tools transformation
|
||||
if tools:
|
||||
|
|
@ -390,8 +443,9 @@ class GoogleGenAIAdapter:
|
|||
|
||||
if "description" in func_decl:
|
||||
function_chunk["description"] = func_decl["description"]
|
||||
if "parametersJsonSchema" in func_decl:
|
||||
function_chunk["parameters"] = func_decl["parametersJsonSchema"]
|
||||
parameters = _validated(_JSON_OBJECT_SCHEMA, _first_present(func_decl, _TOOL_PARAMETERS_KEYS))
|
||||
if parameters is not None:
|
||||
function_chunk["parameters"] = parameters
|
||||
|
||||
openai_tool: _JsonDict = {"type": "function", "function": function_chunk}
|
||||
openai_tools.append(openai_tool)
|
||||
|
|
@ -582,14 +636,6 @@ class GoogleGenAIAdapter:
|
|||
),
|
||||
}
|
||||
|
||||
# Add text field for convenience (common in Google GenAI responses)
|
||||
text_content = ""
|
||||
for part in parts:
|
||||
if isinstance(part, dict) and "text" in part:
|
||||
text_content += part["text"]
|
||||
if text_content:
|
||||
generate_content_response["text"] = text_content
|
||||
|
||||
return generate_content_response
|
||||
|
||||
def translate_streaming_completion_to_generate_content(
|
||||
|
|
@ -656,14 +702,6 @@ class GoogleGenAIAdapter:
|
|||
)
|
||||
streaming_chunk["usageMetadata"] = usage_metadata
|
||||
|
||||
# Add text field for convenience (common in Google GenAI responses)
|
||||
text_content = ""
|
||||
for part in parts:
|
||||
if isinstance(part, dict) and "text" in part:
|
||||
text_content += part["text"]
|
||||
if text_content:
|
||||
streaming_chunk["text"] = text_content
|
||||
|
||||
return streaming_chunk
|
||||
|
||||
def _transform_openai_message_to_google_genai_parts(
|
||||
|
|
|
|||
|
|
@ -372,8 +372,7 @@ def test_completion_to_generate_content_with_tool_calls():
|
|||
assert function_call["name"] == "get_weather"
|
||||
assert function_call["args"]["location"] == "San Francisco"
|
||||
|
||||
# Check text field
|
||||
assert generate_content_response["text"] == "I'll check the weather for you."
|
||||
assert "text" not in generate_content_response
|
||||
|
||||
|
||||
def test_streaming_tool_calls_transformation():
|
||||
|
|
@ -738,12 +737,7 @@ def test_completion_to_generate_content_transformation():
|
|||
mock_response
|
||||
)
|
||||
|
||||
# Verify the transformation
|
||||
assert "text" in generate_content_response
|
||||
assert (
|
||||
generate_content_response["text"]
|
||||
== "Hello! I'm doing well, thank you for asking."
|
||||
)
|
||||
assert "text" not in generate_content_response
|
||||
|
||||
assert "candidates" in generate_content_response
|
||||
assert len(generate_content_response["candidates"]) == 1
|
||||
|
|
@ -1267,3 +1261,383 @@ def test_inline_data_backward_compatibility_text_only():
|
|||
content, str
|
||||
), "Content should be a string for text-only messages (backward compatibility)"
|
||||
assert content == "Hello, how are you?"
|
||||
|
||||
|
||||
def test_tools_transformation_reads_parameters_declaration():
|
||||
"""The google-genai SDK and REST callers send `parameters` (Gemini Schema types), not `parametersJsonSchema`"""
|
||||
from litellm.google_genai.adapters.transformation import GoogleGenAIAdapter
|
||||
|
||||
adapter = GoogleGenAIAdapter()
|
||||
tools = [
|
||||
{
|
||||
"functionDeclarations": [
|
||||
{
|
||||
"name": "park_hours_lookup",
|
||||
"description": "Look up park hours for a given date and park.",
|
||||
"parameters": {
|
||||
"type": "OBJECT",
|
||||
"properties": {
|
||||
"park_id": {"type": "STRING"},
|
||||
"date": {"type": "STRING"},
|
||||
},
|
||||
"required": ["park_id", "date"],
|
||||
},
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
completion_request = adapter.translate_generate_content_to_completion(
|
||||
model="gpt-4.1",
|
||||
contents={"role": "user", "parts": [{"text": "When does EPCOT open?"}]},
|
||||
tools=tools,
|
||||
)
|
||||
|
||||
assert completion_request["tools"][0]["function"]["parameters"] == {
|
||||
"type": "object",
|
||||
"properties": {"park_id": {"type": "string"}, "date": {"type": "string"}},
|
||||
"required": ["park_id", "date"],
|
||||
}
|
||||
|
||||
|
||||
def test_tools_transformation_prefers_parameters_json_schema_over_parameters():
|
||||
from litellm.google_genai.adapters.transformation import GoogleGenAIAdapter
|
||||
|
||||
adapter = GoogleGenAIAdapter()
|
||||
tools = [
|
||||
{
|
||||
"functionDeclarations": [
|
||||
{
|
||||
"name": "lookup",
|
||||
"parametersJsonSchema": {"type": "object", "properties": {"a": {"type": "string"}}},
|
||||
"parameters": {"type": "OBJECT", "properties": {"b": {"type": "STRING"}}},
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
completion_request = adapter.translate_generate_content_to_completion(
|
||||
model="gpt-4.1", contents={"role": "user", "parts": [{"text": "hi"}]}, tools=tools
|
||||
)
|
||||
|
||||
assert completion_request["tools"][0]["function"]["parameters"]["properties"] == {"a": {"type": "string"}}
|
||||
|
||||
|
||||
PARK_TIP_GEMINI_SCHEMA = {
|
||||
"type": "OBJECT",
|
||||
"title": "ParkTipResponse",
|
||||
"properties": {
|
||||
"park_name": {"type": "STRING"},
|
||||
"highlights": {"type": "ARRAY", "items": {"type": "STRING"}},
|
||||
"confidence": {"type": "STRING", "enum": ["low", "medium", "high"]},
|
||||
},
|
||||
"required": ["park_name", "highlights", "confidence"],
|
||||
}
|
||||
|
||||
PARK_TIP_JSON_SCHEMA = {
|
||||
"type": "object",
|
||||
"title": "ParkTipResponse",
|
||||
"properties": {
|
||||
"park_name": {"type": "string"},
|
||||
"highlights": {"type": "array", "items": {"type": "string"}},
|
||||
"confidence": {"type": "string", "enum": ["low", "medium", "high"]},
|
||||
},
|
||||
"required": ["park_name", "highlights", "confidence"],
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize("mime_type_key", ["response_mime_type", "responseMimeType"])
|
||||
@pytest.mark.parametrize(
|
||||
"schema_key,schema",
|
||||
[
|
||||
("response_schema", PARK_TIP_GEMINI_SCHEMA),
|
||||
("responseSchema", PARK_TIP_GEMINI_SCHEMA),
|
||||
("response_json_schema", PARK_TIP_JSON_SCHEMA),
|
||||
("responseJsonSchema", PARK_TIP_JSON_SCHEMA),
|
||||
],
|
||||
)
|
||||
def test_response_schema_config_maps_to_json_schema_response_format(mime_type_key, schema_key, schema):
|
||||
from litellm.google_genai.adapters.transformation import GoogleGenAIAdapter
|
||||
|
||||
adapter = GoogleGenAIAdapter()
|
||||
config = {mime_type_key: "application/json", schema_key: schema}
|
||||
|
||||
completion_request = adapter.translate_generate_content_to_completion(
|
||||
model="gpt-4.1", contents={"role": "user", "parts": [{"text": "Summarize EPCOT"}]}, config=config
|
||||
)
|
||||
|
||||
assert completion_request["response_format"] == {
|
||||
"type": "json_schema",
|
||||
"json_schema": {"name": "response", "schema": PARK_TIP_JSON_SCHEMA},
|
||||
}
|
||||
|
||||
|
||||
def test_response_schema_drops_gemini_property_ordering_at_every_level():
|
||||
from litellm.google_genai.adapters.transformation import GoogleGenAIAdapter
|
||||
|
||||
sdk_pydantic_schema = {
|
||||
"type": "OBJECT",
|
||||
"title": "ParkTipResponse",
|
||||
"propertyOrdering": ["park_name", "highlights"],
|
||||
"properties": {
|
||||
"park_name": {"type": "STRING", "title": "Park Name"},
|
||||
"highlights": {
|
||||
"type": "ARRAY",
|
||||
"items": {
|
||||
"type": "OBJECT",
|
||||
"property_ordering": ["title", "detail"],
|
||||
"properties": {
|
||||
"title": {"type": "STRING"},
|
||||
"detail": {"type": "STRING", "nullable": True},
|
||||
},
|
||||
"required": ["title"],
|
||||
},
|
||||
},
|
||||
},
|
||||
"required": ["park_name", "highlights"],
|
||||
}
|
||||
|
||||
completion_request = GoogleGenAIAdapter().translate_generate_content_to_completion(
|
||||
model="claude-opus-5",
|
||||
contents={"role": "user", "parts": [{"text": "Summarize EPCOT"}]},
|
||||
config={"responseMimeType": "application/json", "responseSchema": sdk_pydantic_schema},
|
||||
)
|
||||
|
||||
assert completion_request["response_format"]["json_schema"]["schema"] == {
|
||||
"type": "object",
|
||||
"title": "ParkTipResponse",
|
||||
"properties": {
|
||||
"park_name": {"type": "string", "title": "Park Name"},
|
||||
"highlights": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"title": {"type": "string"},
|
||||
"detail": {"type": "string", "nullable": True},
|
||||
},
|
||||
"required": ["title"],
|
||||
},
|
||||
},
|
||||
},
|
||||
"required": ["park_name", "highlights"],
|
||||
}
|
||||
assert sdk_pydantic_schema["propertyOrdering"] == ["park_name", "highlights"]
|
||||
assert sdk_pydantic_schema["properties"]["highlights"]["items"]["property_ordering"] == ["title", "detail"]
|
||||
|
||||
|
||||
def test_response_schema_without_mime_type_still_maps_to_json_schema():
|
||||
from litellm.google_genai.adapters.transformation import GoogleGenAIAdapter
|
||||
|
||||
adapter = GoogleGenAIAdapter()
|
||||
|
||||
completion_request = adapter.translate_generate_content_to_completion(
|
||||
model="gpt-4.1",
|
||||
contents={"role": "user", "parts": [{"text": "Summarize EPCOT"}]},
|
||||
config={"responseSchema": PARK_TIP_GEMINI_SCHEMA},
|
||||
)
|
||||
|
||||
assert completion_request["response_format"]["type"] == "json_schema"
|
||||
assert completion_request["response_format"]["json_schema"]["schema"] == PARK_TIP_JSON_SCHEMA
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"config",
|
||||
[
|
||||
{"temperature": 0.2},
|
||||
{"responseMimeType": "text/plain"},
|
||||
{"responseMimeType": "application/json"},
|
||||
{"response_mime_type": "application/json", "temperature": 0.2},
|
||||
{"responseMimeType": "text/x.enum", "responseSchema": {"type": "STRING", "enum": ["a", "b"]}},
|
||||
{"responseMimeType": "application/json", "responseSchema": {"type": "ARRAY", "items": {"type": "STRING"}}},
|
||||
{"responseMimeType": "application/json", "responseSchema": {"type": "STRING", "enum": ["a", "b"]}},
|
||||
{"responseMimeType": "application/json", "responseSchema": {"properties": {"a": {"type": "STRING"}}}},
|
||||
{"responseMimeType": "application/json", "responseSchema": None},
|
||||
],
|
||||
)
|
||||
def test_output_config_outside_object_schema_leaves_response_format_unset(config):
|
||||
from litellm.google_genai.adapters.transformation import GoogleGenAIAdapter
|
||||
|
||||
adapter = GoogleGenAIAdapter()
|
||||
|
||||
completion_request = adapter.translate_generate_content_to_completion(
|
||||
model="gpt-4.1", contents={"role": "user", "parts": [{"text": "Pick one"}]}, config=config
|
||||
)
|
||||
|
||||
assert "response_format" not in completion_request
|
||||
|
||||
|
||||
def test_response_schema_is_not_sent_to_deployment_without_response_format_support():
|
||||
from litellm.google_genai.adapters.transformation import GoogleGenAIAdapter
|
||||
from litellm.types.router import GenericLiteLLMParams
|
||||
|
||||
adapter = GoogleGenAIAdapter()
|
||||
config = {"responseMimeType": "application/json", "responseSchema": PARK_TIP_GEMINI_SCHEMA, "temperature": 0.2}
|
||||
|
||||
completion_request = adapter.translate_generate_content_to_completion(
|
||||
model="openai/gpt-4",
|
||||
contents={"role": "user", "parts": [{"text": "Summarize EPCOT"}]},
|
||||
config=config,
|
||||
litellm_params=GenericLiteLLMParams(custom_llm_provider="openai"),
|
||||
)
|
||||
|
||||
assert "response_format" not in completion_request
|
||||
assert completion_request["temperature"] == 0.2
|
||||
|
||||
|
||||
def test_response_schema_is_sent_when_provider_cannot_be_resolved():
|
||||
from litellm.google_genai.adapters.transformation import GoogleGenAIAdapter
|
||||
|
||||
adapter = GoogleGenAIAdapter()
|
||||
|
||||
completion_request = adapter.translate_generate_content_to_completion(
|
||||
model="my-unmapped-deployment-alias",
|
||||
contents={"role": "user", "parts": [{"text": "Summarize EPCOT"}]},
|
||||
config={"responseSchema": PARK_TIP_GEMINI_SCHEMA},
|
||||
)
|
||||
|
||||
assert completion_request["response_format"]["json_schema"]["schema"] == PARK_TIP_JSON_SCHEMA
|
||||
|
||||
|
||||
def test_pydantic_generation_config_is_tolerated():
|
||||
from pydantic import BaseModel
|
||||
|
||||
from litellm.google_genai.adapters.transformation import GoogleGenAIAdapter
|
||||
|
||||
class SdkStyleConfig(BaseModel):
|
||||
response_mime_type: str = "application/json"
|
||||
response_schema: dict[str, object] = PARK_TIP_GEMINI_SCHEMA
|
||||
|
||||
adapter = GoogleGenAIAdapter()
|
||||
|
||||
completion_request = adapter.translate_generate_content_to_completion(
|
||||
model="gpt-4.1", contents={"role": "user", "parts": [{"text": "hi"}]}, config=SdkStyleConfig()
|
||||
)
|
||||
|
||||
assert completion_request["messages"] == [{"role": "user", "content": "hi"}]
|
||||
assert "response_format" not in completion_request
|
||||
|
||||
|
||||
def test_null_parameters_json_schema_falls_back_to_parameters():
|
||||
from litellm.google_genai.adapters.transformation import GoogleGenAIAdapter
|
||||
|
||||
adapter = GoogleGenAIAdapter()
|
||||
tools = [
|
||||
{
|
||||
"functionDeclarations": [
|
||||
{
|
||||
"name": "lookup",
|
||||
"parametersJsonSchema": None,
|
||||
"parameters": {"type": "OBJECT", "properties": {"b": {"type": "STRING"}}},
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
completion_request = adapter.translate_generate_content_to_completion(
|
||||
model="gpt-4.1", contents={"role": "user", "parts": [{"text": "hi"}]}, tools=tools
|
||||
)
|
||||
|
||||
assert completion_request["tools"][0]["function"]["parameters"] == {
|
||||
"type": "object",
|
||||
"properties": {"b": {"type": "string"}},
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize("parameters", [5, "", "x", [1], True])
|
||||
def test_non_object_tool_parameters_are_dropped_instead_of_forwarded(parameters):
|
||||
from litellm.google_genai.adapters.transformation import GoogleGenAIAdapter
|
||||
|
||||
adapter = GoogleGenAIAdapter()
|
||||
tools = [{"functionDeclarations": [{"name": "lookup", "description": "Look it up", "parameters": parameters}]}]
|
||||
|
||||
completion_request = adapter.translate_generate_content_to_completion(
|
||||
model="gpt-4.1", contents={"role": "user", "parts": [{"text": "hi"}]}, tools=tools
|
||||
)
|
||||
|
||||
assert completion_request["tools"][0]["function"] == {"name": "lookup", "description": "Look it up"}
|
||||
|
||||
|
||||
def test_streaming_chunk_has_no_top_level_text():
|
||||
from litellm.google_genai.adapters.transformation import (
|
||||
GoogleGenAIAdapter,
|
||||
GoogleGenAIStreamWrapper,
|
||||
)
|
||||
from litellm.types.utils import Delta, ModelResponseStream, StreamingChoices
|
||||
|
||||
adapter = GoogleGenAIAdapter()
|
||||
mock_response = ModelResponseStream(
|
||||
id="test-streaming",
|
||||
choices=[StreamingChoices(finish_reason=None, index=0, delta=Delta(content="Hello"))],
|
||||
created=1234567890,
|
||||
model="gpt-4.1",
|
||||
object="chat.completion.chunk",
|
||||
)
|
||||
|
||||
streaming_chunk = adapter.translate_streaming_completion_to_generate_content(
|
||||
mock_response, GoogleGenAIStreamWrapper(completion_stream=None)
|
||||
)
|
||||
|
||||
assert streaming_chunk["candidates"][0]["content"]["parts"] == [{"text": "Hello"}]
|
||||
assert "text" not in streaming_chunk
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_generate_content_sends_response_schema_and_tool_parameters_to_the_provider(respx_mock, monkeypatch):
|
||||
import httpx
|
||||
|
||||
monkeypatch.setenv("DISABLE_AIOHTTP_TRANSPORT", "True")
|
||||
route = respx_mock.post("https://api.openai.com/v1/chat/completions").mock(
|
||||
return_value=httpx.Response(
|
||||
200,
|
||||
json={
|
||||
"id": "chatcmpl-park-tip",
|
||||
"object": "chat.completion",
|
||||
"created": 1234567890,
|
||||
"model": "gpt-4.1",
|
||||
"choices": [
|
||||
{
|
||||
"index": 0,
|
||||
"finish_reason": "stop",
|
||||
"message": {"role": "assistant", "content": '{"park_name": "EPCOT"}'},
|
||||
}
|
||||
],
|
||||
"usage": {"prompt_tokens": 10, "completion_tokens": 20, "total_tokens": 30},
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
response = await agenerate_content(
|
||||
model="openai/gpt-4.1",
|
||||
contents=[{"role": "user", "parts": [{"text": "Summarize EPCOT. Do not call tools."}]}],
|
||||
tools=[
|
||||
{
|
||||
"functionDeclarations": [
|
||||
{
|
||||
"name": "park_hours_lookup",
|
||||
"parameters": {
|
||||
"type": "OBJECT",
|
||||
"properties": {"park_id": {"type": "STRING"}},
|
||||
"required": ["park_id"],
|
||||
},
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
generationConfig={"response_mime_type": "application/json", "response_schema": PARK_TIP_GEMINI_SCHEMA},
|
||||
api_key="sk-test",
|
||||
)
|
||||
|
||||
provider_request = json.loads(route.calls.last.request.content)
|
||||
|
||||
assert provider_request["response_format"] == {
|
||||
"type": "json_schema",
|
||||
"json_schema": {"name": "response", "schema": PARK_TIP_JSON_SCHEMA},
|
||||
}
|
||||
assert provider_request["tools"][0]["function"]["parameters"] == {
|
||||
"type": "object",
|
||||
"properties": {"park_id": {"type": "string"}},
|
||||
"required": ["park_id"],
|
||||
}
|
||||
assert response["candidates"][0]["content"]["parts"] == [{"text": '{"park_name": "EPCOT"}'}]
|
||||
assert "text" not in response
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue