OpenAI und PDF

Rambo sagte einmal “Das ist eine lange Geschichte” und war damit eine kurze Zusammenfassung der ganzen Rambo Filme.

Mit Open AI soll das nun möglich sein.
Also los aus nostalgischen Gründen werde ich ein Ajax mit einem Webservice machen – quisi quasi fast ein Micoservice 🙂
Also machen wir mal das Upload.

using Google.Protobuf.WellKnownTypes;
using Org.BouncyCastle.Asn1.Ocsp;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.ServiceModel.Web;
using System.Web;
using System.Web.Script.Services;
using System.Web.Services;
using System.Web.Services.Description;
using System.Web.UI.WebControls;

namespace GPT3PDF
{
    /// <summary>
    /// Zusammenfassungsbeschreibung für AnaylseService
    /// </summary>
    [WebService(Namespace = "http://tempuri.org/")]
    [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
    [System.ComponentModel.ToolboxItem(false)]
    // Wenn der Aufruf dieses Webdiensts aus einem Skript zulässig sein soll, heben Sie mithilfe von ASP.NET AJAX die Kommentarmarkierung für die folgende Zeile auf. 
    [System.Web.Script.Services.ScriptService]
    public class AnaylseService : System.Web.Services.WebService
    {

        [WebMethod]
        public string HelloWorld()
        {
            return "Hello World";
        }

        [WebMethod]
        [ScriptMethod(ResponseFormat = ResponseFormat.Json)]
        [WebInvoke(Method = "POST", ResponseFormat = WebMessageFormat.Json)]
        public PDFResponse Upload()
        {

            HttpFileCollection Files = HttpContext.Current.Request.Files;


            HttpPostedFile File = Files[0];
            byte[] pdfBytes;


            using (var binaryReader = new BinaryReader(File.InputStream))
            {
                pdfBytes = binaryReader.ReadBytes(File.ContentLength);
            }


            try
            {
                PDFAnalysis pDFAnalysis = new PDFAnalysis();
                string extract = pDFAnalysis.AnalyzePDF(pdfBytes);
              
                return new PDFResponse() { Message = extract };
                
            }
            catch (Exception ex)
            {
              //Error do something with it or leave it :)  

            }

            return new PDFResponse() { Message = "Error! " };

        }
    }

    public class PDFResponse
    {
        public string Message { get; set; } 
        
    }
}


Nun müssen wir nur noch den Text aus dem PDF bekommen. Zum Glück müssen wir das Rad nicht neu erfinden und können auf zum Beispiel PdfPig | Read and extract text and other content from PDFs in C# (port of PDFBox) (uglytoad.github.io) verwenden.
Das extrahieren geht ganz einfach:

  private string ExtractTextFromPDF(byte[] file)
        {

            string extractedText;
            using (var pdf = PdfDocument.Open(file))
            {
                var sb = new StringBuilder();
                for (var i = 0; i < pdf.NumberOfPages; i++)
                {
                    var page = pdf.GetPage(i + 1);
                    sb.Append(page.Text);
                }
                extractedText = sb.ToString();

            }
            return extractedText;
        }

Das File übergeben wir als Byte[] damit wir es nicht irgendwo auf dem Server zwischen speichern müssen.


Nostalgisches Upload und Text extrahieren ist doch einfach oder?

So als nächstes kommt der lustige Teil. Der Aufruf des OpenAI Api.

