Une bibliothèque d'intégration ChatGPT pour .NET, prenant en charge le service OpenAI et Azure OpenAI.
La bibliothèque est disponible sur Nuget. Recherchez simplement ChatGptNet dans l' interface graphique du gestionnaire de package ou exécutez la commande suivante dans le .net CLI :
dotnet add package ChatGptNetEnregistrez le service Chatgpt au démarrage de l'application:
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 prend en charge le service OpenAI et Azure OpenAI, il est donc nécessaire de définir les paramètres de configuration corrects en fonction du fournisseur choisi:
Chatgpt peut être utilisé avec différents modèles pour l'achèvement du chat, à la fois sur le service OpenAI et Azure Openai. Avec la propriété defaultModel , vous pouvez spécifier le modèle par défaut qui sera utilisé, sauf si vous transmettez une valeur explicite dans les méthodes Askasync ou AsyStreamasync .
Même si ce n'est pas strictement nécessaire pour la conversation de chat, la bibliothèque prend également en charge l'API d'intégration, à la fois sur OpenAI et Azure Openai. Quant à l'achèvement du chat, les intégres peuvent être effectués avec différents modèles. Avec la propriété defauftMeddingModel , vous pouvez spécifier le modèle par défaut qui sera utilisé, sauf si vous transmettez une valeur explicite dans la méthode GetEmbeddingAsync .
Les modèles actuellement disponibles sont:
Ils ont des noms fixes, disponibles dans le fichier openaichatgptmodels.cs.
Dans le service Azure Openai, vous devez d'abord déployer un modèle avant de pouvoir passer des appels. Lorsque vous déployez un modèle, vous devez lui attribuer un nom, qui doit correspondre au nom que vous utilisez avec ChatGptNet .
Remarque Certains modèles ne sont pas disponibles dans toutes les régions. Vous pouvez vous référer au tableau de résumé du modèle et à la page de disponibilité de la région pour vérifier les disponibilités actuelles.
ChatGpt vise à prendre en charge les scénarios conversationnels: l'utilisateur peut parler à ChatGpt sans spécifier le contexte complet pour chaque interaction. Cependant, l'historique des conversations n'est pas géré par le service Openai ou Azure Openai, c'est donc à nous de conserver l'état actuel. Par défaut, ChatGPTNET gère cette exigence à l'aide d'un MemoryCache qui stocke les messages pour chaque conversation. Le comportement peut être défini en utilisant les propriétés suivantes:
Si nécessaire, il est possible de fournir un cache personnalisé en implémentant l'interface IchatgptCache, puis en appelant la méthode d'extension 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 > ( ) ;Nous pouvons également définir des paramètres ChatGPT pour l'achèvement du chat au démarrage. Consultez la documentation officielle de la liste des paramètres disponibles et de leur signification.
La configuration peut être lu automatiquement à partir de l'iconfiguration, en utilisant par exemple une section ChatGpt dans le fichier appsettings.json :
"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
//}
}
Puis utilisez la surcharge correspondante de la méthode CHEDCHATGPT :
// Adds ChatGPT service using settings from IConfiguration.
builder . Services . AddChatGpt ( builder . Configuration ) ;La méthode AddChatgpt a également une surcharge qui accepte un IServiceProvider comme argument. Il peut être utilisé, par exemple, si nous sommes dans une API Web et que nous devons prendre en charge les scénarios dans lesquels chaque utilisateur a une clé API différente qui peut être récupérée à l'accès à une base de données via l'injection de dépendance:
builder . Services . AddChatGpt ( ( services , options ) =>
{
var accountService = services . GetRequiredService < IAccountService > ( ) ;
// Dynamically gets the API Key from the service.
var apiKey = " ... "
options . UseOpenAI ( apiKyey ) ;
} ) ;Dans des scénarios plus complexes, il est possible de configurer ChatGptNet en utilisant à la fois le code et l'iconfiguration. Cela peut être utile si nous voulons définir un tas de propriétés communes, mais en même temps, nous avons besoin d'une logique de configuration. Par exemple:
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 utilise un httpclient pour appeler l'achèvement du chat et l'intégration des API. Si vous avez besoin de le personnaliser, vous pouvez utiliser la surcharge de la méthode addchatgpt qui accepte une action <ihttpclientBuiler> comme argument. Par exemple, si vous souhaitez ajouter de la résilience au client HTTP (disons une stratégie de réessayer), vous pouvez utiliser 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 ) ;
} ) ;
} )Plus d'informations sur ce sujet sont disponibles sur la documentation officielle.
La bibliothèque peut être utilisée dans n'importe quelle application .NET construite avec .NET 6.0 ou version ultérieure. Par exemple, nous pouvons créer une API minimale de cette manière:
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 ) ;Si nous voulons simplement récupérer le message de réponse, nous pouvons appeler la méthode GetContent :
var content = response . GetContent ( ) ;Remarque Si la réponse a été filtrée par le système de filtrage de contenu, GetContent renverra NULL . Ainsi, vous devez toujours vérifier la
response.IsContentFiltered.
En utilisant la configuration, il est possible de définir des paramètres par défaut pour l'achèvement du chat. Cependant, nous pouvons également spécifier des paramètres pour chaque demande, en utilisant les surcharges Askasync ou AskStreamasync qui acceptent un objet ChatGptParameters:
var response = await chatGptClient . AskAsync ( conversationId , message , new ChatGptParameters
{
MaxTokens = 150 ,
Temperature = 0.7
} ) ;Nous n'avons pas besoin de spécifier tous les paramètres, seulement ceux que nous voulons remplacer. Les autres seront tirés de la configuration par défaut.
Chatgpt est connu pour être non déterministe. Cela signifie que la même entrée peut produire des sorties différentes. Pour essayer de contrôler ce comportement, nous pouvons utiliser les paramètres de température et de TOPP . Par exemple, le réglage de la température sur des valeurs proches de 0 rend le modèle plus déterministe, tandis que le réglage sur des valeurs proches de 1 rend le modèle plus créatif. Cependant, cela ne suffit pas toujours pour obtenir la même sortie pour la même entrée. Pour résoudre ce problème, OpenAI a introduit le paramètre de semence . S'il est spécifié, le modèle doit échantillonner de manière déterministe, de sorte que les demandes répétées avec la même graine et les mêmes paramètres doivent renvoyer le même résultat. Néanmoins, le déterminisme n'est pas garanti ni dans ce cas, et vous devez vous référer au paramètre de réponse SystemFingerPrint pour surveiller les changements dans le backend. Les changements dans ces valeurs signifient que la configuration du backend a changé, ce qui pourrait avoir un impact sur le déterminisme.
Comme toujours, la propriété de semence peut être spécifiée dans la configuration par défaut ou dans les surcharges Askasync ou Askstreamasync qui acceptent des chatptParameters.
Remarque Seed et SystemFingerPrint ne sont soutenus que par les modèles les plus récents, tels que GPT-4-1106-Preview .
Si vous souhaitez abandonner la réponse au format JSON, vous pouvez utiliser le paramètre ResponseFormat :
var response = await chatGptClient . AskAsync ( conversationId , message , new ChatGptParameters
{
ResponseFormat = ChatGptResponseFormat . Json ,
} ) ;De cette façon, la réponse sera toujours un JSON valide. Remarque qui doit également demander au modèle de produire JSON via un système ou un message utilisateur. Si vous ne le faites pas, le modèle renvoie une erreur.
Comme toujours, la propriété ResponseFormat peut être spécifiée dans la configuration par défaut ou dans les surcharges Askasync ou Askstreamasync qui acceptent des chatgptParameters.
Remarque ResponseFormat n'est pris en charge que par les modèles les plus récents, tels que GPT-4-1106-Preview .
Les méthodes AskAsync et Askstreamasync (voir ci-dessous) fournissent des surcharges qui nécessitent un paramètre ConversationId . Si nous passons une valeur vide, un aléatoire est généré et renvoyé. Nous pouvons transmettre cette valeur dans les invocations ultérieures de Askasync ou Askstreamasync , afin que la bibliothèque récupère automatiquement les messages précédents de la conversation actuelle (selon les paramètres de Messagelimit et de MessageExpiration ) et les envoyer à l'API de complétion du chat.
Il s'agit du comportement par défaut pour toutes les interactions de chat. Si vous souhaitez expulser une interaction particulière de l'historique de la conversation, vous pouvez définir l'argument AddToConversationHistory sur False :
var response = await chatGptClient . AskAsync ( conversationId , message , addToConversationHistory : false ) ;De cette façon, le message sera envoyé à l'API de complétion de chat, mais l'informatique et la réponse correspondante de Chatgpt ne seront pas ajoutées à l'historique de la conversation.
D'un autre côté, dans certains scénarios, il pourrait être utile d'ajouter manuellement une interaction de chat (c'est-à-dire une question suivie d'une réponse) à l'histoire de la conversation. Par exemple, nous pouvons vouloir ajouter un message généré par un bot. Dans ce cas, nous pouvons utiliser la méthode AddInterActionAsync :
await chatGptClient . AddInteractionAsync ( conversationId , question : " What is the weather like in Taggia? " ,
answer : " It's Always Sunny in Taggia " ) ;La question sera ajoutée en tant que message de l'utilisateur et la réponse sera ajoutée en tant que message assistant dans l'historique de la conversation. Comme toujours, ces nouveaux messages (en respectant l'option Messagelimit ) seront utilisés dans les invocations ultérieures d' Askasync ou Askstreamasync .
L'API d'achèvement du chat prend en charge le streaming de réponse. Lorsque vous utilisez cette fonctionnalité, le message partiel Deltas sera envoyé, comme dans Chatgpt. Les jetons seront envoyés en tant qu'événements de serveur uniquement des données à mesure qu'ils seront disponibles. ChatGptNet fournit un streaming de réponse à l'aide de la méthode ASSKSTREAMASYNC :
// Requests a streaming response.
var responseStream = chatGptClient . AskStreamAsync ( conversationId , message ) ;
await foreach ( var response in responseStream )
{
Console . Write ( response . GetContent ( ) ) ;
await Task . Delay ( 80 ) ;
}Remarque Si la réponse a été filtrée par le système de filtrage de contenu, la méthode GetContent dans le foreach renverra les chaînes NULL . Ainsi, vous devez toujours vérifier la
response.IsContentFiltered.
Le streaming de réponse fonctionne en renvoyant un IASyncenumable, afin qu'il puisse être utilisé même dans un projet API Web:
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 ( ) ;Remarque Si la réponse a été filtrée par le système de filtrage de contenu, la méthode Asdeltas dans le foreach renverra la chaîne Nulls .
La bibliothèque est également compatible à 100% avec les applications Blazor WebAssembly:
Consultez le dossier des échantillons pour plus d'informations sur les différentes implémentations.
Chatgpt prend en charge les messages avec le rôle système pour influencer la façon dont l'assistant doit se comporter. Par exemple, nous pouvons dire à chatter quelque chose comme ça:
ChatGptNet fournit cette fonction à l'aide de la méthode SetUpasync :
var conversationId await = chatGptClient . SetupAsync ( " Answer in rhyme " ) ;Si nous utilisons le même ConversationId lorsque nous appelons AskAsync , le message système sera automatiquement envoyé avec chaque demande, afin que l'assistant sache comment se comporter.
Remarque Le message système ne compte pas pour le numéro de limite des messages.
L'historique de la conversation est automatiquement supprimé lorsque le temps d'expiration (spécifié par la propriété MessageExpiration ) est atteint. Cependant, si nécessaire, il est possible d'effacer immédiatement l'histoire:
await chatGptClient . DeleteConversationAsync ( conversationId , preserveSetup : false ) ;L'argument PreserveSetup permet de décider si Mantain également le message système qui a été défini avec la méthode SetUpasync (par défaut: false ).
Avec l'appel des fonctions, nous pouvons décrire les fonctions et faire choisir intelligemment le modèle de sortir un objet JSON contenant des arguments pour appeler ces fonctions. Il s'agit d'une nouvelle façon de connecter plus de manière fiable les capacités de GPT avec des outils et des API externes.
ChatGptNet prend en charge l'appel de fonction en fournissant une surcharge de la méthode Askasync qui permet de spécifier les définitions de fonction. Si ce paramètre est fourni, le modèle décidera quand il est approprié pour en utiliser les fonctions. Par exemple:
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 ) ;Nous pouvons passer un nombre arbitraire de fonctions, chacune avec un nom, une description et un schéma JSON décrivant les paramètres de fonction, en suivant les références de schéma JSON. Sous le capot, les fonctions sont injectées dans le message système dans une syntaxe sur laquelle le modèle a été formé. Cela signifie que les fonctions comptent contre la limite de contexte du modèle et sont facturées sous forme de jetons d'entrée.
L'objet de réponse renvoyé par la méthode Askasync fournit une propriété pour vérifier si le modèle a sélectionné un appel de fonction:
if ( response . ContainsFunctionCalls ( ) )
{
Console . WriteLine ( " I have identified a function to call: " ) ;
var functionCall = response . GetFunctionCall ( ) ! ;
Console . WriteLine ( functionCall . Name ) ;
Console . WriteLine ( functionCall . Arguments ) ;
}Ce code imprimera quelque chose comme ceci:
I have identified a function to call:
GetCurrentWeather
{
"location": "Taggia",
"format": "celsius"
}
Notez que l'API n'exécutera pas les appels de fonction. Il appartient aux développeurs d'exécuter des appels de fonction à l'aide de sorties de modèle.
Après l'exécution réelle, nous devons appeler la méthode AddtoolResponSeSync sur le chatpptClient pour ajouter la réponse à l'historique de conversation, tout comme un message standard, afin qu'il soit automatiquement utilisé pour l'achèvement du chat:
// Calls the remote function API.
var functionResponse = await GetWeatherAsync ( functionCall . Arguments ) ;
await chatGptClient . AddToolResponseAsync ( conversationId , functionCall , functionResponse ) ;De nouveaux modèles comme GPT-4-Turbo prennent en charge une approche plus générale des fonctions, l' appel d'outils . Lorsque vous envoyez une demande, vous pouvez spécifier une liste d'outils que le modèle peut appeler. Actuellement, seules les fonctions sont prises en charge, mais à la version future, d'autres types d'outils seront disponibles.
Pour utiliser l'appel d'outils au lieu de l'appel de la fonction directe, vous devez définir les propriétés de la coche et des outils dans l'objet ChatGptToolParameters (au lieu de FonctionCall et de la fonction , comme dans l'exemple précédent):
var toolParameters = new ChatGptToolParameters
{
ToolChoice = ChatGptToolChoices . Auto , // This is the default if functions are present.
Tools = functions . ToTools ( )
} ;La méthode d'extension Totools est utilisée pour convertir une liste de chatpptfunction en une liste d'outils.
Si vous utilisez cette nouvelle approche, vous devez bien sûr vérifier si le modèle a sélectionné un appel d'outil, en utilisant la même approche affichée précédemment. Ensuite, après l'exécution réelle de la fonction, vous devez appeler la méthode addtoolResponSeSync , mais dans ce cas, vous devez spécifier l'outil (pas la fonction) à laquelle la réponse se réfère:
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 ) ;Enfin, vous devez renvoyer le message d'origine à l'API de complétion du chat, afin que le modèle puisse continuer la conversation en tenant compte de la réponse de l'appel de fonction. Consultez l'échantillon d'appel de fonction pour une implémentation complète de ce workflow.
Lorsque vous utilisez un service Azure OpenAI, nous obtenons automatiquement un filtrage de contenu gratuitement. Pour plus de détails sur le fonctionnement, consultez la documentation. Ces informations sont renvoyées pour tous les scénarios lors de l'utilisation de l'API version 2023-06-01-preview ou version ultérieure. ChatGptNet prend en charge entièrement ce modèle d'objet en fournissant les propriétés correspondantes dans les classes ChatGPTResponse et ChatGptChoice.
Les intégres permet de transformer le texte en un espace vectoriel. Cela peut être utile pour comparer la similitude de deux phrases, par exemple. ChatGptNet prend en charge cette fonctionnalité en fournissant la méthode GetEmbeddingasync :
var response = await chatGptClient . GenerateEmbeddingAsync ( message ) ;
var embeddings = response . GetEmbedding ( ) ;Ce code vous donnera un tableau flottant contenant tous les intégres pour le message spécifié. La longueur du tableau dépend du modèle utilisé:
| Modèle | Dimension de sortie |
|---|---|
| Texte-embelli-ADA-002 | 1536 |
| Texte-incliné-3-Small | 1536 |
| texton | 3072 |
Des modèles plus récents comme le texte-emballé-3-Small et le Text-Embedding-3-Garmage permet aux développeurs de compromettre les performances et le coût de l'utilisation des intérêts. Plus précisément, les développeurs peuvent raccourcir les intérêts sans que l'incorporation de perdre ses propriétés de représentation du concept.
Quant à Chatgpt, ces paramètres peuvent être effectués de différentes manières:
builder . Services . AddChatGpt ( options =>
{
// ...
options . DefaultEmbeddingParameters = new EmbeddingParameters
{
Dimensions = 256
} ;
} ) ; "ChatGPT": {
"DefaultEmbeddingParameters": {
"Dimensions": 256
}
}
Ensuite, si vous souhaitez modifier la dimension d'une demande particulière, vous pouvez spécifier l'argument EmbeddingParameters dans l'invocation GetEmbeddingAsync :
var response = await chatGptClient . GenerateEmbeddingAsync ( request . Message , new EmbeddingParameters
{
Dimensions = 512
} ) ;
var embeddings = response . GetEmbedding ( ) ; // The length of the array is 512Si vous devez calculer la similitude du cosinus entre deux intégres, vous pouvez utiliser la méthode EmbeddingUtility.Cosinesimilarity .
La documentation technique complète est disponible ici.
Le projet évolue constamment. Les contributions sont les bienvenues. N'hésitez pas à déposer des problèmes et à réaliser des demandes sur le dépôt et nous les résoudrons comme nous le pouvons.
AVERTISSEMENT N'oubliez pas de travailler sur la branche de développement , n'utilisez pas directement la branche principale . Créer des demandes de traction ciblant développer .