How to integrate OpenAI API (Text, Codex, DALL-e, ChatGPT coming soon) in your .NET Core app in 15 minutes

OpenAI API overview (from Open AI)

The OpenAI API can be applied to virtually any task that involves understanding or generating natural language or code. We offer a spectrum of models with different levels of power suitable for different tasks, as well as the ability to fine-tune your own custom models. These models can be used for everything from content generation to semantic search and classification.

To makes it a little more understandable, so with OpenAI API you can : generate and edit text, generate/edit/explain code, generate and edit images, train a model, search, classify and comapre text. Checkt the documentation here.

We will use DaVinci GPT-3 model in our app, but you can try other models :

LATEST MODELDESCRIPTIONMAX REQUESTTRAINING DATA
text-davinci-003Most capable GPT-3 model. Can do any task the other models can do, often with higher quality, longer output and better instruction-following. Also supports inserting completions within text.4,000 tokensUp to Jun 2021
text-curie-001Very capable, but faster and lower cost than Davinci.2,048 tokensUp to Oct 2019
text-babbage-001Capable of straightforward tasks, very fast, and lower cost.2,048 tokensUp to Oct 2019
text-ada-001Capable of very simple tasks, usually the fastest model in the GPT-3 series, and lowest cost.2,048 tokensUp to Oct 2019

Important : today (02/17/2023), ChatGPT API is not yet available but OpenAI indicates that it will come soon.

Phase 1 : get your secret API key

  1. Go to the OpenAI website and create a new account
  2. Signup for an OpenAI account
  3. Confirm your email address
  4. Log in to your account and navigate to the ‘View API keys’ dashboard
  5. Click on ‘Create a new secret key’

Store your API key in a secure location. You can test the API right now, but if you intend to use OpenAI API in a real workld case scanerio, check OpenAI documentation for details on usage and pricing.

 Phase 2 : create you .NET Core project to consume OpenAI API

  1. Open Visual Studio 2022
  2. Create a new .NET Core project (in my case a Web API)
  3. Install the ‘OpenAI‘ NuGet package as below :

4. In your program.cs file, just copy the following code (replace the apiKey with yours, generated in phase 1)

using OpenAI_API;
using OpenAI_API.Completions;

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

app.UseHttpsRedirection();

app.MapGet("/givemesecretoftheuniverse", (string prompt) =>
{
    var apiKey = "sk-5ucaz1m00000000000000000000000000000000";
    var answer = string.Empty;

    var openAIAPI = new OpenAIAPI(apiKey);

    var completionRequest = new CompletionRequest
    {
        Prompt = prompt,
        Model = OpenAI_API.Models.Model.DavinciText,
        MaxTokens = 2000
    };

    try
    {
        var result = openAIAPI.Completions.CreateCompletionAsync(completionRequest);

        if (result != null)
        {
            foreach (var item in result.Result.Completions)
            {
                answer += item.Text;
            }
            return Results.Text("Answer of the universe : " + answer);
        }
        else
        {
            return Results.BadRequest("Not found");
        }
    }
    catch (Exception)
    {
        return Results.Problem("OpenAI API is not available or key are incorrect");
    }
});

app.Run();

5. Run your application, and open your Postman app or your favorite web browser to open the following URL : https://<your computer ip / localhost>:<your project port>/givemesecretoftheuniverse?prompt=what is the secret of the universe

6. The flow of the power of OpenAI will flow throught your screen 😁🚀🌚

More possibilities 🚀😁

As said earlier, you can use Codex, Dall-E, etc from OpenAI just by changing the Model = OpenAI_API.Models.Model statement and adapt your code consequently. Today, ChatGPT API is not yet available but OpenAI indicates that it will come soon.

You can also try this .NET library which is pretty cool as well : https://github.com/betalgo/openai

Conclusion

Integrating OpenAI APIs into your application is just as easy as creating a minimal API with .NET Core, so test it, learn it, and make our world a better one. With ChatGPT coming soon, accessible AI is becoming a reality.

