支持 Assistants 流式 API 的 C# OpenAI 库
C# OpenAI 库,支持助手、聊天补全、微调、图像生成等功能
引言
本周,OpenAI 推出了 GPT4-turbo、GPTs、GPT 商店等。他们还发布了一个名为 Assistant API 的新 API。它是新的,还没有 C# 库。所以,我开发了它。
新增!!!(2024-5-07)
我添加了矢量文件,在聊天补全中使用。请查看!
如何使用?
通过 Nuget 下载
HigLabo.OpenAI
HigLabo.OpenAI
就是那个。
你可以在这里看到示例代码
主要类是 OpenAIClient
。你创建 OpenAIClient
类用于 OpenAI
API。
var apiKey = "your api key of OpenAI";
var cl = new OpenAIClient(apiKey);
对于 Azure 端点。
var apiKey = "your api key of OpenAI";
var cl = new OpenAIClient(new AzureSettings
(apiKey, "https://tinybetter-work-for-our-future.openai.azure.com/", "MyDeploymentName"));
调用 ChatCompletion
端点。
var cl = new OpenAIClient("API KEY");
var p = new ChatCompletionsParameter();
p.Messages.Add(new ChatMessage(ChatMessageRole.User, $"How to enjoy coffee?"));
p.Model = "gpt-4";
var res = await cl.ChatCompletionsAsync(p);
foreach (var choice in res.Choices)
{
Console.Write(choice.Message.Content);
}
Console.WriteLine();
Console.WriteLine();
Console.WriteLine("----------------------------------------");
Console.WriteLine("Total tokens: " + res.Usage.Total_Tokens);
使用服务器发送事件消耗 ChatCompletion
端点。
var cl = new OpenAIClient("API KEY");
var result = new ChatCompletionStreamResult();
await foreach (string text in cl.ChatCompletionsStreamAsync("How to enjoy coffee?", "gpt-4", result, CancellationToken.None))
{
Console.Write(text);
}
Console.WriteLine();
Console.WriteLine("Finish reason: " + result.GetFinishReason());
带有函数调用的 ChatCompletion
。
var cl = new OpenAIClient("API KEY");
var p = new ChatCompletionsParameter();
//ChatGPT can correct Newyork,Sanflansisco to New york and San Flancisco.
p.Messages.Add(new ChatMessage(ChatMessageRole.User,
$"I want to know the whether of these locations. Newyork, Sanflansisco, Paris, Tokyo."));
p.Model = "gpt-4";
{
var tool = new ToolObject("function");
tool.Function = new FunctionObject();
tool.Function.Name = "getWhether";
tool.Function.Description = "This service can get whether of specified location.";
tool.Function.Parameters = new
{
type = "object",
properties = new
{
locationList = new
{
type = "array",
description = "Location list that you want to know.",
items = new
{
type = "string",
}
}
}
};
p.Tools = new List<ToolObject>();
p.Tools.Add(tool);
}
{
var tool = new ToolObject("function");
tool.Function = new FunctionObject();
tool.Function.Name = "getLatLong";
tool.Function.Description =
"This service can get latitude and longitude of specified location.";
tool.Function.Parameters = new
{
type = "object",
properties = new
{
locationList = new
{
type = "array",
description = "Location list that you want to know.",
items = new
{
type = "string",
}
}
}
};
p.Tools = new List<ToolObject>();
p.Tools.Add(tool);
}
var result = new ChatCompletionStreamResult();
await foreach (var text in cl.ChatCompletionsStreamAsync(p, result, CancellationToken.None))
{
Console.Write(text);
}
Console.WriteLine();
foreach (var f in result.GetFunctionCallList())
{
Console.WriteLine("Function name is " + f.Name);
Console.WriteLine("Arguments is " + f.Arguments);
}
通过 ChatCompletion
端点使用视觉 API。
var cl = new OpenAIClient("API KEY");
var p = new ChatCompletionsParameter();
var message = new ChatImageMessage(ChatMessageRole.User);
message.AddTextContent("Please describe this image.");
message.AddImageFile(Path.Combine(Environment.CurrentDirectory, "Image", "Pond.jpg"));
p.Messages.Add(message);
p.Model = "gpt-4-vision-preview";
p.Max_Tokens = 300;
var result = new ChatCompletionStreamResult();
await foreach (var text in cl.ChatCompletionsStreamAsync(p, result, CancellationToken.None))
{
Console.Write(text);
}
上传文件以进行微调或传递给助手。
var p = new FileUploadParameter();
p.File.SetFile("my_file.pdf", File.ReadAllBytes("D:\\Data\\my_file.pdf"));
p.Purpose = "assistants";
var res = await client.FileUploadAsync(p);
Console.WriteLine(res);
图像生成。
var cl = new OpenAIClient("API KEY");
var res = await cl.ImagesGenerationsAsync("Blue sky and green field.");
foreach (var item in res.Data)
{
Console.WriteLine(item.Url);
}
通过 API 创建助手。
var cl = new OpenAIClient("API KEY");
var p = new AssistantCreateParameter();
p.Name = "Legal tutor";
p.Instructions = "You are a personal legal tutor.
Write and run code to legal questions based on passed files.";
p.Model = "gpt-4-1106-preview";
p.Tools = new List<ToolObject>();
p.Tools.Add(new ToolObject("code_interpreter"));
p.Tools.Add(new ToolObject("retrieval"));
var res = await cl.AssistantCreateAsync(p);
Console.WriteLine(res);
将文件添加到助手。
var cl = new OpenAIClient("API KEY");
var res = await cl.FilesAsync();
foreach (var item in res.Data)
{
if (item.Purpose == "assistants")
{
var res1 = await cl.AssistantFileCreateAsync(id, item.Id);
}
}
调用助手流式 API。
var assistantId = "your assistant Id";
var threadId = "your thread Id";
if (threadId.Length == 0)
{
var res = await cl.ThreadCreateAsync();
threadId = res.Id;
}
{
var p = new MessageCreateParameter();
p.Thread_Id = threadId;
p.Role = "user";
p.Content = "Hello! I want to know how to use OpenAI assistant API
to get stream response.";
var res = await cl.MessageCreateAsync(p);
}
{
var p = new RunCreateParameter();
p.Assistant_Id = assistantId;
p.Thread_Id = threadId;
p.Stream = true;
var result = new AssistantMessageStreamResult();
await foreach (string text in cl.RunCreateStreamAsync
(p, result, CancellationToken.None))
{
Console.Write(text);
}
Console.WriteLine();
// You can get each server sent event value by these property.
Console.WriteLine(JsonConvert.SerializeObject(result.Thread));
Console.WriteLine(JsonConvert.SerializeObject(result.Run));
Console.WriteLine(JsonConvert.SerializeObject(result.RunStep));
Console.WriteLine(JsonConvert.SerializeObject(result.Message));
}
函数调用并将工具输出提交给助手 API。
var cl = new OpenAIClient("API KEY");
var p0 = new MessageCreateParameter();
p0.Thread_Id = threadId;
p0.Role = "user";
p0.Content = $"I want to know the whether of Tokyo.";
var res = await cl.MessageCreateAsync(p0);
var p = new RunCreateParameter();
p.Assistant_Id = assistantId;
p.Thread_Id = threadId;
p.Tools = new List<ToolObject>();
p.Tools.Add(CreateGetWheatherTool());
var result = new AssistantMessageStreamResult();
await foreach (string text in cl.RunCreateStreamAsync(p, result, CancellationToken.None))
{
Console.Write(text);
}
Console.WriteLine();
Console.WriteLine(JsonConvert.SerializeObject(result.Run));
if (result.Run != null)
{
if (result.Run.Status == "requires_action" &&
result.Run.Required_Action != null)
{
var p1 = new SubmitToolOutputsParameter();
p1.Thread_Id = threadId;
p1.Run_Id = result.Run.Id;
p1.Tool_Outputs = new();
foreach (var toolCall in result.Run.Required_Action.GetToolCallList())
{
Console.WriteLine(toolCall.ToString());
//Pass output from calling your function.
var output = new ToolOutput();
output.Tool_Call_Id = toolCall.Id;
var o = new
{
Wheather = "Cloud",
Temperature = "20℃",
Forecast = "Rain after 3 hours",
};
output.Output = $"{toolCall.Function.Arguments} is {JsonConvert.SerializeObject(o)}";
p1.Tool_Outputs.Add(output);
}
await foreach (var text in cl.SubmitToolOutputsStreamAsync(p1))
{
Console.Write(text);
}
}
}
类架构
主要的类有
OpenAIClient
XXXParameter
XXXAsync
XXXResponse
RestApiResponse
QueryParameter
OpenAIClient
此类管理 API 密钥并调用端点。
你可以在智能感知中将所有端点视为方法。
我为所有具有必需参数的端点提供了方法。这些是调用端点的最简单方法。
var res = await cl.AudioTranscriptionsAsync("GoodMorningItFineDayToday.mp3"
, new MemoryStream(File.ReadAllBytes("D:\\Data\\Dev\\GoodMorningItFineDayToday.mp3"))
, "whisper-1");
OpenAI 提供三种类型的端点。这些内容类型是 json
、form-data
和 server-sent-event
。示例就在那里。
Json 端点
Form-data 端点
流式端点
因此,我提供了 SendJsonAsync
、SendFormDataAsync
、GetStreamAsync
方法来调用这些端点。这些类管理 HTTP 标头和 content-type
并正确处理响应。
你可以通过传递参数对象来调用端点。
var p = new AudioTranscriptionsParameter();
p.File.SetFile("GoodMorningItFineDayToday.mp3",
new MemoryStream(File.ReadAllBytes("D:\\Data\\Dev\\GoodMorningItFineDayToday.mp3")));
p.Model = "whisper-1";
var res = await cl.SendFormDataAsync<AudioTranscriptionsParameter,
AudioTranscriptionsResponse>(p, CancellationToken.None);
但是这种方法需要参数和响应的类型,所以我提供了易于使用的方法。
var p = new AudioTranscriptionsParameter();
p.File.SetFile("GoodMorningItFineDayToday.mp3",
new MemoryStream(File.ReadAllBytes("D:\\Data\\Dev\\GoodMorningItFineDayToday.mp3")));
p.Model = "whisper-1";
var res = await cl.AudioTranscriptionsAsync(p);
XXXParameter
我提供了代表端点所有值的参数类。
例如,这是创建助手端点。
这是 AssistantCreateParameter
类。
/// <summary>
/// Create an assistant with a model and instructions.
/// <seealso href="https://api.openai.com/v1/assistants">
/// https://api.openai.com/v1/assistants</seealso>
/// </summary>
public partial class AssistantCreateParameter : RestApiParameter, IRestApiParameter
{
string IRestApiParameter.HttpMethod { get; } = "POST";
/// <summary>
/// ID of the model to use. You can use the List models API to see all of
/// your available models, or see our Model overview for descriptions of them.
/// </summary>
public string Model { get; set; } = "";
/// <summary>
/// The name of the assistant. The maximum length is 256 characters.
/// </summary>
public string? Name { get; set; }
/// <summary>
/// The description of the assistant. The maximum length is 512 characters.
/// </summary>
public string? Description { get; set; }
/// <summary>
/// The system instructions that the assistant uses.
/// The maximum length is 32768 characters.
/// </summary>
public string? Instructions { get; set; }
/// <summary>
/// A list of tool enabled on the assistant.
/// There can be a maximum of 128 tools per assistant.
/// Tools can be of types code_interpreter, retrieval, or function.
/// </summary>
public List<ToolObject>? Tools { get; set; }
/// <summary>
/// A list of file IDs attached to this assistant.
/// There can be a maximum of 20 files attached to the assistant.
/// Files are ordered by their creation date in ascending order.
/// </summary>
public List<string>? File_Ids { get; set; }
/// <summary>
/// Set of 16 key-value pairs that can be attached to an object.
/// This can be useful for storing additional information about the object
/// in a structured format. Keys can be a maximum of 64 characters long
/// and values can be a maximum of 512 characters long.
/// </summary>
public object? Metadata { get; set; }
string IRestApiParameter.GetApiPath()
{
return $"/assistants";
}
public override object GetRequestBody()
{
return new {
model = this.Model,
name = this.Name,
description = this.Description,
instructions = this.Instructions,
tools = this.Tools,
file_ids = this.File_Ids,
metadata = this.Metadata,
};
}
}
这些参数类是从 API 文档生成的。你可以在 Github 上看到实际的生成器代码。
XXXAsync
这些方法是生成的。我生成了四种方法,你可以轻松调用 API 端点。
AssistantCreate
端点的一个例子。
public async ValueTask<AssistantCreateResponse> AssistantCreateAsync(string model)
public async ValueTask<AssistantCreateResponse>
AssistantCreateAsync(string model, CancellationToken cancellationToken)
public async ValueTask<AssistantCreateResponse>
AssistantCreateAsync(AssistantCreateParameter parameter)
public async ValueTask<AssistantCreateResponse>
AssistantCreateAsync(AssistantCreateParameter parameter,
CancellationToken cancellationToken)
基本上,有两种类型的方法。
一种是仅传递所需参数值。
AssistantCreate
端点需要模型。所以,我生成了
public async ValueTask<AssistantCreateResponse> AssistantCreateAsync(string model)
此方法易于使用,只需使用所需参数即可调用端点。
另一种是你可以使用所有参数值调用 API 端点。
你可以像这样创建参数
var p = new AssistantCreateParameter();
p.Name = "Legal tutor";
p.Instructions = "You are a personal legal tutor.
Write and run code to legal questions based on passed files.";
p.Model = "gpt-4-1106-preview";
p.Tools = new List<ToolObject>();
p.Tools.Add(new ToolObject("code_interpreter"));
p.Tools.Add(new ToolObject("retrieval"));
并将其传递给方法。
var res = await cl.AssistantCreateAsync(p);
XXXResponse
响应类表示 API 端点的实际响应数据。
例如,检索助手端点返回助手对象。
我提供了 AssistantObjectResponse
。(我创建了这个类,而不是代码生成。)
public class AssistantObjectResponse: RestApiResponse
{
public string Id { get; set; } = "";
public int Created_At { get; set; }
public DateTimeOffset CreateTime
{
get
{
return new DateTimeOffset
(DateTime.UnixEpoch.AddSeconds(this.Created_At), TimeSpan.Zero);
}
}
public string Name { get; set; } = "";
public string Description { get; set; } = "";
public string Model { get; set; } = "";
public string Instructions { get; set; } = "";
public List<ToolObject> Tools { get; set; } = new();
public List<string>? File_Ids { get; set; }
public object? MetaData { get; set; }
public override string ToString()
{
return $"{this.Id}\r\n{this.Name}\r\n{this.Instructions}";
}
}
你可以获取 API 端点的响应值。
RestApiResponse
有时,你想获取响应的元数据。你可以获取响应文本、创建此响应的请求对象等。
这是 RestApiResponse
类。
public abstract class RestApiResponse : IRestApiResponse
{
object? IRestApiResponse.Parameter
{
get { return _Parameter; }
}
HttpRequestMessage IRestApiResponse.Request
{
get { return _Request!; }
}
string IRestApiResponse.RequestBodyText
{
get { return _RequestBodyText; }
}
HttpStatusCode IRestApiResponse.StatusCode
{
get { return _StatusCode; }
}
Dictionary<String, String> IRestApiResponse.Headers
{
get { return _Headers; }
}
string IRestApiResponse.ResponseBodyText
{
get { return _ResponseBodyText; }
}
}
这些是隐藏的属性。你可以通过转换为 RestApiResponse
类来访问它
var p = new AssistantCreateParameter();
p.Name = "Legal tutor";
p.Instructions = "You are a personal legal tutor.
Write and run code to legal questions based on passed files.";
p.Model = "gpt-4-1106-preview";
var res = await cl.AssistantCreateAsync(p);
var iRes = res as RestApiResponse;
var responseText = iRes.ResponseBodyText;
Dictionary<string, string> responseHeaders = iRes.Headers;
var parameter = iRes.Parameter as AssistantCreateParameter;
你可以通过 RestApiResponse
将响应文本记录到你自己的日志数据库中。
QueryParameter
一些 API 端点提供过滤、分页功能。你可以通过 QueryParameter
类指定条件。
你可以像这样指定顺序
var p = new MessagesParameter();
p.Thread_Id = "thread_xxxxxxxxxxxx";
p.QueryParameter.Order = "asc";
结论
我对 OpenAI GPTs、GPT 商店等感到非常兴奋。如果你也对 OpenAI 感兴趣,我的库可能会对你的工作有所帮助。 很高兴使用!
历史
- 2023年11月6日:OpenAI 发布了助手 API
- 2023年11月11日:首次发布
HigLabo.OpenAI
- 2023年12月2日:添加发送消息示例
- 2024年1月8日:添加视觉 API 功能
- 2024年2月12日:更新文件上传端点
- 2024年3月17日:添加助手流式 API 的功能