  private string AnalyzeTextWithChatGpt(string extractedText)
        {
            string responseContent = string.Empty;
            try
            {
                List<MessageObj> roles = new List<MessageObj>();
                roles.Add(new MessageObj() { role = "system", content = "Summarize the text" });
                roles.Add(new MessageObj() { role = "user", content = extractedText });
                //MessageObj[] msg;

                // Set up the OpenAI API client
                var content = new StringContent(
            JsonConvert.SerializeObject(new
            {
                model = "gpt-3.5-turbo",
                messages = roles


            }),
            Encoding.UTF8,
            "application/json");

                client.DefaultRequestHeaders.Clear();
                client.DefaultRequestHeaders.Add("Authorization", "Bearer " + chatGptApiKey);
                //var response = client.PostAsync("https://api.openai.com/v1/engines/davinci-codex/completions", content).Result;

                var response = client.PostAsync("https://api.openai.com/v1/chat/completions", content).Result;

                responseContent = response.Content.ReadAsStringAsync().Result;

                var explanation = JsonConvert.DeserializeObject<dynamic>(responseContent).choices[0].message.content;
                return explanation;
            }
            catch (Exception ex)
            {

                Exception exception = new Exception(responseContent, ex);
                throw exception;
            }


        }

Der ganze Source ist unter gpiwonka/GPT3PDF (github.com). Es fehlen natürlich eine gescheite Fehlerbehandlung und auch ein Verfahren, wenn das PDF zu groß ist. Hierzu müsste das PDF gesplittet werden und dann jeder einzelne Teil zusammengefasst werden. Live anzuschauen unter: gpt3pdf20230526121655.azurewebsites.net

OpenAI und Visual Studio Extension

Als kleine Fingerübung dachte ich mir, dass eine Visual Studio Extension gut wäre die Code erklärt – dank OpenAI sollte das ja kein Problem sein.

War es auch nicht.

Zuerst installierte ich das Extensibilty Template Pack 2022 für Visual Studio 2022.
Danach konnte es beginnen:

Als erstes fügte ich ein Tool Window hinzu. Ein Toolwindow ist im Prinzip nichts anderes als eine XAML User control und eine Klasse die von BaseToolWindow ableitet

  public class ExplicareToolWindow : BaseToolWindow<ExplicareToolWindow>
    {
        public override string GetTitle(int toolWindowId) => "Explicare";

        public override Type PaneType => typeof(Pane);

        public override async Task<FrameworkElement> CreateAsync(int toolWindowId, CancellationToken cancellationToken)
        {

           var dte = await Package.GetServiceAsync(typeof(EnvDTE.DTE)) as EnvDTE80.DTE2;

            return new ExplicareToolWindowControl(dte,Package);
        }

        [Guid("b8381bcb-59d1-4b5c-9951-fdbfb44990c9")]
        internal class Pane : ToolWindowPane
        {
            public Pane()
            {
                BitmapImageMoniker = KnownMonikers.ToolWindow;
            }
        }
    }

ExplicareToolWindowControl(dte,Package); ist die eigentliche UserControl – in der wir beliebige UI Elemente einbauen können.
Ich habe jetzt nur einen Button eingebaut den ich “Explain to me” genannt habe.
Schnell und Schmutzig im Code-Behind ein Button Click Event gemacht.
Nun den Text aus dem Visual Studio Editor auslesen dies geschieht mit:

 var textSelection = (TextSelection)MyDTE.ActiveDocument.Selection;

                if (String.IsNullOrEmpty(textSelection.Text))
                {
                    TextDocument doc = (TextDocument)(MyDTE.ActiveDocument.Object("TextDocument"));

                    var p = doc.StartPoint.CreateEditPoint();
                    TextToExplain = p.GetText(doc.EndPoint);

                }
                else
                {
                    TextToExplain = textSelection.Text;

                }

So das schwierigste habe ich hinter mir nun noch einen Aufruf des OpenAI API aufrufen. Das geht eigentlich ganz einfach

  var content = new StringContent(jsonContent, Encoding.UTF8, "application/json");


                var response = client.PostAsync("https://api.openai.com/v1/completions", content).Result;
                if (response.IsSuccessStatusCode)
                {
                    string responseStr = await response.Content.ReadAsStringAsync();

                    var result = JsonConvert.DeserializeObject<OpenAIResponse>(responseStr);
                    explained = result.choices[0].text;
                }
                else
                {
                    throw new BadRequestExecption();
                }

Den ganzen Source ist auf Git Hub gpiwonka/Explicare: A small extension for Visual Studio to better understand code using OpenAI. (github.com)

Gibt es auch als Extension: Explicare – Visual Studio Marketplace