{
 "cells": [
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Using ChatGPT with the Semantic Kernel featuring DALL-E 3\n",
    "\n",
    "This notebook shows how to make use of the new ChatCompletion API from OpenAI, popularized by ChatGPT. This API brings a new ChatML schema which is different from the TextCompletion API. While the text completion API expects input a prompt and returns a simple string, the chat completion API expects in input a Chat history and returns a new message:\n",
    "\n",
    "```\n",
    "messages=[\n",
    "    { \"role\": \"system\",    \"content\": \"You are a helpful assistant.\"},\n",
    "    { \"role\": \"user\",      \"content\": \"Who won the world series in 2020?\"},\n",
    "    { \"role\": \"assistant\", \"content\": \"The Los Angeles Dodgers won the World Series in 2020.\"},\n",
    "    { \"role\": \"user\",      \"content\": \"Where was it played?\"}\n",
    "]\n",
    "```\n",
    "\n",
    "Note that there are three message types:\n",
    "\n",
    "1. A System message is used to give instructions to the chat model, e.g. setting the context and the kind of conversation your app is offering.\n",
    "2. User messages store the data received from the user of your app.\n",
    "3. Assistant messages store the replies generated by the LLM model. \n",
    "\n",
    "Your app is responsible for adding information to the chat history and maintaining this object. The Chat Completion API is stateless, and returns only new messages, that your app can use, e.g. to execute commands, generate images, or simply continue the conversation."
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "When deciding between which one to use, know that ChatGPT models (i.e. gpt-3.5-turbo) are optimized for chat applications and have been fine-tuned for instruction-following and dialogue. As such, for creating semantic plugins with the Semantic Kernel, users may still find the TextCompletion model better suited for certain use cases.\n",
    "\n",
    "The code below shows how to setup SK with ChatGPT, how to manage the Chat history object, and to make things a little more interesting asks ChatGPT to reply with image descriptions instead so we can have a dialog using images, leveraging DALL-E 3 integration."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "dotnet_interactive": {
     "language": "csharp"
    },
    "polyglot_notebook": {
     "kernelName": "csharp"
    },
    "tags": [],
    "vscode": {
     "languageId": "polyglot-notebook"
    }
   },
   "outputs": [],
   "source": [
    "// Usual setup: importing Semantic Kernel SDK and SkiaSharp, used to display images inline.\n",
    "\n",
    "#r \"nuget: Microsoft.SemanticKernel, 1.0.1\"\n",
    "#r \"nuget: SkiaSharp, 2.88.3\"\n",
    "\n",
    "#!import config/Settings.cs\n",
    "#!import config/Utils.cs\n",
    "#!import config/SkiaUtils.cs\n",
    "\n",
    "using Microsoft.SemanticKernel;\n",
    "using Microsoft.SemanticKernel.TextToImage;\n",
    "using Microsoft.SemanticKernel.ChatCompletion;\n",
    "using Microsoft.SemanticKernel.Connectors.OpenAI;"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The notebook uses:\n",
    "\n",
    "* **OpenAI ChatGPT** to chat with the user\n",
    "* **OpenAI Dall-E 3** to transform messages into images"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "dotnet_interactive": {
     "language": "csharp"
    },
    "polyglot_notebook": {
     "kernelName": "csharp"
    },
    "vscode": {
     "languageId": "polyglot-notebook"
    }
   },
   "outputs": [],
   "source": [
    "using Kernel = Microsoft.SemanticKernel.Kernel;\n",
    "\n",
    "#pragma warning disable SKEXP0002, SKEXP0012\n",
    "\n",
    "// Load OpenAI credentials from config/settings.json\n",
    "var (useAzureOpenAI, model, azureEndpoint, apiKey, orgId) = Settings.LoadFromFile();\n",
    "\n",
    "// Configure the two AI features: OpenAI Chat and DALL-E 3 for image generation\n",
    "var builder = Kernel.CreateBuilder();\n",
    "\n",
    "if(useAzureOpenAI)\n",
    "{\n",
    "    builder.AddAzureOpenAIChatCompletion(\"gpt-35-turbo\", azureEndpoint, apiKey);\n",
    "    builder.AddAzureOpenAITextToImage(\"dall-e-3\", azureEndpoint, apiKey);\n",
    "}\n",
    "else\n",
    "{\n",
    "    builder.AddOpenAIChatCompletion(\"gpt-3.5-turbo\", apiKey, orgId);\n",
    "    builder.AddOpenAITextToImage(apiKey, orgId);\n",
    "}\n",
    "\n",
    "var kernel = builder.Build();\n",
    "\n",
    "// Get AI service instance used to generate images\n",
    "var dallE = kernel.GetRequiredService<ITextToImageService>();\n",
    "\n",
    "// Get AI service instance used to manage the user chat\n",
    "var chatGPT = kernel.GetRequiredService<IChatCompletionService>();"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Chat configuration\n",
    "\n",
    "Before starting the chat, we create a new chat object with some instructions, which are included in the chat history. \n",
    "\n",
    "The instructions tell OpenAI what kind of chat we want to have, in this case we ask to reply with \"image descriptions\", so that we can chain ChatGPT with DALL-E 3."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "dotnet_interactive": {
     "language": "csharp"
    },
    "polyglot_notebook": {
     "kernelName": "csharp"
    },
    "tags": [],
    "vscode": {
     "languageId": "polyglot-notebook"
    }
   },
   "outputs": [],
   "source": [
    "var systemMessage = \"You're chatting with a user. Instead of replying directly to the user\"\n",
    "                  + \" provide a description of a cartoonish image that expresses what you want to say.\"\n",
    "                  + \" The user won't see your message, they will see only the image.\"\n",
    "                  + \" Describe the image with details in one sentence.\";\n",
    "\n",
    "var chat = new ChatHistory(systemMessage);"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Let's chat\n",
    "\n",
    "Run the following code to start the chat. The chat consists of a loop with these main steps:\n",
    "\n",
    "1. Ask the user (you) for a message. The user enters a message. Add the user message into the Chat History object.\n",
    "2. Send the chat object to AI asking to generate a response. Add the bot message into the Chat History object.\n",
    "3. Show the answer to the user. In our case before showing the answer, generate an image and show that to the user too.\n",
    "\n",
    "*Note: to stop the chat in VS Code press ESC on the kyboard or the \"stop\" button on the left side.*"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "dotnet_interactive": {
     "language": "csharp"
    },
    "polyglot_notebook": {
     "kernelName": "csharp"
    },
    "vscode": {
     "languageId": "polyglot-notebook"
    }
   },
   "outputs": [],
   "source": [
    "#pragma warning disable SKEXP0002\n",
    "\n",
    "while (true)\n",
    "{\n",
    "    // 1. Ask the user for a message. The user enters a message.  Add the user message into the Chat History object.\n",
    "    var userMessage = await InteractiveKernel.GetInputAsync(\"Your message\");\n",
    "    Console.WriteLine($\"User: {userMessage}\");\n",
    "    chat.AddUserMessage(userMessage);\n",
    "\n",
    "    // 2. Send the chat object to AI asking to generate a response. Add the bot message into the Chat History object.\n",
    "    var assistantReply = await chatGPT.GetChatMessageContentAsync(chat, new OpenAIPromptExecutionSettings());\n",
    "    chat.AddAssistantMessage(assistantReply.Content);\n",
    "\n",
    "    // 3. Show the reply as an image\n",
    "    Console.WriteLine($\"\\nBot:\");\n",
    "    var imageUrl = await dallE.GenerateImageAsync(assistantReply.Content, 1024, 1024);\n",
    "    await SkiaUtils.ShowImage(imageUrl, 1024, 1024);\n",
    "    Console.WriteLine($\"[{assistantReply}]\\n\");\n",
    "}"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": ".NET (C#)",
   "language": "C#",
   "name": ".net-csharp"
  },
  "language_info": {
   "file_extension": ".cs",
   "mimetype": "text/x-csharp",
   "name": "C#",
   "pygments_lexer": "csharp",
   "version": "11.0"
  },
  "polyglot_notebook": {
   "kernelInfo": {
    "defaultKernelName": "csharp",
    "items": [
     {
      "aliases": [],
      "name": "csharp"
     }
    ]
   }
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