[20th anniversary of .NET] Best of C# : null operators

In this series, I’d like to share with you my best experiences with .NET / C# and the best evolutions the .NET team brings us over the time.

Today I’d like to talk about null operators 🙂 C# provides 3 operatrs to make it easier to with with nulls :

  1. Null conditional operator
  2. Null coalescing operator
  3. Null coalescing assignment (new in C# 8)

You can find more details about these operators in this great book => Amazon.fr – C# 10 in a Nutshell: The Definitive Reference – Albahari, Joseph – Livres

Null-conditional operator

The ?. operator is also called the Elvis operator and as originally introduced in C# 6. It allows you to call methods and access members like the . operator except that if the left operand is null, the expression is evaluate to null without throwing the killer exception ‘NullReferenceException‘.

string s = null;
string sUpper = sb?.ToUpper(); // sUpper is null

The only condition is that the final expression/assignment is capable of accepting a null (exit all value type) :

string s = null;
int lengthS = s?.Length; // Won't compile

Then use a Nullable :

string s = null;
int? lengthS = s?.Length; // Ok

Null-coalescing operator

The very useful ?? operator tells to return the value of the left operand if it’s not null, else return the specify value (right operand – example : default value)

string s = null;
string sDefault = s ?? "My default value"; // sDefault is "My default value"

Another good use of this operator is to check an assignment and rise an exception if null (example in ASP.NET controller or anywhere else) :

_companyRepository = companyRepository ?? throw new ArgumentNullException(nameof(companyRepository));

Null-coalescing assignment (C# 8)

The ??= operator assigns a variable only if it’s not null. So it’s a sugar syntax to replace this :

if (s != null) s = "Hello world";

with this :

s ??= "Hello world";

MVP ComCamp 2015 : les ressources

flyer_comcamp2015

L’évènement étant fini, je vous communique les liens vers les présentations de ces deux sessions :

Sitôt fini qu’on me demande déjà de rejouer cette présentation et d’y apporter quelques ajouts (et allégements … je sais je suis bavard !). A suivre donc …

P1010332 (Large)

Utilisation du service WorkflowQueuingService

L’exécution d’un workflow est souvent dépendante des entrées que les hommes (via une console ou une application Windows Form) ou les programmes (ASP.NET, Web Service, Mainframe, etc) feront dans votre workflow, même après plusieurs jours d’attente. Une activité qui attend des entrées d’une entité extérieure se doit de faire deux choses :

  • Notifier qu’elle attend des données (se mettre ‘au repos’)
  • Etre notifié quand les données seront mise à disposition.

Et cela, même si l’instance de son workflow est persistée (dans un fichier texte ou une base de données par exemple). Lorsque les données arrivent, le moteur d’exécution (le runtime) doit pouvoir recharger en mémoire l’instance et être capable de continuer l’exécution du workflow au bon endroit.

Comment faire en sorte de pouvoir spécifier au workflow qu’il est en attente d’une donnée et de traiter la donnée une fois celle-ci mise à la disposition de l’instance du workflow ? Comment assurer une communication ‘propre’ à l’intérieur des couches logicielles et de votre architecture ? Il y a plusieurs méthodes, plus ou moins robuste pour le faire,  une bonne méthode est néanmoins d’utiliser ce qui fait le fondement de WF : les files de Workflow !

De cette façon, à chaque fois que vous avez besoin de pousser une entrée vers le workflow, telle qu’une chaine de cractères ou tout autre objet (dans notre exemple, une transaction financière), vous ajoutez un élément à l’une ou les piles de l’instance du workflow. La pile se chargera à son tour d’appeler les méthodes abonnées aux évènements pour traiter ces entrées comme stimuli de l’activité. Ce principe nous permet d’utiliser le service WorkflowQueuingService qui s’exécute dans le moteur d’exécution de WF et qui va vous permettre d’ajouter et de nommer des files dans une instance de workflow.

L’autre avantage de cette méthode est le fait de pouvoir ainsi marquer des points d’exécution et des points de persistence dans votre workflow sans que vous ne soyez obligé d’être dans la portée d’une transaction.

Imaginons que nous voulons traiter un workflow simple qui attend le traitement d’une transaction financière par un serveur backend.

Voici la structure d’une transaction attendue comme entrée par le workflow :

public struct Transaction {

public double Amount;

public DateTime Date;

public Guid ID;

public Transaction(Guid id, double amount, DateTime date) {

this.ID = id; this.Amount = amount; this.Date = date;

}

public override string ToString() {

return string.Format(« ID: {0} – Amount: {1} – Date: {2} », ID, Amount, Date);

}

}

Commençons par implémenter une activité possédant une propriété contenant notre transaction  :

public partial class WaitProcessedTransaction : System.Workflow.ComponentModel.Activity

{

private Transaction mTransaction;

public Transaction Transaction

{

get { return mTransaction; }

set { mTransaction = value; }

}

}

Voici les méthodes de création (méthode d’initialisation de l’activité) et de destruction (méthode de ‘désinitialisation’, comprendre que cette méthode est appelée de façon synchrone lorsque de la transition de l’état de l’activité de Executing vers Closed) de la file de Workflow :

protected override void Initialize(IServiceProvider provider) {

WorkflowQueuingService qService =

(WorkflowQueuingService)provider.GetService(typeof(WorkflowQueuingService));

if (!qService.Exists(this.Name))

qService.CreateWorkflowQueue(this.Name, true);

}

protected override void Uninitialize(IServiceProvider provider) {

WorkflowQueuingService qService = (WorkflowQueuingService)provider.GetService(typeof(WorkflowQueuingService));

if (qService.Exists(this.Name))

qService.DeleteWorkflowQueue(this.Name);

}

}

On peut constater que les files sont nommées, ici par le nom de l’activité.

Nous allons redéfinir la méthode d’exécution de l’activité par la méthode suivante :

protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext) {

WorkflowQueuingService qService = executionContext.GetService<WorkflowQueuingService>();

WorkflowQueue queue = qService.GetWorkflowQueue(this.Name);

if (queue.Count > 0){

this.mTransaction = (Transaction)queue.Dequeue();

return ActivityExecutionStatus.Closed;

}

queue.QueueItemAvailable += this.ContinueAt;

return ActivityExecutionStatus.Executing;

}

