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

OpenAI-DotNet 7.0.0 #118

Merged
merged 11 commits into from
Jun 17, 2023
88 changes: 88 additions & 0 deletions OpenAI-DotNet-Tests/TestFixture_03_Chat.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
using NUnit.Framework;
using OpenAI.Chat;
using OpenAI.Tests.Weather;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Threading.Tasks;

namespace OpenAI.Tests
Expand Down Expand Up @@ -95,5 +98,90 @@ public async Task Test_3_GetChatStreamingCompletionEnumerableAsync()
}
}
}

[Test]
public async Task Test_4_GetChatFunctionCompletion()
{
Assert.IsNotNull(OpenAIClient.ChatEndpoint);
var messages = new List<Message>
{
new Message(Role.System, "You are a helpful weather assistant."),
new Message(Role.User, "What's the weather like today?"),
};

foreach (var message in messages)
{
Console.WriteLine($"{message.Role}: {message.Content}");
}

var functions = new List<Function>
{
new Function(
nameof(WeatherService.GetCurrentWeather),
"Get the current weather in a given location",
new JsonObject
{
["type"] = "object",
["properties"] = new JsonObject
{
["location"] = new JsonObject
{
["type"] = "string",
["description"] = "The city and state, e.g. San Francisco, CA"
},
["unit"] = new JsonObject
{
["type"] = "string",
["enum"] = new JsonArray {"celsius", "fahrenheit"}
}
},
["required"] = new JsonArray { "location", "unit" }
})
};

var chatRequest = new ChatRequest(messages, functions: functions, functionCall: "auto", model: "gpt-3.5-turbo-0613");
var result = await OpenAIClient.ChatEndpoint.GetCompletionAsync(chatRequest);
Assert.IsNotNull(result);
Assert.IsNotNull(result.Choices);
Assert.IsTrue(result.Choices.Count == 1);
messages.Add(result.FirstChoice.Message);

Console.WriteLine($"{result.FirstChoice.Message.Role}: {result.FirstChoice.Message.Content} | Finish Reason: {result.FirstChoice.FinishReason}");

var locationMessage = new Message(Role.User, "I'm in Glasgow, Scotland");
messages.Add(locationMessage);
Console.WriteLine($"{locationMessage.Role}: {locationMessage.Content}");
chatRequest = new ChatRequest(messages, functions: functions, functionCall: "auto", model: "gpt-3.5-turbo-0613");
result = await OpenAIClient.ChatEndpoint.GetCompletionAsync(chatRequest);

Assert.IsNotNull(result);
Assert.IsNotNull(result.Choices);
Assert.IsTrue(result.Choices.Count == 1);
messages.Add(result.FirstChoice.Message);

if (!string.IsNullOrWhiteSpace(result.FirstChoice.Message.Content))
{
Console.WriteLine($"{result.FirstChoice.Message.Role}: {result.FirstChoice.Message.Content} | Finish Reason: {result.FirstChoice.FinishReason}");

var unitMessage = new Message(Role.User, "celsius");
messages.Add(unitMessage);
Console.WriteLine($"{unitMessage.Role}: {unitMessage.Content}");
chatRequest = new ChatRequest(messages, functions: functions, functionCall: "auto", model: "gpt-3.5-turbo-0613");
result = await OpenAIClient.ChatEndpoint.GetCompletionAsync(chatRequest);
Assert.IsNotNull(result);
Assert.IsNotNull(result.Choices);
Assert.IsTrue(result.Choices.Count == 1);
}

Assert.IsTrue(result.FirstChoice.FinishReason == "function_call");
Assert.IsTrue(result.FirstChoice.Message.Function.Name == nameof(WeatherService.GetCurrentWeather));
Console.WriteLine($"{result.FirstChoice.Message.Role}: {result.FirstChoice.Message.Function.Name} | Finish Reason: {result.FirstChoice.FinishReason}");
Console.WriteLine($"{result.FirstChoice.Message.Function.Arguments}");
var functionArgs = JsonSerializer.Deserialize<WeatherArgs>(result.FirstChoice.Message.Function.Arguments.ToString());
var functionResult = WeatherService.GetCurrentWeather(functionArgs);
Assert.IsNotNull(functionResult);
messages.Add(new Message(Role.Function, functionResult));
Console.WriteLine($"{Role.Function}: {functionResult}");
}
}
}
23 changes: 23 additions & 0 deletions OpenAI-DotNet-Tests/TestServices/WeatherService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using System.Text.Json.Serialization;

