@@ -668,6 +668,118 @@ async def test_get_completion_inputs_inserts_missing_tool_results():
668668 assert tool_message ["content" ] == _MISSING_TOOL_RESULT_MESSAGE
669669
670670
671+ @pytest .mark .asyncio
672+ async def test_get_completion_inputs_serializes_native_only_tool ():
673+ llm_request = LlmRequest (
674+ config = types .GenerateContentConfig (
675+ tools = [types .Tool (google_search = types .GoogleSearch ())]
676+ )
677+ )
678+
679+ _ , tools , _ , _ = await _get_completion_inputs (
680+ llm_request , model = "openai/gpt-4o"
681+ )
682+
683+ assert tools == [
684+ types .Tool (google_search = types .GoogleSearch ()).model_dump (
685+ by_alias = True , exclude_none = True
686+ )
687+ ]
688+
689+
690+ @pytest .mark .asyncio
691+ async def test_get_completion_inputs_mixed_native_and_function_tools ():
692+ llm_request = LlmRequest (
693+ config = types .GenerateContentConfig (
694+ tools = [
695+ types .Tool (google_search = types .GoogleSearch ()),
696+ types .Tool (
697+ function_declarations = [
698+ types .FunctionDeclaration (
699+ name = "get_weather" ,
700+ description = "Gets the weather." ,
701+ parameters = types .Schema (
702+ type = types .Type .OBJECT ,
703+ properties = {
704+ "city" : types .Schema (type = types .Type .STRING )
705+ },
706+ ),
707+ )
708+ ]
709+ ),
710+ ]
711+ )
712+ )
713+
714+ _ , tools , _ , _ = await _get_completion_inputs (
715+ llm_request , model = "openai/gpt-4o"
716+ )
717+
718+ assert len (tools ) == 2
719+ native_tools = [t for t in tools if "type" not in t ]
720+ function_tools = [t for t in tools if t .get ("type" ) == "function" ]
721+ assert len (native_tools ) == 1
722+ assert len (function_tools ) == 1
723+ assert function_tools [0 ]["function" ]["name" ] == "get_weather"
724+
725+
726+ @pytest .mark .asyncio
727+ async def test_get_completion_inputs_collects_tools_beyond_index_zero ():
728+ llm_request = LlmRequest (
729+ config = types .GenerateContentConfig (
730+ tools = [
731+ types .Tool (
732+ function_declarations = [
733+ types .FunctionDeclaration (
734+ name = "first_tool" , description = "First tool."
735+ )
736+ ]
737+ ),
738+ types .Tool (
739+ function_declarations = [
740+ types .FunctionDeclaration (
741+ name = "second_tool" , description = "Second tool."
742+ )
743+ ]
744+ ),
745+ ]
746+ )
747+ )
748+
749+ _ , tools , _ , _ = await _get_completion_inputs (
750+ llm_request , model = "openai/gpt-4o"
751+ )
752+
753+ assert [t ["function" ]["name" ] for t in tools ] == [
754+ "first_tool" ,
755+ "second_tool" ,
756+ ]
757+
758+
759+ @pytest .mark .asyncio
760+ async def test_get_completion_inputs_no_tools_returns_none ():
761+ llm_request = LlmRequest (config = types .GenerateContentConfig ())
762+
763+ _ , tools , _ , _ = await _get_completion_inputs (
764+ llm_request , model = "openai/gpt-4o"
765+ )
766+
767+ assert tools is None
768+
769+
770+ @pytest .mark .asyncio
771+ async def test_get_completion_inputs_empty_tool_ignored ():
772+ llm_request = LlmRequest (
773+ config = types .GenerateContentConfig (tools = [types .Tool ()])
774+ )
775+
776+ _ , tools , _ , _ = await _get_completion_inputs (
777+ llm_request , model = "openai/gpt-4o"
778+ )
779+
780+ assert tools is None
781+
782+
671783def test_schema_to_dict_filters_none_enum_values ():
672784 # Use model_construct to bypass strict enum validation.
673785 top_level_schema = types .Schema .model_construct (
0 commit comments