void ContinueAt(object sender, QueueEventArgs e){

ActivityExecutionContext context = (ActivityExecutionContext)sender;

WorkflowQueuingService qService = context.GetService<WorkflowQueuingService>();

WorkflowQueue queue = qService.GetWorkflowQueue(this.Name);

this.mTransaction = (Transaction)queue.Dequeue();

context.CloseActivity();

}

Dans la méthode d’exécution, nous récupérons le service de file puis nous extrayons l’entrée qui pourrait potentiellement se trouver dans la file. Si la donnée est récupérée de la file, nous spécifions la clôture de l’activité, sinon nous abonnons une méthode à l’évènement qui sera appelée dés qu’une entrée dans la file sera disponible, puis nous laissons le statut de l’activité comme ‘en cours d’exécution’.

Maintenant c’est au tour de l’application de jouer son rôle en fournissant l’entrée dans la file nommée de l’instance du workflow :

// Création d’une instance de workflow et démarrage

WorkflowInstance instance = workflowRuntime.CreateWorkflow(typeof(WorkflowDemo.WorkflowQueueTest));

instance.Start();

// On crée une transaction financière et la met dans la file nommée ‘WaitTransactionProcessed’ (qui est le nom de l’activité ciblée) ce qui aura pour effet de réveiller le workflow et d’exécuter la méthode abonnée à l’évènement ‘QueueItemAvailable’ de la file (méthode ‘ContinueAt’)

Transaction t = new Transaction(Guid.NewGuid(), 1000.0, DateTime.Now);

instance.EnqueueItem(« AttendreTraitementTransaction », t, null, null);

