These are TypedDicts (plain dicts at runtime), so no behavioral change — just more descriptive type hints for readers. Use ToolParam instead of FunctionToolParam for the Responses adapter to reflect that custom non-function tools are supported. Use ChatCompletionToolParam instead of Any for the completions adapter return type. Update tests to use typed params in expected values.
341 lines
14 KiB
Python
341 lines
14 KiB
Python
#
|
|
# Copyright (c) 2024-2026, Daily
|
|
#
|
|
# SPDX-License-Identifier: BSD 2-Clause License
|
|
#
|
|
|
|
import unittest
|
|
|
|
from openai.types.chat import ChatCompletionToolParam
|
|
from openai.types.responses.function_tool_param import FunctionToolParam
|
|
from openai.types.responses.tool_search_tool_param import ToolSearchToolParam
|
|
|
|
from pipecat.adapters.schemas.function_schema import FunctionSchema
|
|
from pipecat.adapters.schemas.tools_schema import AdapterType, ToolsSchema
|
|
from pipecat.adapters.services.anthropic_adapter import AnthropicLLMAdapter
|
|
from pipecat.adapters.services.bedrock_adapter import AWSBedrockLLMAdapter
|
|
from pipecat.adapters.services.gemini_adapter import GeminiLLMAdapter
|
|
from pipecat.adapters.services.open_ai_adapter import OpenAILLMAdapter
|
|
from pipecat.adapters.services.open_ai_realtime_adapter import OpenAIRealtimeLLMAdapter
|
|
from pipecat.adapters.services.open_ai_responses_adapter import OpenAIResponsesLLMAdapter
|
|
|
|
|
|
class TestFunctionAdapters(unittest.TestCase):
|
|
def setUp(self) -> None:
|
|
"""Sets up a common tools schema for all tests."""
|
|
function_def = FunctionSchema(
|
|
name="get_weather",
|
|
description="Get the weather in a given location",
|
|
properties={
|
|
"location": {"type": "string", "description": "The city, e.g. San Francisco"},
|
|
"format": {
|
|
"type": "string",
|
|
"enum": ["celsius", "fahrenheit"],
|
|
"description": "The temperature unit to use.",
|
|
},
|
|
},
|
|
required=["location", "format"],
|
|
)
|
|
self.tools_def = ToolsSchema(standard_tools=[function_def])
|
|
|
|
def test_openai_adapter(self):
|
|
"""Test OpenAI adapter format transformation."""
|
|
expected = [
|
|
ChatCompletionToolParam(
|
|
type="function",
|
|
function={
|
|
"name": "get_weather",
|
|
"description": "Get the weather in a given location",
|
|
"parameters": {
|
|
"type": "object",
|
|
"properties": {
|
|
"location": {
|
|
"type": "string",
|
|
"description": "The city, e.g. San Francisco",
|
|
},
|
|
"format": {
|
|
"type": "string",
|
|
"enum": ["celsius", "fahrenheit"],
|
|
"description": "The temperature unit to use.",
|
|
},
|
|
},
|
|
"required": ["location", "format"],
|
|
},
|
|
},
|
|
)
|
|
]
|
|
assert OpenAILLMAdapter().to_provider_tools_format(self.tools_def) == expected
|
|
|
|
def test_anthropic_adapter(self):
|
|
"""Test Anthropic adapter format transformation."""
|
|
expected = [
|
|
{
|
|
"name": "get_weather",
|
|
"description": "Get the weather in a given location",
|
|
"input_schema": {
|
|
"type": "object",
|
|
"properties": {
|
|
"location": {
|
|
"type": "string",
|
|
"description": "The city, e.g. San Francisco",
|
|
},
|
|
"format": {
|
|
"type": "string",
|
|
"enum": ["celsius", "fahrenheit"],
|
|
"description": "The temperature unit to use.",
|
|
},
|
|
},
|
|
"required": ["location", "format"],
|
|
},
|
|
}
|
|
]
|
|
assert AnthropicLLMAdapter().to_provider_tools_format(self.tools_def) == expected
|
|
|
|
def test_gemini_adapter(self):
|
|
"""Test Gemini adapter format transformation."""
|
|
expected = [
|
|
{
|
|
"function_declarations": [
|
|
{
|
|
"name": "get_weather",
|
|
"description": "Get the weather in a given location",
|
|
"parameters": {
|
|
"type": "object",
|
|
"properties": {
|
|
"location": {
|
|
"type": "string",
|
|
"description": "The city, e.g. San Francisco",
|
|
},
|
|
"format": {
|
|
"type": "string",
|
|
"enum": ["celsius", "fahrenheit"],
|
|
"description": "The temperature unit to use.",
|
|
},
|
|
},
|
|
"required": ["location", "format"],
|
|
},
|
|
}
|
|
]
|
|
}
|
|
]
|
|
assert GeminiLLMAdapter().to_provider_tools_format(self.tools_def) == expected
|
|
|
|
def test_openai_realtime_adapter(self):
|
|
"""Test Anthropic adapter format transformation."""
|
|
expected = [
|
|
{
|
|
"type": "function",
|
|
"name": "get_weather",
|
|
"description": "Get the weather in a given location",
|
|
"parameters": {
|
|
"type": "object",
|
|
"properties": {
|
|
"location": {
|
|
"type": "string",
|
|
"description": "The city, e.g. San Francisco",
|
|
},
|
|
"format": {
|
|
"type": "string",
|
|
"enum": ["celsius", "fahrenheit"],
|
|
"description": "The temperature unit to use.",
|
|
},
|
|
},
|
|
"required": ["location", "format"],
|
|
},
|
|
}
|
|
]
|
|
assert OpenAIRealtimeLLMAdapter().to_provider_tools_format(self.tools_def) == expected
|
|
|
|
def test_gemini_adapter_with_custom_tools(self):
|
|
"""Test Gemini adapter format transformation."""
|
|
search_tool = {"google_search": {}}
|
|
expected = [
|
|
{
|
|
"function_declarations": [
|
|
{
|
|
"name": "get_weather",
|
|
"description": "Get the weather in a given location",
|
|
"parameters": {
|
|
"type": "object",
|
|
"properties": {
|
|
"location": {
|
|
"type": "string",
|
|
"description": "The city, e.g. San Francisco",
|
|
},
|
|
"format": {
|
|
"type": "string",
|
|
"enum": ["celsius", "fahrenheit"],
|
|
"description": "The temperature unit to use.",
|
|
},
|
|
},
|
|
"required": ["location", "format"],
|
|
},
|
|
}
|
|
]
|
|
},
|
|
search_tool,
|
|
]
|
|
tools_def = self.tools_def
|
|
tools_def.custom_tools = {AdapterType.GEMINI: [search_tool]}
|
|
assert GeminiLLMAdapter().to_provider_tools_format(tools_def) == expected
|
|
|
|
def test_openai_adapter_with_custom_tools(self):
|
|
"""Test OpenAI adapter appends custom tools."""
|
|
tool_search = {"type": "tool_search"}
|
|
expected = [
|
|
ChatCompletionToolParam(
|
|
type="function",
|
|
function={
|
|
"name": "get_weather",
|
|
"description": "Get the weather in a given location",
|
|
"parameters": {
|
|
"type": "object",
|
|
"properties": {
|
|
"location": {
|
|
"type": "string",
|
|
"description": "The city, e.g. San Francisco",
|
|
},
|
|
"format": {
|
|
"type": "string",
|
|
"enum": ["celsius", "fahrenheit"],
|
|
"description": "The temperature unit to use.",
|
|
},
|
|
},
|
|
"required": ["location", "format"],
|
|
},
|
|
},
|
|
),
|
|
tool_search,
|
|
]
|
|
tools_def = self.tools_def
|
|
tools_def.custom_tools = {AdapterType.OPENAI: [tool_search]}
|
|
assert OpenAILLMAdapter().to_provider_tools_format(tools_def) == expected
|
|
|
|
def test_openai_responses_adapter_with_custom_tools(self):
|
|
"""Test OpenAI Responses adapter appends custom tools."""
|
|
tool_search = {"type": "tool_search"}
|
|
expected = [
|
|
FunctionToolParam(
|
|
type="function",
|
|
name="get_weather",
|
|
description="Get the weather in a given location",
|
|
parameters={
|
|
"type": "object",
|
|
"properties": {
|
|
"location": {
|
|
"type": "string",
|
|
"description": "The city, e.g. San Francisco",
|
|
},
|
|
"format": {
|
|
"type": "string",
|
|
"enum": ["celsius", "fahrenheit"],
|
|
"description": "The temperature unit to use.",
|
|
},
|
|
},
|
|
"required": ["location", "format"],
|
|
},
|
|
strict=None,
|
|
),
|
|
ToolSearchToolParam(type="tool_search"),
|
|
]
|
|
tools_def = self.tools_def
|
|
tools_def.custom_tools = {AdapterType.OPENAI: [tool_search]}
|
|
assert OpenAIResponsesLLMAdapter().to_provider_tools_format(tools_def) == expected
|
|
|
|
def test_openai_responses_adapter(self):
|
|
"""Test OpenAI Responses adapter format transformation."""
|
|
expected = [
|
|
{
|
|
"type": "function",
|
|
"name": "get_weather",
|
|
"description": "Get the weather in a given location",
|
|
"parameters": {
|
|
"type": "object",
|
|
"properties": {
|
|
"location": {
|
|
"type": "string",
|
|
"description": "The city, e.g. San Francisco",
|
|
},
|
|
"format": {
|
|
"type": "string",
|
|
"enum": ["celsius", "fahrenheit"],
|
|
"description": "The temperature unit to use.",
|
|
},
|
|
},
|
|
"required": ["location", "format"],
|
|
},
|
|
"strict": None,
|
|
}
|
|
]
|
|
assert OpenAIResponsesLLMAdapter().to_provider_tools_format(self.tools_def) == expected
|
|
|
|
def test_openai_realtime_adapter_with_custom_tools(self):
|
|
"""Test OpenAI Realtime adapter appends custom tools."""
|
|
tool_search = {"type": "tool_search"}
|
|
expected = [
|
|
{
|
|
"type": "function",
|
|
"name": "get_weather",
|
|
"description": "Get the weather in a given location",
|
|
"parameters": {
|
|
"type": "object",
|
|
"properties": {
|
|
"location": {
|
|
"type": "string",
|
|
"description": "The city, e.g. San Francisco",
|
|
},
|
|
"format": {
|
|
"type": "string",
|
|
"enum": ["celsius", "fahrenheit"],
|
|
"description": "The temperature unit to use.",
|
|
},
|
|
},
|
|
"required": ["location", "format"],
|
|
},
|
|
},
|
|
tool_search,
|
|
]
|
|
tools_def = self.tools_def
|
|
tools_def.custom_tools = {AdapterType.OPENAI: [tool_search]}
|
|
assert OpenAIRealtimeLLMAdapter().to_provider_tools_format(tools_def) == expected
|
|
|
|
def test_openai_adapter_ignores_other_adapter_custom_tools(self):
|
|
"""Test that OpenAI adapter ignores custom tools for other adapters."""
|
|
expected = OpenAILLMAdapter().to_provider_tools_format(self.tools_def)
|
|
tools_def = self.tools_def
|
|
tools_def.custom_tools = {AdapterType.GEMINI: [{"google_search": {}}]}
|
|
assert OpenAILLMAdapter().to_provider_tools_format(tools_def) == expected
|
|
|
|
def test_bedrock_adapter(self):
|
|
"""Test AWS Bedrock adapter format transformation."""
|
|
expected = [
|
|
{
|
|
"toolSpec": {
|
|
"name": "get_weather",
|
|
"description": "Get the weather in a given location",
|
|
"inputSchema": {
|
|
"json": {
|
|
"type": "object",
|
|
"properties": {
|
|
"format": {
|
|
"type": "string",
|
|
"enum": ["celsius", "fahrenheit"],
|
|
"description": "The temperature unit to use.",
|
|
},
|
|
"location": {
|
|
"type": "string",
|
|
"description": "The city, e.g. San Francisco",
|
|
},
|
|
},
|
|
"required": ["location", "format"],
|
|
}
|
|
},
|
|
}
|
|
}
|
|
]
|
|
assert AWSBedrockLLMAdapter().to_provider_tools_format(self.tools_def) == expected
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|