Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Several improvements / fixes for functions #320

Merged
merged 11 commits into from
Jul 25, 2023
23 changes: 21 additions & 2 deletions OpenAI.Playground/TestHelpers/ChatCompletionTestHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,13 @@ public static async Task RunChatFunctionCallTest(IOpenAIService sdk)
@enum: new List<string> { "celsius", "fahrenheit" })
.AddParameter("num_days", "integer", "The number of days to forecast")
.Build();

var fn3 = new FunctionDefinitionBuilder("get_current_datetime", "Get the current date and time, e.g. 'Saturday, June 24, 2023 6:14:14 PM'")
.Build();

var fn4 = new FunctionDefinitionBuilder("identify_number_sequence", "Get a sequence of numbers present in the user message")
.AddArrayParameter("values", "number", "Sequence of numbers specified by the user")
.Build();

try
{
Expand All @@ -121,16 +128,21 @@ public static async Task RunChatFunctionCallTest(IOpenAIService sdk)
Messages = new List<ChatMessage>
{
ChatMessage.FromSystem("Don't make assumptions about what values to plug into functions. Ask for clarification if a user request is ambiguous."),

// to test weather forecast functions:
ChatMessage.FromUser("Give me a weather report for Chicago, USA, for the next 5 days."),

// or to test array functions, use this instead:
// ChatMessage.FromUser("The combination is: One. Two. Three. Four. Five."),
},
Functions = new List<FunctionDefinition> { fn1, fn2 },
Functions = new List<FunctionDefinition> { fn1, fn2, fn3, fn4 },
// optionally, to force a specific function:
// FunctionCall = new Dictionary<string, string> { { "name", "get_current_weather" } },
MaxTokens = 50,
Model = Models.Gpt_3_5_Turbo_0613
});

/* expected output along the lines of:
/* when testing weather forecasts, expected output should be along the lines of:

Message:
Function call: get_n_day_weather_forecast
Expand All @@ -139,6 +151,13 @@ public static async Task RunChatFunctionCallTest(IOpenAIService sdk)
num_days: 5
*/

/* when testing array functions, expected output should be along the lines of:

Message:
Function call: identify_number_sequence
values: [1, 2, 3, 4, 5]
*/

await foreach (var completionResult in completionResults)
{
if (completionResult.Successful)
Expand Down
62 changes: 53 additions & 9 deletions OpenAI.SDK/ObjectModels/RequestModels/ChatMessage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,14 @@ public class FunctionParameterPropertyValue
[JsonPropertyName("type")]
public string Type { get; set; } = "string";

/// <summary>
/// If type is "array", this specifies the element type for all items in the array.
/// If type is not "array", this should be null.
/// For more details, see https://json-schema.org/understanding-json-schema/reference/array.html
/// </summary>
[JsonPropertyName("items")]
public ArrayItemsDefinition? Items { get; set; }

/// <summary>
/// Optional. Argument description.
/// </summary>
Expand All @@ -174,6 +182,22 @@ public class FunctionParameterPropertyValue
/// </summary>
[JsonPropertyName("enum")]
public IList<string>? Enum { get; set; }

public class ArrayItemsDefinition
{
/// <summary>
/// Argument type for all array items (e.g. string, integer, and so on).
/// For examples, see https://json-schema.org/understanding-json-schema/reference/object.html
/// </summary>
[JsonPropertyName("type")]
public string Type { get; set; } = "string";

/// <summary>
/// Optional. Array item description.
/// </summary>
[JsonPropertyName("description")]
public string? Description { get; set; }
}
}

public class FunctionDefinitionBuilder
Expand All @@ -187,7 +211,11 @@ public FunctionDefinitionBuilder(string fnName, string? fnDescription)
_definition = new()
{
Name = fnName,
Description = fnDescription
Description = fnDescription,
Parameters = new FunctionParameters
{
Properties = new Dictionary<string, FunctionParameterPropertyValue>()
}
};
}

Expand Down Expand Up @@ -222,19 +250,35 @@ public FunctionDefinitionBuilder AddParameter(
string name, string type, string? description = null,
IList<string>? @enum = null, bool required = true)
{
_definition.Parameters ??= new FunctionParameters();
_definition.Parameters.Properties ??= new Dictionary<string, FunctionParameterPropertyValue>();
var value = new FunctionParameterPropertyValue() { Type = type, Description = description, Enum = @enum };

Add(name, value, required);

_definition.Parameters.Properties[name] =
new FunctionParameterPropertyValue() { Type = type, Description = description, Enum = @enum };
return this;
}

public FunctionDefinitionBuilder AddArrayParameter(
string name, string itemType, string? description = null, string? itemDescription = null,
IList<string>? @enum = null, bool required = true)
{
var items = new FunctionParameterPropertyValue.ArrayItemsDefinition { Type = itemType, Description = itemDescription };
var value = new FunctionParameterPropertyValue() { Type = "array", Items = items, Description = description, Enum = @enum };

Add(name, value, required);

return this;
}

private void Add(string name, FunctionParameterPropertyValue value, bool required)
{
var pars = _definition.Parameters!;
pars.Properties![name] = value;

if (required)
{
_definition.Parameters.Required ??= new List<string>();
_definition.Parameters.Required.Add(name);
pars.Required ??= new List<string>();
pars.Required.Add(name);
}

return this;
}

public FunctionDefinition Build() => _definition;
Expand Down
2 changes: 1 addition & 1 deletion OpenAI.SDK/ObjectModels/ResponseModels/BaseResponse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public record Error
{
[JsonPropertyName("code")] public string? Code { get; set; }

[JsonPropertyName("message")] public string? Message { get; set; }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need this? Do you have any sample responses that show why the change is necessary?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, please see the sample response in the description of commit 80bde47

It came back as an array of strings (rather than a string) which caused the JSON serializer to blow up

[JsonPropertyName("message")] public object? Message { get; set; }

[JsonPropertyName("param")] public string? Param { get; set; }

Expand Down