Una biblioteca de integración de ChatGPT para .NET, que admite el servicio Operai y Azure OpenAI.
La biblioteca está disponible en Nuget. Simplemente busque chatgptnet en la GUI del administrador de paquetes o ejecute el siguiente comando en el CLI .NET :
dotnet add package ChatGptNetRegistre el servicio CHATGPT al inicio de la aplicación:
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 admite el servicio OpenAI y Azure OpenAI, por lo que es necesario establecer la configuración de configuración correcta en función del proveedor elegido:
ChatGPT se puede usar con diferentes modelos para completar el chat, tanto en el servicio OpenAI y Azure OpenAI. Con la propiedad predeterminada de Model , puede especificar el modelo predeterminado que se utilizará, a menos que pase un valor explícito en los métodos askAsync o asystreamasync .
Incluso si no es estrictamente necesario para la conversación de chat, la biblioteca también admite la API de incrustación, tanto en Openai como en Azure OpenAi. En cuanto a la finalización del chat, se pueden hacer incrustaciones con diferentes modelos. Con la propiedad de Model de Embeding de CoFtaltEd , puede especificar el modelo predeterminado que se utilizará, a menos que pase un valor explícito en el método GetEmbeddingAsync .
Los modelos disponibles actualmente son:
Tienen nombres fijos, disponibles en el archivo OpenAICHATPPTMODELS.CS.
En el servicio Azure OpenAI, primero debe implementar un modelo antes de poder hacer llamadas. Cuando implementa un modelo, debe asignarle un nombre, que debe coincidir con el nombre que usa con chatgptnet .
Tenga en cuenta que algunos modelos no están disponibles en todas las regiones. Puede consultar la tabla de resumen del modelo y la página de disponibilidad de región para verificar las disponibilidades actuales.
ChatGPT tiene como objetivo admitir escenarios de conversación: el usuario puede hablar con ChatGPT sin especificar el contexto completo para cada interacción. Sin embargo, el historial de conversación no es administrado por el servicio Operai o Azure OpenAI, por lo que depende de nosotros retener el estado actual. De forma predeterminada, ChatGPTNet maneja este requisito utilizando un MemoryCache que almacena mensajes para cada conversación. El comportamiento se puede establecer utilizando las siguientes propiedades:
Si es necesario, es posible proporcionar un caché personalizado mediante la implementación de la interfaz iChatpptcache y luego llamando al método de extensión 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 > ( ) ;También podemos establecer los parámetros de ChatGPT para la finalización del chat al inicio. Consulte la documentación oficial de la lista de parámetros disponibles y su significado.
La configuración se puede leer automáticamente desde iconfiguration, utilizando, por ejemplo, una sección de chatgpt en el archivo 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
//}
}
Y luego use la sobrecarga correspondiente del método Che AddChatgpt :
// Adds ChatGPT service using settings from IConfiguration.
builder . Services . AddChatGpt ( builder . Configuration ) ;El método AddChatGPT también tiene una sobrecarga que acepta un IServiceProvider como argumento. Se puede usar, por ejemplo, si estamos en una API web y necesitamos admitir escenarios en los que cada usuario tiene una clave API diferente que se puede recuperar accediendo a una base de datos a través de la inyección de dependencia:
builder . Services . AddChatGpt ( ( services , options ) =>
{
var accountService = services . GetRequiredService < IAccountService > ( ) ;
// Dynamically gets the API Key from the service.
var apiKey = " ... "
options . UseOpenAI ( apiKyey ) ;
} ) ;En escenarios más complejos, es posible configurar ChatGPTNet utilizando el código y el Iconfiguration. Esto puede ser útil si queremos establecer un montón de propiedades comunes, pero al mismo tiempo necesitamos alguna lógica de configuración. Por ejemplo:
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 ) ;
} ) ;ChatPptnet utiliza un HTTPClient para llamar a la finalización de chat e incrustar API. Si necesita personalizarlo, puede usar la sobrecarga del método AddChatgpt que acepta una acción <IHTTPClientBuiler> como argumento. Por ejemplo, si desea agregar resistencia al cliente HTTP (digamos una política de reintento), puede usar 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 ) ;
} ) ;
} )Más información sobre este tema está disponible en la documentación oficial.
La biblioteca se puede usar en cualquier aplicación .NET construida con .NET 6.0 o posterior. Por ejemplo, podemos crear una API mínima de esta manera:
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 solo queremos recuperar el mensaje de respuesta, podemos llamar al método GetContent :
var content = response . GetContent ( ) ;Nota Si la respuesta ha sido filtrada por el sistema de filtrado de contenido, GetContent devolverá NULL . Por lo tanto, siempre debe verificar la propiedad
response.IsContentFilteredantes de intentar acceder al contenido real.
Usando la configuración, es posible establecer los parámetros predeterminados para la finalización del chat. Sin embargo, también podemos especificar parámetros para cada solicitud, utilizando las sobrecargas de AskAsync o askstreamasync que acepta un objeto ChatGpTParameters:
var response = await chatGptClient . AskAsync ( conversationId , message , new ChatGptParameters
{
MaxTokens = 150 ,
Temperature = 0.7
} ) ;No necesitamos especificar todos los parámetros, solo los que queremos anular. Los otros se tomarán de la configuración predeterminada.
Se sabe que ChatGPT no es determinista. Esto significa que la misma entrada puede producir diferentes salidas. Para tratar de controlar este comportamiento, podemos usar los parámetros de temperatura y TOPP . Por ejemplo, establecer la temperatura en valores cercanos a 0 hace que el modelo sea más determinista, mientras que establecerla en valores cercanos a 1 hace que el modelo sea más creativo. Sin embargo, esto no siempre es suficiente para obtener la misma salida para la misma entrada. Para abordar este problema, OpenAI introdujo el parámetro de semilla . Si se especifica, el modelo debe probar determinista, de modo que las solicitudes repetidas con la misma semilla y parámetros deberían devolver el mismo resultado. Sin embargo, el determinismo no está garantizado ni en este caso, y debe consultar el parámetro de respuesta SystemFingerprint para monitorear los cambios en el backend. Los cambios en estos valores significan que la configuración de backend ha cambiado, y esto podría afectar el determinismo.
Como siempre, la propiedad de la semilla se puede especificar en la configuración predeterminada o en las sobrecargas de AskAsync o AskStreamAsync que acepta un chatgptParameters.
Nota Seed y Systemfingerprint solo son compatibles con los modelos más recientes, como GPT-4-1106 previa .
Si desea forjar la respuesta en formato JSON, puede usar el parámetro ResponseFormat :
var response = await chatGptClient . AskAsync ( conversationId , message , new ChatGptParameters
{
ResponseFormat = ChatGptResponseFormat . Json ,
} ) ;De esta manera, la respuesta siempre será un JSON válido. Tenga en cuenta que también debe instruir al modelo que produzca JSON a través de un sistema o mensaje de usuario. Si no hace esto, el modelo devolverá un error.
Como siempre, la propiedad ResponseFormat se puede especificar en la configuración predeterminada o en las sobrecargas de AskAsync o AskStreamAsync que acepta un chatgptParameters.
Nota ResponseFormat solo es compatible con los modelos más recientes, como la previa vista GPT-4-1106 .
Los métodos AskAsync y AskStreamAsync (ver más abajo) proporcionan sobrecargas que requieren un parámetro de conversación . Si pasamos un valor vacío, se genera y se devuelve uno al azar. Podemos aprobar este valor en las invocaciones posteriores de AskAsync o Askstreamasync , de modo que la biblioteca recupere automáticamente los mensajes anteriores de la conversación actual (de acuerdo con la configuración de Messagelimit y MessageExpiration ) y los envíe a API de finalización de chat.
Este es el comportamiento predeterminado para todas las interacciones de chat. Si desea expulsar una interacción particular del historial de conversación, puede establecer el argumento addoconversationhistory en falso :
var response = await chatGptClient . AskAsync ( conversationId , message , addToConversationHistory : false ) ;De esta manera, el mensaje se enviará a la API de finalización de chat, pero la respuesta correspondiente de ChatGPT no se agregará al historial de conversación.
Por otro lado, en algunos escenarios, podría ser útil agregar manualmente una interacción de chat (es decir, una pregunta seguida de una respuesta) a la historia de la conversación. Por ejemplo, es posible que deseemos agregar un mensaje generado por un bot. En este caso, podemos usar el método AddInteractionAsync :
await chatGptClient . AddInteractionAsync ( conversationId , question : " What is the weather like in Taggia? " ,
answer : " It's Always Sunny in Taggia " ) ;La pregunta se agregará como mensaje de usuario y la respuesta se agregará como mensaje de asistente en el historial de conversación. Como siempre, estos nuevos mensajes (respetando la opción Messagelimit ) se utilizarán en invocaciones posteriores de AskAsync o Askstreamasync .
La API de finalización de chat admite la transmisión de respuesta. Al usar esta función, se enviarán Deltas de mensaje parcial, como en ChatGPT. Los tokens se enviarán como eventos de Servidor de solo datos a medida que estén disponibles. Chatgptnet proporciona transmisión de respuesta utilizando el método 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 ) ;
}Nota Si la respuesta ha sido filtrada por el sistema de filtrado de contenido, el método GetContent en el foreach devolverá las cadenas nulas . Por lo tanto, siempre debe verificar la propiedad
response.IsContentFilteredantes de intentar acceder al contenido real.
La transmisión de respuesta funciona devolviendo un iAsyncenumerable, por lo que se puede usar incluso en un proyecto de 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 ( ) ;Tenga en cuenta que si la respuesta ha sido filtrada por el sistema de filtrado de contenido, el método asdeltas en el foreach devolverá la cadena NULLS .
La biblioteca es 100% compatible también con aplicaciones Blazor WebAssembly:
Consulte la carpeta de muestras para obtener más información sobre las diferentes implementaciones.
ChatGPT admite mensajes con el rol del sistema para influir en cómo debe comportarse el asistente. Por ejemplo, podemos decirle que chatea algo así:
Chatgptnet proporciona esta función utilizando el método Setupasync :
var conversationId await = chatGptClient . SetupAsync ( " Answer in rhyme " ) ;Si usamos la misma conversación al llamar a AskAsync , el mensaje del sistema se enviará automáticamente junto con cada solicitud, para que el asistente sepa cómo comportarse.
Nota El mensaje del sistema no cuenta para el número de límite de mensajes.
El historial de conversación se elimina automáticamente cuando se alcanza el tiempo de vencimiento (especificado por la propiedad de MessageExpiration ). Sin embargo, si es necesario, es posible despejar inmediatamente el historial:
await chatGptClient . DeleteConversationAsync ( conversationId , preserveSetup : false ) ;El argumento PreservesEsTUP permite decidir si Mantain también el mensaje del sistema que se ha establecido con el método SetuPASync (predeterminado: FALSE ).
Con las llamadas de funciones, podemos describir funciones y hacer que el modelo elija de manera inteligente generar un objeto JSON que contenga argumentos para llamar a esas funciones. Esta es una nueva forma de conectar las capacidades de GPT de manera más confiable con herramientas y API externas.
CHATGPTNet admite plenamente la llamada de función proporcionando una sobrecarga del método askAsync que permite especificar definiciones de funciones. Si se suministra este parámetro, entonces el modelo decidirá cuándo es apropiado usar una de las funciones. Por ejemplo:
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 ) ;Podemos pasar un número arbitrario de funciones, cada una con un nombre, una descripción y un esquema JSON que describe los parámetros de la función, siguiendo las referencias del esquema JSON. Debajo del capó, las funciones se inyectan en el mensaje del sistema en una sintaxis, el modelo ha sido entrenado. Esto significa que las funciones cuentan con el límite de contexto del modelo y se anuncian como tokens de entrada.
El objeto de respuesta devuelto por el método askAsync proporciona una propiedad para verificar si el modelo ha seleccionado una llamada de función:
if ( response . ContainsFunctionCalls ( ) )
{
Console . WriteLine ( " I have identified a function to call: " ) ;
var functionCall = response . GetFunctionCall ( ) ! ;
Console . WriteLine ( functionCall . Name ) ;
Console . WriteLine ( functionCall . Arguments ) ;
}Este código imprimirá algo como esto:
I have identified a function to call:
GetCurrentWeather
{
"location": "Taggia",
"format": "celsius"
}
Tenga en cuenta que la API no ejecutará ninguna llamada de función. Depende de los desarrolladores ejecutar llamadas de función utilizando salidas de modelo.
Después de la ejecución real, necesitamos llamar al método addToolResponseSync en el chatgptClient para agregar la respuesta al historial de conversación, al igual que un mensaje estándar, para que se use automáticamente para completar el chat:
// Calls the remote function API.
var functionResponse = await GetWeatherAsync ( functionCall . Arguments ) ;
await chatGptClient . AddToolResponseAsync ( conversationId , functionCall , functionResponse ) ;Los modelos más nuevos como GPT-4-Turbo admiten un enfoque más general de las funciones, la llamada de herramientas . Cuando envía una solicitud, puede especificar una lista de herramientas que el modelo puede llamar. Actualmente, solo las funciones son compatibles, pero en el futuro se lanzan otros tipos de herramientas disponibles.
Para usar llamadas de herramientas en lugar de llamadas de función directa, debe establecer las propiedades de herramientas y herramientas en el objeto chatgpttoolparameters (en lugar de functionCall y function , como en el ejemplo anterior):
var toolParameters = new ChatGptToolParameters
{
ToolChoice = ChatGptToolChoices . Auto , // This is the default if functions are present.
Tools = functions . ToTools ( )
} ;El método de extensión Totools se utiliza para convertir una lista de chatgptFunction a una lista de herramientas.
Si usa este nuevo enfoque, por supuesto, aún necesita verificar si el modelo ha seleccionado una llamada de herramienta, utilizando el mismo enfoque que se muestra antes. Luego, después de la ejecución real de la función, debe llamar al método addToolResponseSync , pero en este caso debe especificar la herramienta (no la función) a la que se refiere la respuesta:
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 ) ;Finalmente, debe reenviar el mensaje original a la API de finalización de chat, para que el modelo pueda continuar con la conversación teniendo en cuenta la respuesta de llamadas de función. Consulte la muestra de llamadas de función para una implementación completa de este flujo de trabajo.
Al usar el servicio Azure OpenAI, automáticamente obtenemos el filtrado de contenido de forma gratuita. Para obtener detalles sobre cómo funciona, consulte la documentación. Esta información se devuelve para todos los escenarios cuando se usa API versión 2023-06-01-preview o posterior. CHATGPTNET admite completamente este modelo de objetos proporcionando las propiedades correspondientes en las clases de chatGpTResponse y chatgptchoice.
Los incrustaciones permiten transformar el texto en un espacio vectorial. Esto puede ser útil para comparar la similitud de dos oraciones, por ejemplo. CHATGPTNET admite completamente esta función proporcionando el método GetEmbeddingAsync :
var response = await chatGptClient . GenerateEmbeddingAsync ( message ) ;
var embeddings = response . GetEmbedding ( ) ;Este código le dará una matriz flotante que contiene todos los incrustaciones para el mensaje especificado. La longitud de la matriz depende del modelo utilizado:
| Modelo | Dimensión de salida |
|---|---|
| Texto incrustado-ADA-002 | 1536 |
| Texto incrustado-3-Small | 1536 |
| texto incrustado-3-larga | 3072 |
Los modelos más nuevos como Text-INMBED-3-SEMA y Text-Embedding-3-Large permiten a los desarrolladores el rendimiento de la compensación y el costo del uso de incrustaciones. Específicamente, los desarrolladores pueden acortar las incrustaciones sin que la incrustación pierda sus propiedades de representación conceptual.
En cuanto a ChatGPT, esta configuración se puede hacer de varias maneras:
builder . Services . AddChatGpt ( options =>
{
// ...
options . DefaultEmbeddingParameters = new EmbeddingParameters
{
Dimensions = 256
} ;
} ) ; "ChatGPT": {
"DefaultEmbeddingParameters": {
"Dimensions": 256
}
}
Luego, si desea cambiar la dimensión para una solicitud en particular, puede especificar el argumento de InchingDingParameters en la invocación GetEmbeddingAsync :
var response = await chatGptClient . GenerateEmbeddingAsync ( request . Message , new EmbeddingParameters
{
Dimensions = 512
} ) ;
var embeddings = response . GetEmbedding ( ) ; // The length of the array is 512Si necesita calcular la similitud cosena entre dos incrustaciones, puede usar el método de incrustación .
La documentación técnica completa está disponible aquí.
El proyecto está en constante evolución. Las contribuciones son bienvenidas. No dude en presentar problemas y extraer solicitudes en el repositorio y los abordaremos lo que podamos.
Advertencia Recuerde trabajar en la rama de desarrollo , no use la rama maestra directamente. Crear solicitudes de solicitudes de extracción para desarrollar .