Repository: jakecyr/openai-function-calling

Issue:
Generate the `required` list for function parameters
## Overview of Feature
When generating the JSON schema for a function, some parameters might have default values. In the case where at least one parameter has a default value, we should populate `required` in the JSON schema with all required parameters.

## Why add this feature?

So GPT can skip some parameters if it thinks they should not be set.

## Acceptance Criteria to Meet

Given this function:
```
def get_current_weather(location, unit="fahrenheit"):
```

The generated JSON schema should be:
```
{
  "name": "get_current_weather",
  "description": "Get the current weather and return a summary.",
  "parameters": {
    "type": "object",
    "properties": {
      "location": {
        "type": "string"
      },
      "unit": {
        "type": "string"
      }
    },
    "required": ["location"]   <----------------------
  }
}
```

Discussion:
@jakecyr: I updated the library to better handle required parameters including the case you documented above. 

You can see the changes in [PR](https://github.com/jakecyr/openai-function-calling/pull/23).

You can install version [2.2.0](https://pypi.org/project/openai-function-calling/2.2.0/) to get the changes.

Solution patch:
diff --git a/openai_function_calling/function_inferrer.py b/openai_function_calling/function_inferrer.py
index 4982299..264708f 100644
--- a/openai_function_calling/function_inferrer.py
+++ b/openai_function_calling/function_inferrer.py
@@ -172,6 +172,9 @@ class FunctionInferrer:
             parameter_type: str = python_type_to_json_schema_type(parameter.kind.name)
             enum_values: list[str] | None = None
 
+            if parameter.default is inspect.Parameter.empty:
+                function_definition.required_parameters.append(name)
+
             if parameter_type == "null":
                 if isinstance(parameter.annotation, EnumMeta):
                     enum_values = list(


Test patch:
diff --git a/tests/test_function_inferer.py b/tests/test_function_inferer.py
index 7a8cc47..7d904f4 100644
--- a/tests/test_function_inferer.py
+++ b/tests/test_function_inferer.py
@@ -2,7 +2,7 @@
 
 from dataclasses import dataclass
 from enum import Enum, auto
-from typing import List  # noqa: UP035
+from typing import Optional
 
 import pytest
 
@@ -54,7 +54,7 @@ def add_generic_locations(locations: list) -> None:
     """
 
 
-def add_generic_typing_locations(locations: List) -> None:  # noqa: UP006
+def add_generic_typing_locations(locations: list) -> None:
     """Add a locations to the database.
 
     Args:
@@ -108,6 +108,55 @@ def get_temperature(location: str, unit: TemperatureUnit) -> float:
     return 75
 
 
+def get_temperature_with_default(
+    location: str,
+    unit: TemperatureUnit = TemperatureUnit.CELSIUS,
+) -> float:
+    """Get the current temperature.
+
+    Args:
+        location: The location to get the temperature for.
+        unit: The unit to return the temperature in.
+
+    Returns:
+        The current temperature in the specified unit.
+
+    """
+    return 75
+
+
+def get_temperature_with_all_defaults(
+    location: str = "Boston, MA",
+    unit: TemperatureUnit = TemperatureUnit.CELSIUS,
+) -> float:
+    """Get the current temperature.
+
+    Args:
+        location: The location to get the temperature for.
+        unit: The unit to return the temperature in.
+
+    Returns:
+        The current temperature in the specified unit.
+
+    """
+    return 75
+
+
+def get_local_places_with_optional_location(
+    location: Optional[str] = None,  # noqa: FA100
+) -> list:
+    """Get the current temperature.
+
+    Args:
+        location: The location to get the temperature for.
+
+    Returns:
+        The current temperature in the specified unit.
+
+    """
+    return []
+
+
 def test_infer_from_function_reference_returns_a_function_instance() -> None:
     function: Function = FunctionInferrer.infer_from_function_reference(
         fully_documented_sum
@@ -218,3 +267,38 @@ def test_infer_from_function_reference_with_typing_list_parameter_raises_value_e
         ValueError, match="Expected array parameter 'locations' to have an item type."
     ):
         FunctionInferrer.infer_from_function_reference(add_generic_typing_locations)
+
+
+def test_infer_from_function_reference_adds_expected_required_parameters() -> None:
+    function: Function = FunctionInferrer.infer_from_function_reference(
+        get_temperature_with_default
+    )
+
+    assert function.required_parameters == ["location"]
+
+
+def test_infer_from_function_reference_with_no_defaults_returns_required_parameters() -> (
+    None
+):
+    function: Function = FunctionInferrer.infer_from_function_reference(get_temperature)
+
+    assert set(function.required_parameters) == {"unit", "location"}
+
+
+def test_infer_from_function_reference_with_all_defaults_returns_no_required_parameters() -> (
+    None
+):
+    function: Function = FunctionInferrer.infer_from_function_reference(
+        get_temperature_with_all_defaults
+    )
+
+    assert len(function.required_parameters) == 0
+
+
+def test_infer_from_function_reference_with_optional_parameters_not_included_in_required() -> (
+    None
+):
+    function: Function = FunctionInferrer.infer_from_function_reference(
+        get_local_places_with_optional_location
+    )
+    assert function.required_parameters == []