En utilisant un objet d’une instance de workflow, et cela n’importe où dans l’application hôte, vous allez pouvoir marquer des points d’exécution dans votre workflow grâce aux files et communiquer une ou plusieurs entrées à votre activité pour pouvoir continuer son exécution. Cela est vrai même si celui-ci a été persisté pendant son état de repos.

Dans notre cas, nous parlons d’un workflow qui peut attendre un stimulus d’une entité extérieure sur des périodes plutôt longues, il faut donc s’attendre à se que le workflow soit décharger de la mémoire pour être persisté à un endroit physique. Dans notre cas nous allons le faire persister dans une base SQL Server 2005 (classique en quelque sorte) en utilisant le service SqlWorkflowPersistenceService (voici un lien pour mettre en place tout ce qu’il faut : http://msdn2.microsoft.com/en-us/library/aa349366.aspx).

clip_image002

Note : voici également un excellent schéma des états d’un workflow : http://msdn2.microsoft.com/en-us/library/aa663362.hostingwwf03l%28en-us,msdn.10%29.jpg

Voici les quelques lignes qu’il faut rajouter pour configurer le runtime :

string stringConnection = @ »Initial Catalog=WF_Persistence;Data Source=localhost\SQLEXPRESS;User Id=x;Password=x »;

// Création d’une instance du service de persistence Sql Server avec un argument à true pour spécifier de persister une instance de workflow dés que son état passe au repos

SqlWorkflowPersistenceService persistenceService = new SqlWorkflowPersistenceService(

stringConnection, true, TimeSpan.MaxValue, new TimeSpan(0, 1, 0));

workflowRuntime.AddService(persistenceService);

Pour mieux comprendre ce qui se passe on rajoute des handle à différents évènements qui afficheront les changements d’état :

workflowRuntime.WorkflowIdled += new EventHandler<WorkflowEventArgs>(workflowRuntime_WorkflowIdled);

workflowRuntime.WorkflowPersisted += new EventHandler<WorkflowEventArgs>(workflowRuntime_WorkflowPersisted);

workflowRuntime.WorkflowLoaded += new EventHandler<WorkflowEventArgs>(workflowRuntime_WorkflowLoaded);

workflowRuntime.WorkflowCompleted += new EventHandler<WorkflowCompletedEventArgs>(workflowRuntime_WorkflowCompleted);

void workflowRuntime_WorkflowCompleted(object sender, WorkflowCompletedEventArgs e) { Console.WriteLine(« Workflow completed ! »); }

void workflowRuntime_WorkflowLoaded(object sender, WorkflowEventArgs e){ Console.WriteLine(« Workflow loaded ! »); }

void workflowRuntime_WorkflowPersisted(object sender, WorkflowEventArgs e) { Console.WriteLine(« Workflow persisted ! »); }

void workflowRuntime_WorkflowIdled(object sender, WorkflowEventArgs e) { Console.WriteLine(« Workflow idle ! »); }

Imaginons que nous avons un workflow séquentiel contenant notre activité qui attend le traitement d’une transaction lancée en amont d’un workflow, et d’une activité qui affiche un message de confirmation du traitement :

clip_image004

Nous pourrons observer le résultat suivant :

clip_image006

Voici un cas simple d’utilisation du service WorkflowQueuingService. Ce service est à la base de nombreuses activités et reste un moyen élégant et efficace de placer des points d’attente nommés – par le nom de la file – où vos activités peuvent recevoir des données, c’est à dire des endroits logiques dans votre workflow dont l’exécution peut être reprise quand un stimuli spécifique (une données, un évènement, …) arrive, tout en étant capable d’être persisté pendant le temps d’attente.

Le lien MSDN vers le WorkflowQueuingService qui devrait être à la base de beaucoup de vos activités communicant avec l’extérieur (permet de remplacer très élégamment l’activité HandleExternalEvent : http://msdn2.microsoft.com/en-us/library/system.workflow.runtime.workflowqueuingservice.aspx