namespace OpenAI.Tests.Weather
{
internal class WeatherService
{
public static string GetCurrentWeather(WeatherArgs weatherArgs)
{
return $"The current weather in {weatherArgs.Location} is 20 {weatherArgs.Unit}";
}
}

internal class WeatherArgs
{
[JsonPropertyName("location")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
public string Location { get; set; }

[JsonPropertyName("unit")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
public string Unit { get; set; }
}
}
38 changes: 34 additions & 4 deletions OpenAI-DotNet/Chat/ChatRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ public sealed class ChatRequest
/// The list of messages for the current chat session.
/// </param>
/// <param name="model">
/// ID of the model to use.<br/>
/// Currently, only gpt-4, gpt-3.5-turbo and gpt-3.5-turbo-0301 are supported.
/// Id of the model to use.<br/>
/// Currently, only gpt-4 and gpt-3.5-turbo and their variants are supported.
/// </param>
/// <param name="temperature">
/// What sampling temperature to use, between 0 and 2.
Expand Down Expand Up @@ -66,6 +66,12 @@ public sealed class ChatRequest
/// <param name="user">
/// A unique identifier representing your end-user, which can help OpenAI to monitor and detect abuse.
/// </param>
/// <param name="functionCall">
/// If functions is not null or empty, this is required. Pass "auto" to let the API decide, "none" if none are to be called, or {"name": "function-name"}
/// </param>
/// <param name="functions">
/// An optional list of functions to get arguments for.
/// </param>
public ChatRequest(
IEnumerable<Message> messages,
string model = null,
Expand All @@ -76,8 +82,10 @@ public ChatRequest(
int? maxTokens = null,
double? presencePenalty = null,
double? frequencyPenalty = null,
Dictionary<string, double> logitBias = null,
string user = null)
IReadOnlyDictionary<string, double> logitBias = null,
string user = null,
string functionCall = null,
IEnumerable<Function> functions = null)
{
Model = string.IsNullOrWhiteSpace(model) ? Models.Model.GPT3_5_Turbo : model;

Expand All @@ -103,6 +111,14 @@ public ChatRequest(
FrequencyPenalty = frequencyPenalty;
LogitBias = logitBias;
User = user;

if (string.IsNullOrEmpty(functionCall) && Functions is { Count: > 0 })
{
throw new ArgumentException("If functions are provided, please also provide a function_call specifier e.g. (auto, none, or {\"name\": \"<insert-function-name>\"})");
}

FunctionCall = functionCall;
Functions = functions?.ToList();
}

/// <summary>
Expand Down Expand Up @@ -200,5 +216,19 @@ public ChatRequest(
/// </summary>
[JsonPropertyName("user")]
public string User { get; }

/// <summary>
/// If functions is not null or empty, this is required. Pass "auto" to let the API decide, "none" if none are to be called, or {"name": "function-name"}
/// </summary>
[JsonPropertyName("function_call")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
public string FunctionCall { get; }

/// <summary>
/// An optional list of functions to get arguments for.
/// </summary>
[JsonPropertyName("functions")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
public IReadOnlyList<Function> Functions { get; }
}
}
2 changes: 2 additions & 0 deletions OpenAI-DotNet/Chat/Choice.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,12 @@ public Choice(

[JsonInclude]
[JsonPropertyName("message")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
public Message Message { get; private set; }

[JsonInclude]
[JsonPropertyName("delta")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
public Delta Delta { get; private set; }

[JsonInclude]
Expand Down
81 changes: 81 additions & 0 deletions OpenAI-DotNet/Chat/Function.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
using System;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;

namespace OpenAI.Chat
{
/// <summary>
/// <see href="https://platform.openai.com/docs/guides/gpt/function-calling"/>
/// </summary>
public class Function
{
/// <summary>
/// Creates a new function description to insert into a chat conversation.
/// </summary>
/// <param name="name">
/// Required. The name of the function to generate arguments for based on the context in a message.<br/>
/// May contain a-z, A-Z, 0-9, underscores and dashes, with a maximum length of 64 characters. Recommended to not begin with a number or a dash.
/// </param>
/// <param name="description">
/// An optional description of the function, used by the API to determine if it is useful to include in the response.
/// </param>
/// <param name="parameters">
/// An optional JSON object describing the parameters of the function that the model should generate in JSON schema format (json-schema.org).
/// </param>
/// <param name="arguments">
/// The arguments to use when calling the function.
/// </param>
public Function(string name, string description = null, JsonNode parameters = null, JsonNode arguments = null)
{
if (string.IsNullOrWhiteSpace(name))
{
throw new ArgumentException($"{nameof(name)} cannot be null or whitespace.", nameof(name));
}

if (name.Length > 64)
{
throw new ArgumentException($"{nameof(name)} cannot be longer than 64 characters.", nameof(name));
}

Name = name;
Description = description;
Parameters = parameters;
Arguments = arguments;
}

/// <summary>
/// The name of the function to generate arguments for.<br/>
/// May contain a-z, A-Z, 0-9, and underscores and dashes, with a maximum length of 64 characters.
/// Recommended to not begin with a number or a dash.
/// </summary>
[JsonInclude]
[JsonPropertyName("name")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
public string Name { get; private set; }

/// <summary>
/// The optional description of the function.
/// </summary>
[JsonInclude]
[JsonPropertyName("description")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
public string Description { get; private set; }

/// <summary>
/// The optional parameters of the function.
/// Describe the parameters that the model should generate in JSON schema format (json-schema.org).
/// </summary>
[JsonInclude]
[JsonPropertyName("parameters")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
public JsonNode Parameters { get; private set; }

/// <summary>
/// The arguments to use when calling the function.
/// </summary>
[JsonInclude]
[JsonPropertyName("arguments")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
public JsonNode Arguments { get; private set; }
}
}
16 changes: 15 additions & 1 deletion OpenAI-DotNet/Chat/Message.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,15 @@ public sealed class Message
/// Optional, The name of the author of this message.<br/>
/// May contain a-z, A-Z, 0-9, and underscores, with a maximum length of 64 characters.
/// </param>
public Message(Role role, string content, string name = null)
/// <param name="function">
/// The function that should be called, as generated by the model.
/// </param>
public Message(Role role, string content, string name = null, Function function = null)
{
Role = role;
Content = content;
Name = name;
Function = function;
}

/// <summary>
Expand All @@ -36,14 +40,24 @@ public Message(Role role, string content, string name = null)
/// </summary>
[JsonInclude]
[JsonPropertyName("content")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
public string Content { get; private set; }

/// <summary>
/// The function that should be called, as generated by the model.
/// </summary>
[JsonInclude]
[JsonPropertyName("function_call")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
public Function Function { get; private set; }

/// <summary>
/// Optional, The name of the author of this message.<br/>
/// May contain a-z, A-Z, 0-9, and underscores, with a maximum length of 64 characters.
/// </summary>
[JsonInclude]
[JsonPropertyName("name")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
public string Name { get; private set; }

public override string ToString() => Content ?? string.Empty;
Expand Down
2 changes: 2 additions & 0 deletions OpenAI-DotNet/Chat/Role.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,7 @@ public enum Role
Assistant = 2,
[EnumMember(Value = "user")]
User = 3,
[EnumMember(Value = "function")]
Function = 4,
}
}
2 changes: 1 addition & 1 deletion OpenAI-DotNet/Embeddings/EmbeddingsRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public sealed class EmbeddingsRequest
/// Each input must not exceed 8192 tokens in length.
/// </param>
/// <param name="model">
/// ID of the model to use.<br>
/// ID of the model to use.<br/>
/// Defaults to: <see cref="Model.Embedding_Ada_002"/>
/// </param>
/// <param name="user">
Expand Down
12 changes: 10 additions & 2 deletions OpenAI-DotNet/Models/Model.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,14 @@ public Model(string id, string ownedBy = null)
public string Parent { get; private set; }

/// <summary>
/// More capable than any GPT-3.5 model, able to do more complex tasks, and optimized for chat. Will be updated with our latest model iteration.
/// More capable than any GPT-3.5 model, able to do more complex tasks, and optimized for chat.
/// Will be updated with our latest model iteration.
/// </summary>
public static Model GPT4 { get; } = new("gpt-4", "openai");

/// <summary>
/// Same capabilities as the base gpt-4 mode but with 4x the context length. Will be updated with our latest model iteration.
/// Same capabilities as the base gpt-4 mode but with 4x the context length.
/// Will be updated with our latest model iteration. Tokens are 2x the price of gpt-4.
/// </summary>
public static Model GPT4_32K { get; } = new("gpt-4-32k", "openai");

Expand All @@ -87,6 +89,12 @@ public Model(string id, string ownedBy = null)
/// </summary>
public static Model GPT3_5_Turbo { get; } = new("gpt-3.5-turbo", "openai");

/// <summary>
/// Same capabilities as the base gpt-3.5-turbo mode but with 4x the context length.
/// Tokens are 2x the price of gpt-3.5-turbo. Will be updated with our latest model iteration.
/// </summary>
public static Model GPT3_5_Turbo_16K { get; } = new("gpt-3.5-turbo-16k", "openai");

/// <summary>
/// The most powerful, largest engine available, although the speed is quite slow.<para/>
/// Good at: Complex intent, cause and effect, summarization for audience
Expand Down
6 changes: 4 additions & 2 deletions OpenAI-DotNet/OpenAI-DotNet.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@ More context [on Roger Pincombe's blog](https://rogerpincombe.com/openai-dotnet-
<RepositoryUrl>https://github.com/RageAgainstThePixel/OpenAI-DotNet</RepositoryUrl>
<PackageTags>OpenAI, AI, ML, API, gpt-4, gpt-3.5-tubo, gpt-3, chatGPT, chat-gpt, gpt-2, gpt</PackageTags>
<Title>OpenAI API</Title>
<Version>6.8.7</Version>
<PackageReleaseNotes>Version 6.8.7
<Version>7.0.0</Version>
<PackageReleaseNotes>Version 7.0.0
- Added function calling to chat models
Version 6.8.7
- Added ToString and string operator to Moderation Scores
Version 6.8.6
- Populated finish reason in streaming chat final message content
Expand Down
Loading