From 4a18bcf917105e6dcadd694d3df211adc4cf03ff Mon Sep 17 00:00:00 2001 From: Chun-Ho Hung Date: Mon, 1 Dec 2025 16:41:30 -0800 Subject: [PATCH 1/2] Rename CustomToolCallDefinition to CustomCallDefinition and make CustomToolDefinition fields optional - Rename CustomToolCallDefinition to CustomCallDefinition for brevity - Make description and format_ fields optional in CustomToolDefinition - Update all tests to reflect the renaming and optional field changes - Add tests for optional fields and partial initialization - Use Optional[] syntax for Python 3.9 compatibility --- src/fastapi_poe/types.py | 19 +++++++++++---- tests/test_types.py | 50 +++++++++++++++++++++++++++------------- 2 files changed, 48 insertions(+), 21 deletions(-) diff --git a/src/fastapi_poe/types.py b/src/fastapi_poe/types.py index 3116884..f255ff2 100644 --- a/src/fastapi_poe/types.py +++ b/src/fastapi_poe/types.py @@ -223,11 +223,16 @@ class ToolDefinition(BaseModel): class CustomToolDefinition(BaseModel): - """Custom tool definition for OpenAI-compatible custom tools.""" + """Custom tool definition for OpenAI-compatible custom tools. + + Corresponds to `chat_completion_custom_tool_param.Custom` but + with a looser format specification. + + """ name: str - description: str - format_: dict[str, Any] = Field(alias="format") + description: Optional[str] = None + format_: Optional[dict[str, Any]] = Field(default=None, alias="format") model_config = ConfigDict(populate_by_name=True) @@ -263,8 +268,12 @@ class ToolCallDefinition(BaseModel): function: FunctionCallDefinition -class CustomToolCallDefinition(BaseModel): - """Custom tool call in model response.""" +class CustomCallDefinition(BaseModel): + """Custom tool call in model response. + + Corresponds to `chat_completion_message_custom_tool_call.Custom`. + + """ name: str input_: str = Field(alias="input") diff --git a/tests/test_types.py b/tests/test_types.py index 7f504a6..0b66ced 100644 --- a/tests/test_types.py +++ b/tests/test_types.py @@ -2,7 +2,7 @@ import pytest from fastapi_poe.types import ( CostItem, - CustomToolCallDefinition, + CustomCallDefinition, CustomToolDefinition, MessageReaction, PartialResponse, @@ -255,13 +255,31 @@ def test_field_name_works_with_populate_by_name(self) -> None: ) assert tool.format_ == {"type": "string"} - def test_requires_all_fields(self) -> None: - """Test that all required fields are validated""" + def test_requires_name_field(self) -> None: + """Test that name field is required""" with pytest.raises(pydantic.ValidationError): - CustomToolDefinition(name="my_tool") # type: ignore + CustomToolDefinition() # type: ignore - with pytest.raises(pydantic.ValidationError): - CustomToolDefinition(description="desc", format={}) # type: ignore + def test_optional_fields(self) -> None: + """Test that description and format are optional""" + tool = CustomToolDefinition(name="my_tool") + assert tool.name == "my_tool" + assert tool.description is None + assert tool.format_ is None + + def test_with_only_name_and_description(self) -> None: + """Test with only name and description""" + tool = CustomToolDefinition(name="tool", description="desc") + assert tool.name == "tool" + assert tool.description == "desc" + assert tool.format_ is None + + def test_with_only_name_and_format(self) -> None: + """Test with only name and format""" + tool = CustomToolDefinition(name="tool", format={"type": "string"}) + assert tool.name == "tool" + assert tool.description is None + assert tool.format_ == {"type": "string"} def test_serialization_uses_alias(self) -> None: """Test that serialization uses 'format' not 'format_'""" @@ -309,30 +327,30 @@ def test_invalid_type_for_format(self) -> None: CustomToolDefinition(name="tool", description="desc", format="not a dict") # type: ignore -class TestCustomToolCallDefinition: +class TestCustomCallDefinition: def test_basic_instantiation(self) -> None: - """Test creating CustomToolCallDefinition with alias 'input'""" - call = CustomToolCallDefinition(name="my_tool", input='{"arg": "value"}') + """Test creating CustomCallDefinition with alias 'input'""" + call = CustomCallDefinition(name="my_tool", input='{"arg": "value"}') assert call.name == "my_tool" assert call.input_ == '{"arg": "value"}' def test_field_name_works_with_populate_by_name(self) -> None: """Test that 'input_' field name also works due to populate_by_name=True""" - call = CustomToolCallDefinition(name="my_tool", input_='{"data": 123}') # type: ignore + call = CustomCallDefinition(name="my_tool", input_='{"data": 123}') # type: ignore assert call.input_ == '{"data": 123}' def test_requires_all_fields(self) -> None: """Test that all required fields are validated""" with pytest.raises(pydantic.ValidationError): - CustomToolCallDefinition(name="my_tool") # type: ignore + CustomCallDefinition(name="my_tool") # type: ignore with pytest.raises(pydantic.ValidationError): - CustomToolCallDefinition(input="data") # type: ignore + CustomCallDefinition(input="data") # type: ignore def test_serialization_uses_alias(self) -> None: """Test that serialization uses 'input' not 'input_'""" - call = CustomToolCallDefinition(name="tool1", input="test_input") + call = CustomCallDefinition(name="tool1", input="test_input") data = call.model_dump(by_alias=True) assert "input" in data assert "input_" not in data @@ -340,7 +358,7 @@ def test_serialization_uses_alias(self) -> None: def test_serialization_without_alias(self) -> None: """Test that serialization without by_alias uses 'input_'""" - call = CustomToolCallDefinition(name="tool1", input="test_input") + call = CustomCallDefinition(name="tool1", input="test_input") data = call.model_dump(by_alias=False) assert "input_" in data assert "input" not in data @@ -348,7 +366,7 @@ def test_serialization_without_alias(self) -> None: def test_json_serialization(self) -> None: """Test JSON serialization with alias""" - call = CustomToolCallDefinition(name="calculator", input='{"operation": "add"}') + call = CustomCallDefinition(name="calculator", input='{"operation": "add"}') json_str = call.model_dump_json(by_alias=True) assert '"input"' in json_str assert '"input_"' not in json_str @@ -359,6 +377,6 @@ def test_deserialization_from_json(self) -> None: "name": "calculator", "input": '{"operation": "add", "a": 1, "b": 2}', } - call = CustomToolCallDefinition(**json_data) + call = CustomCallDefinition(**json_data) assert call.name == "calculator" assert call.input_ == '{"operation": "add", "a": 1, "b": 2}' From 3a05d0fc48caef3718e8e2416f04dde6f7f2ee29 Mon Sep 17 00:00:00 2001 From: Chun-Ho Hung Date: Mon, 1 Dec 2025 16:46:55 -0800 Subject: [PATCH 2/2] bump to 0.0.81: Reviewers: Test Plan: Differential Revision: Asana Tasks: Screenshot: CC: Deploy To: --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 9dac472..f7558c1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "fastapi_poe" -version = "0.0.80" +version = "0.0.81" authors = [ { name="Yusheng Ding", email="yding@quora.com" }, { name="Kris Yang", email="kryang@quora.com" },