.NET的ChatGpt集成库,支持OpenAI和Azure OpenAi服务。
该库可在Nuget上找到。只需在包装管理器GUI中搜索ChatGptNet或在.NET CLI中运行以下命令:
dotnet add package ChatGptNet在应用程序启动时注册CHATGPT服务:
builder . Services . AddChatGpt ( options =>
{
// OpenAI.
//options.UseOpenAI(apiKey: "", organization: "");
// Azure OpenAI Service.
//options.UseAzure(resourceName: "", apiKey: "", authenticationType: AzureAuthenticationType.ApiKey);
options . DefaultModel = " my-model " ;
options . DefaultEmbeddingModel = " text-embedding-ada-002 " ;
options . MessageLimit = 16 ; // Default: 10
options . MessageExpiration = TimeSpan . FromMinutes ( 5 ) ; // Default: 1 hour
options . DefaultParameters = new ChatGptParameters
{
MaxTokens = 800 ,
//MaxCompletionTokens = 800, // o1 series models support this property instead of MaxTokens
Temperature = 0.7
} ;
} ) ;ChatGptNet支持OpenAI和Azure OpenAI服务,因此有必要根据所选提供商设置正确的配置设置:
Chatgpt可以与不同的型号一起聊天,无论是在OpenAI和Azure Openai服务上。使用DefaultModel属性,您可以指定将使用的默认模型,除非您在AskAsync或AsyStreamAsync方法中传递了显式值。
即使不是严格进行聊天对话的必要条件,图书馆也支持OpenAI和Azure Openai上的嵌入API。至于聊天完成,可以使用不同的型号进行嵌入。使用DefaultEmbedModel属性,您可以指定将使用的默认模型,除非您在GeteMbedDingAsync方法中传递了显式值。
当前可用的型号是:
它们具有固定的名称,可在OpenAICHATGPTMODELS.CS文件中使用。
在Azure OpenAi服务中,您需要先部署模型才能拨打电话。部署模型时,您需要为其分配一个名称,该名称必须与您使用的名称与ChatGptNet匹配。
注意某些模型在所有区域均不可用。您可以参考模型摘要表和区域可用性页面以检查当前可用性。
CHATGPT的目的是支持对话方案:用户可以与Chatgpt交谈,而无需为每次互动指定完整上下文。但是,对话历史记录不是由OpenAI或Azure OpenAi服务管理的,因此由我们保留当前状态。默认情况下, ChatGptNet使用存储每个对话的消息的内存速度来处理这一要求。可以使用以下属性设置该行为:
如有必要,可以通过实现IchatgptCache接口,然后调用withCache扩展方法来提供自定义缓存:
public class LocalMessageCache : IChatGptCache
{
private readonly Dictionary < Guid , IEnumerable < ChatGptMessage > > localCache = new ( ) ;
public Task SetAsync ( Guid conversationId , IEnumerable < ChatGptMessage > messages , TimeSpan expiration , CancellationToken cancellationToken = default )
{
localCache [ conversationId ] = messages . ToList ( ) ;
return Task . CompletedTask ;
}
public Task < IEnumerable < ChatGptMessage > ? > GetAsync ( Guid conversationId , CancellationToken cancellationToken = default )
{
localCache . TryGetValue ( conversationId , out var messages ) ;
return Task . FromResult ( messages ) ;
}
public Task RemoveAsync ( Guid conversationId , CancellationToken cancellationToken = default )
{
localCache . Remove ( conversationId ) ;
return Task . CompletedTask ;
}
public Task < bool > ExistsAsync ( Guid conversationId , CancellationToken cancellationToken = default )
{
var exists = localCache . ContainsKey ( conversationId ) ;
return Task . FromResult ( exists ) ;
}
}
// Registers the custom cache at application startup.
builder . Services . AddChatGpt ( /* ... */ ) . WithCache < LocalMessageCache > ( ) ;我们还可以在启动时设置chatgpt参数以进行聊天完成。检查官方文档中是否可用参数列表及其含义。
可以自动从IconFiguration读取配置,例如AppSettings.json文件中的ChatGpt部分:
"ChatGPT": {
"Provider": "OpenAI", // Optional. Allowed values: OpenAI (default) or Azure
"ApiKey": "", // Required
//"Organization": "", // Optional, used only by OpenAI
"ResourceName": "", // Required when using Azure OpenAI Service
"ApiVersion": "2024-10-21", // Optional, used only by Azure OpenAI Service (default: 2024-10-21)
"AuthenticationType": "ApiKey", // Optional, used only by Azure OpenAI Service. Allowed values: ApiKey (default) or ActiveDirectory
"DefaultModel": "my-model",
"DefaultEmbeddingModel": "text-embedding-ada-002", // Optional, set it if you want to use embedding
"MessageLimit": 20,
"MessageExpiration": "00:30:00",
"ThrowExceptionOnError": true // Optional, default: true
//"User": "UserName",
//"DefaultParameters": {
// "Temperature": 0.8,
// "TopP": 1,
// "MaxTokens": 500,
// "MaxCompletionTokens": null, // o1 series models support this property instead of MaxTokens
// "PresencePenalty": 0,
// "FrequencyPenalty": 0,
// "ResponseFormat": { "Type": "text" }, // Allowed values for Type: text (default) or json_object
// "Seed": 42 // Optional (any integer value)
//},
//"DefaultEmbeddingParameters": {
// "Dimensions": 1536
//}
}
然后使用相应的che addchatgpt方法的超载:
// Adds ChatGPT service using settings from IConfiguration.
builder . Services . AddChatGpt ( builder . Configuration ) ;AddChatGpt方法还具有一个超负荷,它接受IserviceProvider作为参数。例如,如果我们在Web API中,则可以使用它,并且我们需要支持每个用户具有不同API密钥的方案,可以通过依赖项注入来检索这些API密钥,以访问数据库:
builder . Services . AddChatGpt ( ( services , options ) =>
{
var accountService = services . GetRequiredService < IAccountService > ( ) ;
// Dynamically gets the API Key from the service.
var apiKey = " ... "
options . UseOpenAI ( apiKyey ) ;
} ) ;在更复杂的方案中,可以使用代码和ICONFIGURATION配置ChatGptNet 。如果我们要设置一堆常见属性,这可能很有用,但是与此同时,我们需要一些配置逻辑。例如:
builder . Services . AddChatGpt ( ( services , options ) =>
{
// Configure common properties (message limit and expiration, default parameters, ecc.) using IConfiguration.
options . UseConfiguration ( builder . Configuration ) ;
var accountService = services . GetRequiredService < IAccountService > ( ) ;
// Dynamically gets the API Key from the service.
var apiKey = " ... "
options . UseOpenAI ( apiKyey ) ;
} ) ;ChatGptNet使用HTTPCLIENT调用聊天完成和嵌入API。如果需要自定义,则可以使用ADDCHATGPT方法的过载,该方法接受操作<ihttpclientbuiler>作为参数。例如,如果要为HTTP客户端添加弹性(假设重试策略),则可以使用Polly:
// using Microsoft.Extensions.DependencyInjection;
// Requires: Microsoft.Extensions.Http.Resilience
builder . Services . AddChatGpt ( context . Configuration ,
httpClient =>
{
// Configures retry policy on the inner HttpClient using Polly.
httpClient . AddStandardResilienceHandler ( options =>
{
options . AttemptTimeout . Timeout = TimeSpan . FromMinutes ( 1 ) ;
options . CircuitBreaker . SamplingDuration = TimeSpan . FromMinutes ( 3 ) ;
options . TotalRequestTimeout . Timeout = TimeSpan . FromMinutes ( 3 ) ;
} ) ;
} )有关此主题的更多信息,请访问官方文档。
该库可用于使用.NET 6.0或更高版本构建的任何.NET应用程序中。例如,我们可以以这种方式创建最小的API:
app . MapPost ( " /api/chat/ask " , async ( Request request , IChatGptClient chatGptClient ) =>
{
var response = await chatGptClient . AskAsync ( request . ConversationId , request . Message ) ;
return TypedResults . Ok ( response ) ;
} )
. WithOpenApi ( ) ;
// ...
public record class Request ( Guid ConversationId , string Message ) ;如果我们只想检索响应消息,我们可以调用getContent方法:
var content = response . GetContent ( ) ;请注意,如果响应已通过内容过滤系统过滤,则GetContent将返回null 。因此,在尝试访问实际内容之前,您应始终检查
response.IsContentFiltered。
使用配置,可以设置默认参数以进行聊天完成。但是,我们还可以使用接受ChatGptParameters对象的AskAsync或AskStreamAsync Overloads指定每个请求的参数:
var response = await chatGptClient . AskAsync ( conversationId , message , new ChatGptParameters
{
MaxTokens = 150 ,
Temperature = 0.7
} ) ;我们不需要指定所有参数,而只需要覆盖的参数。其他将从默认配置中获取。
众所周知,CHATGPT是非确定性的。这意味着相同的输入可以产生不同的输出。为了控制这种行为,我们可以使用温度和TOPP参数。例如,将温度设置为接近0的值使模型更具确定性,同时将其设置为接近1的值会使模型更具创造力。但是,这并不总是足以获得相同输入的相同输出。为了解决这个问题,OpenAI引入了种子参数。如果指定,模型应确定性采样,以便具有相同种子的重复请求和参数应返回相同的结果。但是,在这种情况下,不能保证确定性,您应该参考SystemFingerPrint响应参数以监视后端的变化。该值的变化意味着后端配置已经改变,这可能会影响确定性。
与往常一样,可以在默认配置或AskAsync或AskStreamasync Overloads中指定种子属性。
注意种子和Systemfingerprint仅由最近的模型(例如GPT-4-1106-Preiview)支持。
如果要以JSON格式进行响应,则可以使用ResponseFormat参数:
var response = await chatGptClient . AskAsync ( conversationId , message , new ChatGptParameters
{
ResponseFormat = ChatGptResponseFormat . Json ,
} ) ;这样,响应将永远是有效的JSON。请注意,还必须指示模型通过系统或用户消息产生JSON。如果您不这样做,则该模型将返回错误。
与往常一样,可以在默认配置或AskAsync或AskStreamAsync Overloads中指定响应Format属性,该属性接受ChatGptParameters。
Note ResponseFormat仅由最新模型(例如GPT-4-1106-preiview)支持。
AskAsync和AskStreamAsync (见下文)方法提供了需要对话参数的过载。如果我们传递一个空值,则会生成一个随机的值并返回。我们可以在AskAsync或AskStreamAsync的随后调用中传递此值,以便库自动检索当前对话的先前消息(根据MessageLimit和MessageExpiration设置),并将其发送到聊天完成API。
这是所有聊天交互的默认行为。如果您想从对话历史记录中忽略特定的交互,则可以将addoconversation history参数设置为false :
var response = await chatGptClient . AskAsync ( conversationId , message , addToConversationHistory : false ) ;这样,该消息将发送到聊天完成API,但是IT和ChatGpt的相应答案不会添加到对话历史记录中。
另一方面,在某些情况下,手动在对话历史上手动添加聊天互动(即一个问题,然后是答案)可能很有用。例如,我们可能要添加一个由机器人生成的消息。在这种情况下,我们可以使用AddInteractionSASYNC方法:
await chatGptClient . AddInteractionAsync ( conversationId , question : " What is the weather like in Taggia? " ,
answer : " It's Always Sunny in Taggia " ) ;该问题将作为用户消息添加,答案将在对话历史记录中作为助理消息添加。与往常一样,这些新消息(尊重MessageLimit选项)将在随后的AskAsync或AskStreamAsync的调用中使用。
聊天完成API支持响应流。使用此功能时,将发送部分消息Deltas,例如在ChatGpt中。令牌将作为仅数据的服务器事件发送时,它们将作为可用。 ChatGptNet使用AskStreamAsync方法提供响应流:
// Requests a streaming response.
var responseStream = chatGptClient . AskStreamAsync ( conversationId , message ) ;
await foreach ( var response in responseStream )
{
Console . Write ( response . GetContent ( ) ) ;
await Task . Delay ( 80 ) ;
}请注意,如果响应已通过内容过滤系统过滤,则foreach中的getContent方法将返回null字符串。因此,在尝试访问实际内容之前,您应始终检查
response.IsContentFiltered。
响应流通过返回iAsyncenumerable来起作用,因此即使在Web API项目中也可以使用:
app . MapGet ( " /api/chat/stream " , ( Guid ? conversationId , string message , IChatGptClient chatGptClient ) =>
{
async IAsyncEnumerable < string ? > Stream ( )
{
// Requests a streaming response.
var responseStream = chatGptClient . AskStreamAsync ( conversationId . GetValueOrDefault ( ) , message ) ;
// Uses the "AsDeltas" extension method to retrieve partial message deltas only.
await foreach ( var delta in responseStream . AsDeltas ( ) )
{
yield return delta ;
await Task . Delay ( 50 ) ;
}
}
return Stream ( ) ;
} )
. WithOpenApi ( ) ;请注意,如果响应已通过内容过滤系统过滤,则Foreach中的Asdeltas方法将返回NULLS字符串。
该库100%兼容,也与Ellazor WebAssembly应用程序兼容:
查看样本文件夹以获取有关不同实现的更多信息。
Chatgpt支持具有系统角色的消息,以影响助手应如何行事。例如,我们可以说出类似的东西:
ChatGptNet使用SetupAsync方法提供此功能:
var conversationId await = chatGptClient . SetupAsync ( " Answer in rhyme " ) ;如果我们在调用AskAsync时使用相同的对话ID ,则系统消息将与每个请求一起自动发送,以便助手知道如何行事。
注意系统消息不计算消息限制号码。
当达到到期时间(由MessageExpiration属性指定)时,会自动删除对话历史记录。但是,如有必要,有可能立即清除历史记录:
await chatGptClient . DeleteConversationAsync ( conversationId , preserveSetup : false ) ;PreservEsetup参数允许确定Mantain是否还使用setUpasync方法设置的系统消息(默认: false )。
通过函数调用,我们可以描述功能,并让模型智能选择输出包含参数以调用这些函数的JSON对象。这是一种更可靠地将GPT功能与外部工具和API联系起来的新方法。
ChatGptNet通过提供允许指定函数定义的AskAsync方法的过载来完全支持函数调用。如果提供此参数,则该模型将决定何时适当地使用函数。例如:
var functions = new List < ChatGptFunction >
{
new ( )
{
Name = " GetCurrentWeather " ,
Description = " Get the current weather " ,
Parameters = JsonDocument . Parse ( """
{
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "The city and/or the zip code"
},
"format": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "The temperature unit to use. Infer this from the user's location."
}
},
"required": ["location", "format"]
}
""" )
} ,
new ( )
{
Name = " GetWeatherForecast " ,
Description = " Get an N-day weather forecast " ,
Parameters = JsonDocument . Parse ( """
{
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "The city and/or the zip code"
},
"format": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "The temperature unit to use. Infer this from the user's location."
},
"daysNumber": {
"type": "integer",
"description": "The number of days to forecast"
}
},
"required": ["location", "format", "daysNumber"]
}
""" )
}
} ;
var toolParameters = new ChatGptToolParameters
{
FunctionCall = ChatGptToolChoices . Auto , // This is the default if functions are present.
Functions = functions
} ;
var response = await chatGptClient . AskAsync ( " What is the weather like in Taggia? " , toolParameters ) ;我们可以在JSON模式引用之后传递一个任意数量的函数,每个功能都具有名称,描述和JSON模式描述函数参数。在引擎盖下,将函数注入了系统消息中,该函数已对模型进行了训练。这意味着功能符合模型的上下文限制,并将其作为输入令牌计数。
AskAsync方法返回的响应对象提供了一个属性来检查模型是否选择了函数调用:
if ( response . ContainsFunctionCalls ( ) )
{
Console . WriteLine ( " I have identified a function to call: " ) ;
var functionCall = response . GetFunctionCall ( ) ! ;
Console . WriteLine ( functionCall . Name ) ;
Console . WriteLine ( functionCall . Arguments ) ;
}此代码将打印出这样的内容:
I have identified a function to call:
GetCurrentWeather
{
"location": "Taggia",
"format": "celsius"
}
请注意,API实际上不会执行任何函数调用。开发人员可以使用模型输出执行函数调用。
实际执行后,我们需要在ChatGptClient上调用AddToolResponsync方法,以将响应添加到对话历史记录中,就像标准消息一样,以便自动用于聊天完成:
// Calls the remote function API.
var functionResponse = await GetWeatherAsync ( functionCall . Arguments ) ;
await chatGptClient . AddToolResponseAsync ( conversationId , functionCall , functionResponse ) ;诸如GPT-4-Turbo之类的新模型支持一种更通用的功能方法,即工具调用。发送请求时,您可以指定模型可能调用的工具列表。当前,仅支持功能,但是在将来发布的其他类型的工具将提供。
要使用工具调用而不是直接函数调用,您需要在ChatgptToolParameters对象中设置工具选择和工具属性(如前所述,而不是functionCall和function ,如上一个示例):
var toolParameters = new ChatGptToolParameters
{
ToolChoice = ChatGptToolChoices . Auto , // This is the default if functions are present.
Tools = functions . ToTools ( )
} ;Totools扩展方法用于将ChatGptFunction的列表转换为工具列表。
如果使用这种新方法,当然,您仍然需要检查模型是否已选择了一个工具调用,则使用以前显示的相同方法。然后,在函数的实际执行后,您必须调用AddToolResponseansync方法,但是在这种情况下,您需要指定响应所指的工具(不是函数):
var tool = response . GetToolCalls ( ) ! . First ( ) ;
var functionCall = response . GetFunctionCall ( ) ! ;
// Calls the remote function API.
var functionResponse = await GetWeatherAsync ( functionCall . Arguments ) ;
await chatGptClient . AddToolResponseAsync ( conversationId , tool , functionResponse ) ;最后,您需要将原始消息重新发送到聊天完成API,以便该模型可以考虑到功能呼叫响应的对话。查看呼叫示例的函数以完成此工作流的完整实现。
使用Azure OpenAI服务时,我们会自动免费获得内容过滤。有关其工作原理的详细信息,请查看文档。当使用API版本2023-06-01-preview或更高版本时,所有方案都会返回此信息。 ChatGptNet通过在ChatgPtresponse和ChatGptChoice类中提供相应的属性来完全支持此对象模型。
嵌入允许将文本转换为向量空间。例如,这对于比较两个句子的相似性可能很有用。 ChatGptNet通过提供GeteMbedDingAsync方法来完全支持此功能:
var response = await chatGptClient . GenerateEmbeddingAsync ( message ) ;
var embeddings = response . GetEmbedding ( ) ;此代码将为您提供一个浮点数组,其中包含指定消息的所有嵌入式。数组的长度取决于所使用的模型:
| 模型 | 输出维度 |
|---|---|
| 文本插入-ADA-002 | 1536年 |
| text-embedding-3-small | 1536年 |
| 文本插入3大 | 3072 |
诸如Text-embedding-3-small和Text-ebbedding-3-large之类的较新型号使开发人员可以权衡性能和使用嵌入的成本。具体而言,开发人员可以缩短嵌入,而不会嵌入失去其概念代表性的属性。
至于chatgpt,可以通过各种方式完成此设置:
builder . Services . AddChatGpt ( options =>
{
// ...
options . DefaultEmbeddingParameters = new EmbeddingParameters
{
Dimensions = 256
} ;
} ) ; "ChatGPT": {
"DefaultEmbeddingParameters": {
"Dimensions": 256
}
}
然后,如果要更改特定请求的维度,则可以在getembeddingAsync Invocation中指定嵌入式参数参数:
var response = await chatGptClient . GenerateEmbeddingAsync ( request . Message , new EmbeddingParameters
{
Dimensions = 512
} ) ;
var embeddings = response . GetEmbedding ( ) ; // The length of the array is 512如果您需要计算两个嵌入之间的余弦相似性,则可以使用嵌入性。Cosineimerity方法。
完整的技术文档可在此处找到。
该项目不断发展。欢迎捐款。随时提出问题并提取回购请求,我们将尽可能地解决。
警告记住要在开发分支上工作,不要直接使用主分支。创建针对目标的拉请请求。