diff --git a/src/google/adk/models/lite_llm.py b/src/google/adk/models/lite_llm.py index bfe310231a..d5b362fae0 100644 --- a/src/google/adk/models/lite_llm.py +++ b/src/google/adk/models/lite_llm.py @@ -2092,6 +2092,39 @@ def _schema_to_dict(schema: types.Schema | dict[str, Any]) -> dict[str, Any]: return schema_dict +def _append_response_schema_to_description( + description: str, + function_declaration: types.FunctionDeclaration, +) -> str: + """Appends a rendering of the function's output schema to its description. + + OpenAI-compatible chat completions tool definitions have no standard field + for declaring the schema of a tool's result, so the schema is rendered into + the tool description, which is forwarded to the model. The description is + returned unchanged when the function declaration has no output schema. + + Args: + description: The original tool description. + function_declaration: The function declaration to read the output schema + from. `response_json_schema` takes precedence over `response`. + + Returns: + The description, with the rendered output schema appended when one exists. + """ + response_schema: Optional[dict[str, Any]] = None + if function_declaration.response_json_schema: + response_schema = dict(function_declaration.response_json_schema) + elif function_declaration.response: + response_schema = _schema_to_dict(function_declaration.response) + + if not response_schema: + return description + + rendered_schema = json.dumps(response_schema, sort_keys=True) + suffix = f"Returns a JSON object conforming to this schema: {rendered_schema}" + return f"{description}\n{suffix}" if description else suffix + + def _function_declaration_to_tool_param( function_declaration: types.FunctionDeclaration, ) -> dict[str, Any]: @@ -2129,7 +2162,9 @@ def _function_declaration_to_tool_param( "type": "function", "function": { "name": function_declaration.name, - "description": function_declaration.description or "", + "description": _append_response_schema_to_description( + function_declaration.description or "", function_declaration + ), "parameters": parameters, }, } diff --git a/tests/unittests/models/test_litellm.py b/tests/unittests/models/test_litellm.py index 7f67a87fe5..1929d9c16f 100644 --- a/tests/unittests/models/test_litellm.py +++ b/tests/unittests/models/test_litellm.py @@ -1711,6 +1711,88 @@ def test_function_declaration_to_tool_param_with_parameters_json_schema(): assert _function_declaration_to_tool_param(func_decl) == expected +def test_function_declaration_to_tool_param_with_response_json_schema(): + """Ensure a raw response_json_schema is rendered into the description.""" + + func_decl = types.FunctionDeclaration( + name="fn_with_output", + description="desc", + parameters_json_schema={ + "type": "object", + "properties": {"a": {"type": "string"}}, + }, + response_json_schema={ + "type": "object", + "properties": {"result": {"type": "string"}}, + }, + ) + + tool_param = _function_declaration_to_tool_param(func_decl) + + assert tool_param["function"]["description"] == ( + "desc\nReturns a JSON object conforming to this schema:" + ' {"properties": {"result": {"type": "string"}}, "type": "object"}' + ) + assert tool_param["function"]["parameters"] == { + "type": "object", + "properties": {"a": {"type": "string"}}, + } + + +def test_function_declaration_to_tool_param_with_response_schema(): + """Ensure a types.Schema response is rendered into the description.""" + + func_decl = types.FunctionDeclaration( + name="fn_with_output_schema", + description="desc", + response=types.Schema( + type=types.Type.OBJECT, + properties={"result": types.Schema(type=types.Type.STRING)}, + ), + ) + + tool_param = _function_declaration_to_tool_param(func_decl) + + assert tool_param["function"]["description"] == ( + "desc\nReturns a JSON object conforming to this schema:" + ' {"properties": {"result": {"type": "string"}}, "type": "object"}' + ) + + +def test_function_declaration_to_tool_param_without_response_schema(): + """Ensure the description is unchanged when no output schema is declared.""" + + func_decl = types.FunctionDeclaration( + name="fn_without_output", + description="desc", + parameters_json_schema={"type": "object", "properties": {}}, + ) + + assert ( + _function_declaration_to_tool_param(func_decl)["function"]["description"] + == "desc" + ) + + +def test_function_declaration_to_tool_param_response_schema_without_description(): + """Ensure an empty description yields only the rendered output schema.""" + + func_decl = types.FunctionDeclaration( + name="fn_no_description", + response_json_schema={ + "type": "object", + "properties": {"result": {"type": "string"}}, + }, + ) + + assert _function_declaration_to_tool_param(func_decl)["function"][ + "description" + ] == ( + "Returns a JSON object conforming to this schema:" + ' {"properties": {"result": {"type": "string"}}, "type": "object"}' + ) + + @pytest.mark.asyncio async def test_generate_content_async_with_system_instruction( lite_llm_instance, mock_acompletion