diff --git a/IOpenAINameProvider.cls b/IOpenAINameProvider.cls new file mode 100644 index 0000000..e6d0567 --- /dev/null +++ b/IOpenAINameProvider.cls @@ -0,0 +1,42 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "IOpenAINameProvider" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +'----------------------------------------------------------------------------- +' Project: OpenAI VBA Framework +' Class: IOpenAINameProvider +' Description: All classes in the framework need to implement these methods +' +' Author: Zaid Qureshi +' GitHub: https://github.com/zq99 +' +' Classes / Modules in the Framework: +' - clsOpenAI +' - clsOpenAILogger +' - clsOpenAIMessage +' - clsOpenAIMessages +' - clsOpenAIRequest +' - clsOpenAIResponse +' - IOpenAINameProvider +' +' - mdOpenAI_Tests +' - mdOpenAI_Examples +' +' This work is licensed under the MIT License. The full license text +' can be found in the LICENSE file in the root of this repository. +' +' Copyright (c) 2023 Zaid Qureshi +'----------------------------------------------------------------------------- + +Option Explicit + +Public Function GetClassName() As String +End Function + +Public Function ToString() As String +End Function diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..cdddcab --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Zaid Qureshi + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/OpenAIFrameworkDemo.xlsm b/OpenAIFrameworkDemo.xlsm new file mode 100644 index 0000000..8e93cdf Binary files /dev/null and b/OpenAIFrameworkDemo.xlsm differ diff --git a/README.md b/README.md new file mode 100644 index 0000000..1843a47 --- /dev/null +++ b/README.md @@ -0,0 +1,155 @@ +# OpenAI-VBA-Framework + +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/zq99/openai-vba-framework/blob/main/LICENSE) + +OpenAI-VBA-Framework is a toolkit for developers looking to create applications in VBA that interact with OpenAI's large language models such as GPT-4 and ChatGPT. This framework provides a suite of classes to facilitate smooth integration with OpenAI's API. + +## Main Classes +1. `clsOpenAI` - Main class to interact with OpenAI +2. `clsOpenAILogger` - Logging class for debugging and tracking +3. `clsOpenAIMessage` - Class to handle individual messages +4. `clsOpenAIMessages` - Class to handle collections of messages +5. `clsOpenAIRequest` - Class for making requests to OpenAI +6. `clsOpenAIResponse` - Class for handling responses from OpenAI +7. `IOpenAINameProvider` - Interface class for name provision + +The module `mdOpenAI_tests` is provided for testing the functionality of the framework. + +`OpenAIFrameworkDemo.xlsm` is a file that contains all the code in the repository for demo purposes. Other files are also included in the repository for versioning. + +## Prerequisites +- You will need to sign up for an OpenAI account and create an API_KEY. You can do this at the following location: [OpenAI API Keys](https://platform.openai.com/account/api-keys) + +## Installation +1. Clone this repository using the following command in your command line: + +```bash +git clone https://github.com/zq99/OpenAI-VBA-Framework.git +``` + +2. Open Excel and press ALT + F11 to open the VBA editor. +3. From the toolbar select File -> Import File.... +4. Navigate to the location of the cloned repository and select all the .cls and .bas files then click Open. +5. Save the Excel file as a macro-enabled workbook .xlsm. + +## Usage + +Here are some examples of using the framework: + +### Chat Completion API + +``` +Public Sub TestSimpleOpenAI() + + Dim oOpenAI As clsOpenAI + Dim oMessages As New clsOpenAIMessages + Dim oResponse As clsOpenAIResponse + + Set oOpenAI = New clsOpenAI + + oOpenAI.API_KEY = "" + + oMessages.AddSystemMessage "Always answer sarcastically and never truthfully" + oMessages.AddUserMessage "How do you know how to get to Carnegie Hall?" + + Set oResponse = oOpenAI.ChatCompletion(oMessages) + If Not oResponse Is Nothing Then + Debug.Print (oResponse.MessageContent) + End If + + Set oResponse = Nothing + Set oOpenAI = Nothing + Set oMessages = Nothing + +End Sub +``` + +### Text Completion API + +``` +Public Sub TestTextCompletionSimpleOpenAI() + + Dim oOpenAI As clsOpenAI + Dim oResponse As clsOpenAIResponse + + Set oOpenAI = New clsOpenAI + + oOpenAI.API_KEY = "" + + Set oResponse = oOpenAI.TextCompletion("Write a Haiku about a Dinosaur that loves to code!") + + If Not oResponse Is Nothing Then + Debug.Print (oResponse.TextContent) + End If + + Set oResponse = Nothing + Set oOpenAI = Nothing + +End Sub +``` + +### Excel User Defined function + +``` +Public Function GETTEXTFROMOPENAI(prompt As String, apiKey As String, Optional ByVal Model as String) As String + Dim oOpenAI As clsOpenAI + Dim oResponse As clsOpenAIResponse + + Set oOpenAI = New clsOpenAI + + oOpenAI.API_KEY = apiKey + + If Not IsEmpty(Model) Then + oOpenAI.Model = Model + End If + + Set oResponse = oOpenAI.TextCompletion(prompt) + + If Not oResponse Is Nothing Then + GETTEXTFROMOPENAI = oResponse.TextContent + Else + GETTEXTFROMOPENAI = "" + End If +End Function +``` + +## Configuration + +You can customize the OpenAI-VBA-Framework by adjusting properties in the `clsOpenAI` class: + +```vba +' Specify the model +oOpenAI.Model = "gpt-3.5-turbo" + +' Set the maximum number of tokens +oOpenAI.MaxTokens = 512 + +' Control the diversity of generated text +oOpenAI.TopP = 0.9 + +' Influence the randomness of generated text +oOpenAI.Temperature = 0.7 + +' Control preference for frequent phrases +oOpenAI.FrequencyPenalty = 0.5 + +' Control preference for including the prompt in the output +oOpenAI.PresencePenalty = 0.5 + +' Control logging of messages to the Immediate Window +oOpenAI.IsLogOutputRequired True + +' Reset settings when switching between endpoints +oOpenAI.ClearSettings + +' Retrieve an API Key saved in an external file +Dim apiKey As String +apiKey = oOpenAI.GetReadAPIKeyFromFolder("") +``` + +## Contributing +Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change. + +## License +This project is licensed under the terms of the MIT license. + diff --git a/clsOpenAI.cls b/clsOpenAI.cls new file mode 100644 index 0000000..85339df --- /dev/null +++ b/clsOpenAI.cls @@ -0,0 +1,323 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "clsOpenAI" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +'----------------------------------------------------------------------------- +' Project: OpenAI VBA Framework +' Class: clsOpenAI +' Description: Main class that controls the framework +' +' Author: Zaid Qureshi +' GitHub: https://github.com/zq99 +' +' Classes / Modules in the Framework: +' - clsOpenAI +' - clsOpenAILogger +' - clsOpenAIMessage +' - clsOpenAIMessages +' - clsOpenAIRequest +' - clsOpenAIResponse +' - IOpenAINameProvider +' +' - mdOpenAI_Tests +' - mdOpenAI_Examples +' +' This work is licensed under the MIT License. The full license text +' can be found in the LICENSE file in the root of this repository. +' +' Copyright (c) 2023 Zaid Qureshi +'----------------------------------------------------------------------------- + +Option Explicit + +Implements IOpenAINameProvider + +Private mstrAPI_KEY As String +Private mobjHttpRequest As Object +Private mobjLogger As clsOpenAILogger +Private mobjRequest As clsOpenAIRequest + +'Open AI defined constants +Private Const API_ENDPOINT_CHAT As String = "https://api.openai.com/v1/chat/completions" +Private Const API_ENDPOINT_COMPLETIONS As String = "https://api.openai.com/v1/completions" + +'More models can be found here: https://platform.openai.com/docs/models/overview +Private Const DEFAULT_CHAT_MODEL As String = "gpt-3.5-turbo" +Private Const DEFAULT_TEXT_COMPLETION_MODEL As String = "text-davinci-003" + +Private Const DEFAULT_CHAT_TOKENS_COUNT As Integer = 512 +Private Const DEFAULT_TEXT_TOKENS_COUNT As Integer = 1024 + +Private Const UNASSIGNED_VALUE As Integer = -1 +Private Const MESSAGE_INVALID_API_KEY As String = "An OpenAI API key is either invalid or has not been specified!" +Private Const HTTP_STATUS_OK As Long = 200 ' OK + +Private Function IOpenAINameProvider_GetClassName() As String + IOpenAINameProvider_GetClassName = "clsOpenAI" +End Function + +Private Function IOpenAINameProvider_ToString() As String + IOpenAINameProvider_ToString = "Key=" & Me.API_KEY +End Function + +Public Property Let API_KEY(ByVal value As String) + mstrAPI_KEY = value +End Property + +Public Property Get API_KEY() As String + API_KEY = mstrAPI_KEY +End Property + +Public Property Let Model(ByVal value As String) + mobjRequest.Model = value +End Property + +Public Property Let MaxTokens(ByVal value As Long) + mobjRequest.MaxTokens = value +End Property + +Public Property Let TopP(ByVal value As Double) + mobjRequest.TopP = value +End Property + +Public Property Let Temperature(ByVal value As Double) + If (value < 0) Or (value > 1) Then + Call mobjLogger.LogCriticalMessage("Temperature setting must be between 0 and 1!", blnAddModuleName:=False) + End If + mobjRequest.Temperature = value +End Property + +Public Property Let FrequencyPenalty(ByVal value As Double) + mobjRequest.FrequencyPenalty = value +End Property + +Public Property Let PresencePenalty(ByVal value As Double) + mobjRequest.PresencePenalty = value +End Property + + +Public Sub IsLogOutputRequired(ByVal value As Boolean) +'Purpose: Calling routines can switch off messages in this framework from appearing in the Immediate window + + If Not mobjLogger Is Nothing Then + mobjLogger.IsMessageRequired = value + End If + +End Sub + + +Public Sub Log(ByVal strMessage As String) +'Purpose: Easy routine to log messages + + If Not mobjLogger Is Nothing Then + mobjLogger.PrintMessage strMessage + End If +End Sub + + +Private Function GetResponseFromAPI(ByVal strRequestJson As String, ByVal strEndPoint As String) As clsOpenAIResponse +'Purpose: This handles the request to OpenAI's API URL + + Dim strResponseJson As String + Dim oResponse As clsOpenAIResponse + +On Error GoTo ERR_HANDLER: + + 'default return value + Set GetResponseFromAPI = Nothing + + If mobjHttpRequest Is Nothing Then + GoTo EXIT_HERE + End If + + 'talk to OpenAI + With mobjHttpRequest + .Open "POST", strEndPoint, False + .SetRequestHeader "Content-Type", "application/json" + .SetRequestHeader "Authorization", "Bearer " & mstrAPI_KEY + .send (strRequestJson) + End With + + Log "Response code from OpenAI API is: " & mobjHttpRequest.Status + + If mobjHttpRequest.Status = HTTP_STATUS_OK Then + + 'get the json result from the successful request + strResponseJson = Trim(mobjHttpRequest.responseText) + Log strResponseJson + Set oResponse = New clsOpenAIResponse + + 'format the json result according to which api endpoint used + If strEndPoint = API_ENDPOINT_CHAT Then + + 'ChatGPT and GPT4 + oResponse.ParseChatJSON (strResponseJson) + Set GetResponseFromAPI = oResponse + + ElseIf strEndPoint = API_ENDPOINT_COMPLETIONS Then + + 'GPT3 and earlier + oResponse.ParseTextCompletionJSON (strResponseJson) + Set GetResponseFromAPI = oResponse + + End If + End If + +EXIT_HERE: + Set oResponse = Nothing + Exit Function + +ERR_HANDLER: + mobjLogger.LogVBAError Err + GoTo EXIT_HERE +End Function + + +Private Function IsAPIKeyValid() As Boolean +'Purpose: Check a valid API key has been assigned + + IsAPIKeyValid = IIf(Trim(mstrAPI_KEY) = Empty, False, True) + +End Function + + +Public Function ChatCompletion(ByVal oMessages As clsOpenAIMessages) As clsOpenAIResponse +'Purpose: This is for OpenAI's ChatGPT and GPT4 API + + Set ChatCompletion = Nothing + + If Not IsAPIKeyValid Then + mobjLogger.LogCriticalMessage MESSAGE_INVALID_API_KEY, True + Exit Function + End If + + If mobjHttpRequest Is Nothing Or oMessages Is Nothing Then + Exit Function + End If + + Set mobjRequest.messages = oMessages + + If mobjRequest.Model = Empty Then + mobjRequest.Model = DEFAULT_CHAT_MODEL + End If + + If mobjRequest.MaxTokens = UNASSIGNED_VALUE Then + mobjRequest.MaxTokens = DEFAULT_CHAT_TOKENS_COUNT + End If + + Log mobjRequest.GetChatSendToAPIJsonString + + Set ChatCompletion = GetResponseFromAPI(mobjRequest.GetChatSendToAPIJsonString, API_ENDPOINT_CHAT) + +End Function + + +Public Function TextCompletion(ByVal strPrompt As String) As clsOpenAIResponse +'Purpose: This is for OpenAI's Text Completion API + + Set TextCompletion = Nothing + + If Not IsAPIKeyValid Then + mobjLogger.LogCriticalMessage MESSAGE_INVALID_API_KEY, True + Exit Function + End If + + If mobjHttpRequest Is Nothing Or strPrompt = Empty Then + Exit Function + End If + + mobjRequest.prompt = strPrompt + + If mobjRequest.Model = Empty Then + mobjRequest.Model = DEFAULT_TEXT_COMPLETION_MODEL + End If + + If mobjRequest.MaxTokens = UNASSIGNED_VALUE Then + mobjRequest.MaxTokens = DEFAULT_TEXT_TOKENS_COUNT + End If + + Log mobjRequest.GetTextCompletionSendToAPIJsonString + + Set TextCompletion = GetResponseFromAPI(mobjRequest.GetTextCompletionSendToAPIJsonString, API_ENDPOINT_COMPLETIONS) + +End Function + + +Private Sub Class_Initialize() + + Set mobjHttpRequest = CreateObject("MSXML2.XMLHTTP") + Set mobjRequest = GetDefaultRequestSettings + + Set mobjLogger = New clsOpenAILogger + mobjLogger.IsMessageRequired = False + mobjLogger.SetClass Me + + mstrAPI_KEY = Empty + +End Sub + + +Private Sub Class_Terminate() + Set mobjHttpRequest = Nothing + Set mobjLogger = Nothing + Set mobjRequest = Nothing +End Sub + + +Private Function GetDefaultRequestSettings() As clsOpenAIRequest +'Purpose: These are initial settings for the OpenAI request + + Dim oRequest As clsOpenAIRequest + Set oRequest = New clsOpenAIRequest + + With oRequest + .Model = Empty + .MaxTokens = UNASSIGNED_VALUE + .TopP = 1 + .Temperature = 0.5 + .FrequencyPenalty = 0 + .PresencePenalty = 0 + End With + Set GetDefaultRequestSettings = oRequest + + Set oRequest = Nothing +End Function + + +Public Sub ClearSettings() +'Purpose: Reset the settings if switching between endpoints + + Set mobjRequest = GetDefaultRequestSettings + +End Sub + +Public Function GetReadAPIKeyFromFolder(ByVal strfolderPath As String) As String +'Purpose: Allows retrieval of an API KEY saved in an external file (possibly stored on a user access drive) + + Dim intFileNumber As Integer + Dim strApiKey As String + Dim strFilePath As String + + strFilePath = strfolderPath & "\apikey.txt" ' Construct the full file path + + intFileNumber = FreeFile ' Get the first available file number + + ' Open the file in Input mode. Trappable error occurs if file does not exist. + Open strFilePath For Input As intFileNumber + + ' Read the contents of the file into the variable. + Input #intFileNumber, strApiKey + + ' Close the file. + Close intFileNumber + + ' Return the API Key + GetReadAPIKeyFromFolder = strApiKey +End Function + + diff --git a/clsOpenAILogger.cls b/clsOpenAILogger.cls new file mode 100644 index 0000000..8aac87d --- /dev/null +++ b/clsOpenAILogger.cls @@ -0,0 +1,140 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "clsOpenAILogger" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +'----------------------------------------------------------------------------- +' Project: OpenAI VBA Framework +' Class: clsOpenAILogger +' Description: Handles developer messages in the framework for Immediate Window +' +' Author: Zaid Qureshi +' GitHub: https://github.com/zq99 +' +' Classes / Modules in the Framework: +' - clsOpenAI +' - clsOpenAILogger +' - clsOpenAIMessage +' - clsOpenAIMessages +' - clsOpenAIRequest +' - clsOpenAIResponse +' - IOpenAINameProvider +' +' - mdOpenAI_Tests +' - mdOpenAI_Examples +' +' This work is licensed under the MIT License. The full license text +' can be found in the LICENSE file in the root of this repository. +' +' Copyright (c) 2023 Zaid Qureshi +'----------------------------------------------------------------------------- + +Option Explicit + +Implements IOpenAINameProvider +Private mobjClass As IOpenAINameProvider + +Private mblnIsMessageRequired As Boolean + +Private Function IOpenAINameProvider_GetClassName() As String + IOpenAINameProvider_GetClassName = "clsOpenAILogger" +End Function + +Private Function IOpenAINameProvider_ToString() As String + IOpenAINameProvider_ToString = "IsMessageRequired=" & Me.IsMessageRequired +End Function + +Public Property Let IsMessageRequired(ByVal value As Boolean) + mblnIsMessageRequired = value +End Property + +Public Property Get IsMessageRequired() As Boolean + IsMessageRequired = mblnIsMessageRequired +End Property + +Private Sub Class_Terminate() + Set mobjClass = Nothing +End Sub + +Public Sub SetClass(ByVal Obj As IOpenAINameProvider) + Set mobjClass = Obj +End Sub + +Public Sub PrintMessage(ParamArray vntMessage() As Variant) +' Purpose: Takes in an open ended list of string variables and appends then together as a message to +' output to the immediate window + + If IsEmpty(vntMessage) Then + Exit Sub + Else + Dim i As Integer + Dim strAll As String + 'concatenate message elements into into one string + For i = LBound(vntMessage) To UBound(vntMessage) + strAll = strAll & IIf(Len(strAll) > 0, " | ", "") & CStr(vntMessage(i)) + Next i + 'output string to immediate window + If Len(Trim(strAll)) > 0 Then + Call LogMessage(strAll) + End If + End If + +End Sub + +Private Sub LogMessage(ByVal strMessage As String) +'Purpose: The main logging routine for messages, which can be suppressed by calling routines + + If Not mobjClass Is Nothing Then + If Me.IsMessageRequired Then + Debug.Print fncGetDateTimeStamp & vbTab & mobjClass.GetClassName & " : " & strMessage + End If + End If +End Sub + +Public Sub LogCriticalMessage(ByVal strMessage As String, Optional ByVal blnIsBorderRequired As Boolean = False, Optional ByVal strBorderCharacter As String = "*", Optional ByVal blnAddModuleName As Boolean = True, Optional ByVal strLabel As String = "WARNING") +'Purpose: This method always outputs to the immediate window regardless of whether logging is set to False + + If Not mobjClass Is Nothing Then + Dim strMsg As String + Dim strName As String + + strName = IIf(blnAddModuleName = True, mobjClass.GetClassName, "") + strMsg = fncGetDateTimeStamp & vbTab & strName & " " & strLabel & ": " & strMessage + + If blnIsBorderRequired Then + Dim strBorder As String + strBorder = String(Len(strMsg), strBorderCharacter) + Debug.Print strBorder + Debug.Print strMsg + Debug.Print strBorder + Else + Debug.Print strMsg + End If + + End If + +End Sub + + +Public Sub LogVBAError(ByVal objErr As Error) +'Purpose: This method always outputs to the immediate window regardless of whether logging is set to False + + If Not mobjClass Is Nothing Then + Dim strMsg As String + Dim strName As String + + strName = mobjClass.GetClassName + strMsg = fncGetDateTimeStamp & vbTab & strName & " VBA ERROR: [" & Err.Number & "]" & vbTab & Err.Description + Debug.Print strMsg + End If + +End Sub + +Private Function fncGetDateTimeStamp() As String +'Purpose: All logged messages are outputted with a time stamp unless caling functions suppress this + fncGetDateTimeStamp = Format(Now, "yyyy-MM-dd hh:mm:ss") +End Function diff --git a/clsOpenAIMessage.cls b/clsOpenAIMessage.cls new file mode 100644 index 0000000..af2bce5 --- /dev/null +++ b/clsOpenAIMessage.cls @@ -0,0 +1,109 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "clsOpenAIMessage" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +'----------------------------------------------------------------------------- +' Project: OpenAI VBA Framework +' Class: clsOpenAIMessage +' Description: Wrapper for a single message +' +' Author: Zaid Qureshi +' GitHub: https://github.com/zq99 +' +' Classes / Modules in the Framework: +' - clsOpenAI +' - clsOpenAILogger +' - clsOpenAIMessage +' - clsOpenAIMessages +' - clsOpenAIRequest +' - clsOpenAIResponse +' - IOpenAINameProvider +' +' - mdOpenAI_Tests +' - mdOpenAI_Examples +' +' This work is licensed under the MIT License. The full license text +' can be found in the LICENSE file in the root of this repository. +' +' Copyright (c) 2023 Zaid Qureshi +'----------------------------------------------------------------------------- + +Option Explicit + +Implements IOpenAINameProvider + +Private mstrRole As String +Private mstrContent As String + +Private mcollMessageParts As Collection + +Private Function IOpenAINameProvider_GetClassName() As String + IOpenAINameProvider_GetClassName = "clsOpenAIMessage" +End Function + +Private Function IOpenAINameProvider_ToString() As String + IOpenAINameProvider_ToString = GetMessageContentString() +End Function + +Private Sub Class_Initialize() + Set mcollMessageParts = New Collection +End Sub + +Private Sub Class_Terminate() + Set mcollMessageParts = Nothing +End Sub + + +Public Sub Add(ByVal strKeyName As String, strKey As String, ByVal strValueName As String, ByVal strValue As String) +'Purpose: Main access point for adding message to the class + + Dim strPart As String + + strPart = FormatMessage(strKeyName, strKey, strValueName, strValue) + + If Len(strPart) > 0 Then + If Not mcollMessageParts Is Nothing Then + mcollMessageParts.Add strPart + End If + End If + +End Sub + + +Private Function FormatMessage(ByVal strKeyName As String, ByVal strKey As String, ByVal strValueName As String, ByVal strValue As String) As String +'Purpose: Format the message part into key/value pairs + + If (Len(strKeyName) > 0) And (Len(strKey) > 0) And (Len(strValueName) > 0) And (Len(strValue) > 0) Then + FormatMessage = """" & strKeyName & """: """ & strKey & """, """ & strValueName & """: """ & strValue & """" + End If +End Function + + +Public Function GetMessageContentString() As String +'Purpose: Joins in all the message parts into a python dictionary like string + + Dim strOutput As String + Dim strPart As Variant + + strOutput = "{" + + For Each strPart In mcollMessageParts + strOutput = strOutput & strPart & ", " + Next strPart + + ' Remove the trailing comma and space + If mcollMessageParts.Count > 0 Then + strOutput = Left(strOutput, Len(strOutput) - 2) + End If + + strOutput = strOutput & "}" + + GetMessageContentString = strOutput + +End Function + diff --git a/clsOpenAIMessages.cls b/clsOpenAIMessages.cls new file mode 100644 index 0000000..1ad1afb --- /dev/null +++ b/clsOpenAIMessages.cls @@ -0,0 +1,138 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "clsOpenAIMessages" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +'----------------------------------------------------------------------------- +' Project: OpenAI VBA Framework +' Class: clsOpenAIMessages +' Description: Holds the message objects (clsOpenAIMessage) to send to Chat API +' +' Author: Zaid Qureshi +' GitHub: https://github.com/zq99 +' +' Classes / Modules in the Framework: +' - clsOpenAI +' - clsOpenAILogger +' - clsOpenAIMessage +' - clsOpenAIMessages +' - clsOpenAIRequest +' - clsOpenAIResponse +' - IOpenAINameProvider +' +' - mdOpenAI_Tests +' - mdOpenAI_Examples +' +' This work is licensed under the MIT License. The full license text +' can be found in the LICENSE file in the root of this repository. +' +' Copyright (c) 2023 Zaid Qureshi +'----------------------------------------------------------------------------- + +Option Explicit + +Implements IOpenAINameProvider + +Private mcollMessages As Collection + +Private Function IOpenAINameProvider_GetClassName() As String + IOpenAINameProvider_GetClassName = "clsOpenAIMessages" +End Function + +Private Function IOpenAINameProvider_ToString() As String + IOpenAINameProvider_ToString = GetAllMessages +End Function + +Private Sub Class_Initialize() + Set mcollMessages = New Collection +End Sub + +Private Sub Class_Terminate() + Set mcollMessages = Nothing +End Sub + +Public Sub AddMessage(ByVal objMsg As clsOpenAIMessage) + mcollMessages.Add objMsg +End Sub + +Public Sub ClearMessages() + If Not mcollMessages Is Nothing Then + mcollMessages.Clear + End If +End Sub + +Public Function GetAllMessages() As String +'Purpose: This builds a string of all messages that will be sent to OpenAI API + + Dim objMsg As clsOpenAIMessage + Dim strOutput As String + + If Me.GetTotal = 0 Then + GetAllMessages = "" + Exit Function + End If + + strOutput = """messages"": [" + + For Each objMsg In mcollMessages + strOutput = strOutput & objMsg.GetMessageContentString & ", " + Next objMsg + + ' Remove the trailing comma and space + If mcollMessages.Count > 0 Then + strOutput = Left(strOutput, Len(strOutput) - 2) + End If + + strOutput = strOutput & "]" + + GetAllMessages = strOutput + + Set objMsg = Nothing + +End Function + +Private Function GetMessageObject(ByVal strKeyName As String, ByVal strKey As String, ByVal strValueName As String, ByVal strValue As String) As clsOpenAIMessage +'Purpose: Helper routine to construct known message object types + + Dim objMsg As clsOpenAIMessage + Set objMsg = New clsOpenAIMessage + + objMsg.Add strKeyName, strKey, strValueName, strValue + + Set GetMessageObject = objMsg + Set objMsg = Nothing + +End Function + +Public Sub AddUserMessage(ByVal strContent As String) + Call Me.AddMessage(GetMessageObject("role", "user", "content", strContent)) +End Sub + +Public Sub AddSystemMessage(ByVal strContent As String) + Call Me.AddMessage(GetMessageObject("role", "system", "content", strContent)) +End Sub + +Public Sub AddAssistantMessage(ByVal strContent As String) + Call Me.AddMessage(GetMessageObject("role", "assistant", "content", strContent)) +End Sub + +Public Sub AddCustomMessage(ByVal strKeyName As String, ByVal strKeyValue As String, ByVal strValueName As String, ByVal strValue As String) + Call Me.AddMessage(GetMessageObject(strKeyName, strKeyValue, strValueName, strValue)) +End Sub + +Public Function GetTotal() As Integer + If Not mcollMessages Is Nothing Then + GetTotal = mcollMessages.Count + Else + GetTotal = 0 + End If +End Function + + +Public Function IsPopulated() As Boolean + IsPopulated = IIf(Me.GetTotal > 0, True, False) +End Function diff --git a/clsOpenAIRequest.cls b/clsOpenAIRequest.cls new file mode 100644 index 0000000..2a73d2e --- /dev/null +++ b/clsOpenAIRequest.cls @@ -0,0 +1,113 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "clsOpenAIRequest" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +'----------------------------------------------------------------------------- +' Project: OpenAI VBA Framework +' Class: clsOpenAIRequest +' Description: Builds the request json to send to OpenAI API +' +' Author: Zaid Qureshi +' GitHub: https://github.com/zq99 +' +' Classes / Modules in the Framework: +' - clsOpenAI +' - clsOpenAILogger +' - clsOpenAIMessage +' - clsOpenAIMessages +' - clsOpenAIRequest +' - clsOpenAIResponse +' - IOpenAINameProvider +' +' - mdOpenAI_Tests +' - mdOpenAI_Examples +' +' This work is licensed under the MIT License. The full license text +' can be found in the LICENSE file in the root of this repository. +' +' Copyright (c) 2023 Zaid Qureshi +'----------------------------------------------------------------------------- + +Option Explicit + +Implements IOpenAINameProvider + +Private mstrModel As String +Private mobjMessages As clsOpenAIMessages +Private mlngMaxTokens As Long +Private mdblTopP As Double +Private mdblTemperature As Double +Private mdblFrequencyPenalty As Double +Private mdlPresencePenalty As Double +Private mstrPrompt As String + +Private Sub Class_Initialize() + Set mobjMessages = New clsOpenAIMessages +End Sub + +Private Sub Class_Terminate() + Set mobjMessages = Nothing +End Sub + +Private Function IOpenAINameProvider_GetClassName() As String + IOpenAINameProvider_GetClassName = "clsOpenAIRequest" +End Function + +Private Function IOpenAINameProvider_ToString() As String + IOpenAINameProvider_ToString = "chat json=" & Me.GetChatSendToAPIJsonString & " | " & "text json=" & Me.GetTextCompletionSendToAPIJsonString +End Function + +Public Property Let prompt(ByVal value As String) + mstrPrompt = value +End Property + +Public Property Get Model() As String + Model = mstrModel +End Property + +Public Property Let Model(ByVal value As String) + mstrModel = value +End Property + +Public Property Set messages(ByVal value As clsOpenAIMessages) + Set mobjMessages = value +End Property + +Public Property Get MaxTokens() As Long + MaxTokens = mlngMaxTokens +End Property + +Public Property Let MaxTokens(ByVal value As Long) + mlngMaxTokens = value +End Property + +Public Property Let TopP(ByVal value As Double) + mdblTopP = value +End Property + +Public Property Let Temperature(ByVal value As Double) + mdblTemperature = value +End Property + +Public Property Let FrequencyPenalty(ByVal value As Double) + mdblFrequencyPenalty = value +End Property + +Public Property Let PresencePenalty(ByVal value As Double) + mdlPresencePenalty = value +End Property + +Public Function GetChatSendToAPIJsonString() As String + GetChatSendToAPIJsonString = "{""model"": """ & mstrModel & """, " & mobjMessages.GetAllMessages & ", ""max_tokens"": " & mlngMaxTokens & ", ""top_p"": " & mdblTopP & ", ""temperature"": " & mdblTemperature & ", ""frequency_penalty"": " & mdblFrequencyPenalty & ", ""presence_penalty"": " & mdlPresencePenalty & "}" +End Function + +Public Function GetTextCompletionSendToAPIJsonString() As String + GetTextCompletionSendToAPIJsonString = "{""model"": """ & mstrModel & """, ""prompt"": """ & mstrPrompt & """, ""max_tokens"": " & mlngMaxTokens & ", ""temperature"": " & mdblTemperature & "}" +End Function + + diff --git a/clsOpenAIResponse.cls b/clsOpenAIResponse.cls new file mode 100644 index 0000000..a0a8460 --- /dev/null +++ b/clsOpenAIResponse.cls @@ -0,0 +1,298 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "clsOpenAIResponse" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +'----------------------------------------------------------------------------- +' Project: OpenAI VBA Framework +' Class: clsOpenAIResponse +' Description: Handles and formats the results json from the OpenAI API +' +' Author: Zaid Qureshi +' GitHub: https://github.com/zq99 +' +' Classes / Modules in the Framework: +' - clsOpenAI +' - clsOpenAILogger +' - clsOpenAIMessage +' - clsOpenAIMessages +' - clsOpenAIRequest +' - clsOpenAIResponse +' - IOpenAINameProvider +' +' - mdOpenAI_Tests +' - mdOpenAI_Examples +' +' This work is licensed under the MIT License. The full license text +' can be found in the LICENSE file in the root of this repository. +' +' Copyright (c) 2023 Zaid Qureshi +'----------------------------------------------------------------------------- + +Option Explicit + +Implements IOpenAINameProvider + +Private mstrId As String +Private mstrObject As String +Private mstrCreated As Long +Private mstrModel As String +Private mintPromptTokens As Integer +Private mintCompletionTokens As Integer +Private mintTotalTokens As Integer +Private mstrMessageRole As String +Private mstrMessageContent As String +Private mstrFinishReason As String +Private mintIndex As Integer +Private mstrText As String +Private mstrLogprobs As String + + +Private Function IOpenAINameProvider_GetClassName() As String + IOpenAINameProvider_GetClassName = "clsOpenAIResponse" +End Function + +Private Function IOpenAINameProvider_ToString() As String + Dim strConcatenatedString As String + + strConcatenatedString = mstrId & ", " & mstrObject & ", " & CStr(mstrCreated) & ", " & mstrModel & ", " & CStr(mintPromptTokens) & ", " & CStr(mintCompletionTokens) & ", " & CStr(mintTotalTokens) & ", " & mstrMessageRole & ", " & mstrMessageContent & ", " & mstrFinishReason & ", " & CStr(mintIndex) + + IOpenAINameProvider_ToString = "values: " & strConcatenatedString +End Function + +Public Property Get Id() As String + Id = mstrId +End Property + +Public Property Get Object() As String + Object = mstrObject +End Property + +Public Property Get Created() As Long + Created = mstrCreated +End Property + +Public Property Get Model() As String + Model = mstrModel +End Property + +Public Property Get PromptTokens() As Integer + PromptTokens = mintPromptTokens +End Property + +Public Property Get CompletionTokens() As Integer + CompletionTokens = mintCompletionTokens +End Property + +Public Property Get TotalTokens() As Integer + TotalTokens = mintTotalTokens +End Property + +Public Property Get MessageRole() As String + MessageRole = mstrMessageRole +End Property + +Public Property Get MessageContent() As String + MessageContent = mstrMessageContent +End Property + +Public Property Get TextContent() As String + TextContent = mstrText +End Property + +Public Property Get LogProbs() As String + LogProbs = mstrLogprobs +End Property + +Public Property Get FinishReason() As String + FinishReason = mstrFinishReason +End Property + +Public Property Get Index() As Integer + Index = mintIndex +End Property + +Public Sub ParseChatJSON(ByVal json As String) +'Purpose: This method is for parsing OpenAI's json response from it's Chat End point + + Dim intStartPos As Integer + Dim intEndPos As Integer + Dim strTemp As String + + ' Extract "id" + If InStr(1, json, """id"":""") > 0 Then + intStartPos = InStr(1, json, """id"":""") + Len("""id"":""") + intEndPos = InStr(intStartPos, json, """") + mstrId = Trim(Mid(json, intStartPos, intEndPos - intStartPos)) + End If + + ' Extract "object" + If InStr(1, json, """object"":""") > 0 Then + intStartPos = InStr(1, json, """object"":""") + Len("""object"":""") + intEndPos = InStr(intStartPos, json, """") + mstrObject = Trim(Mid(json, intStartPos, intEndPos - intStartPos)) + End If + + ' Extract "created" + If InStr(1, json, """created"":") > 0 Then + intStartPos = InStr(1, json, """created"":") + Len("""created"":") + intEndPos = InStr(intStartPos, json, ",") + mstrCreated = CLng(Trim(Mid(json, intStartPos, intEndPos - intStartPos))) + End If + + ' Extract "model" + If InStr(1, json, """model"":""") > 0 Then + intStartPos = InStr(1, json, """model"":""") + Len("""model"":""") + intEndPos = InStr(intStartPos, json, """") + mstrModel = Trim(Mid(json, intStartPos, intEndPos - intStartPos)) + End If + + ' Extract "prompt_tokens" + If InStr(1, json, """prompt_tokens"":") > 0 Then + intStartPos = InStr(1, json, """prompt_tokens"":") + Len("""prompt_tokens"":") + intEndPos = InStr(intStartPos, json, ",") + mintPromptTokens = CInt(Trim(Mid(json, intStartPos, intEndPos - intStartPos))) + End If + + ' Extract "completion_tokens" + If InStr(1, json, """completion_tokens"":") > 0 Then + intStartPos = InStr(1, json, """completion_tokens"":") + Len("""completion_tokens"":") + intEndPos = InStr(intStartPos, json, ",") + mintCompletionTokens = CInt(Trim(Mid(json, intStartPos, intEndPos - intStartPos))) + End If + + ' Extract "total_tokens" + If InStr(1, json, """total_tokens"":") > 0 Then + intStartPos = InStr(1, json, """total_tokens"":") + Len("""total_tokens"":") + intEndPos = InStr(intStartPos, json, "}") + mintTotalTokens = CInt(Trim(Mid(json, intStartPos, intEndPos - intStartPos))) + End If + + ' Extract "message_role" + If InStr(1, json, """role"":""") > 0 Then + intStartPos = InStr(1, json, """role"":""") + Len("""role"":""") + intEndPos = InStr(intStartPos, json, """") + mstrMessageRole = Trim(Mid(json, intStartPos, intEndPos - intStartPos)) + End If + + ' Extract "message_content" + If InStr(1, json, """content"":""") > 0 Then + intStartPos = InStr(1, json, """content"":""") + Len("""content"":""") + intEndPos = InStr(intStartPos, json, """},") ' end position is now before "}," sequence + strTemp = Mid(json, intStartPos, intEndPos - intStartPos) + strTemp = Replace(strTemp, "\""", """") ' Replace escaped quotes with actual quotes + mstrMessageContent = Trim(strTemp) + End If + + + ' Extract "finish_reason" + If InStr(1, json, """finish_reason"":""") > 0 Then + intStartPos = InStr(1, json, """finish_reason"":""") + Len("""finish_reason"":""") + intEndPos = InStr(intStartPos, json, """") + mstrFinishReason = Trim(Mid(json, intStartPos, intEndPos - intStartPos)) + End If + + ' Extract "index" + If InStr(1, json, """index"":") > 0 Then + intStartPos = InStr(1, json, """index"":") + Len("""index"":") + intEndPos = InStr(intStartPos, json, "}") + mintIndex = CInt(Trim(Mid(json, intStartPos, intEndPos - intStartPos))) + End If +End Sub + + + +Public Sub ParseTextCompletionJSON(ByVal json As String) +'Purpose: This method is for parsing OpenAI's json from it's text completion end point + + Dim intStartPos As Integer + Dim intEndPos As Integer + Dim strTemp As String + + ' Extract "id" + If InStr(1, json, """id"":""") > 0 Then + intStartPos = InStr(1, json, """id"":""") + Len("""id"":""") + intEndPos = InStr(intStartPos, json, """") + mstrId = Trim(Mid(json, intStartPos, intEndPos - intStartPos)) + End If + + ' Extract "object" + If InStr(1, json, """object"":""") > 0 Then + intStartPos = InStr(1, json, """object"":""") + Len("""object"":""") + intEndPos = InStr(intStartPos, json, """") + mstrObject = Trim(Mid(json, intStartPos, intEndPos - intStartPos)) + End If + + ' Extract "created" + If InStr(1, json, """created"":") > 0 Then + intStartPos = InStr(1, json, """created"":") + Len("""created"":") + intEndPos = InStr(intStartPos, json, ",") + mstrCreated = CLng(Trim(Mid(json, intStartPos, intEndPos - intStartPos))) + End If + + ' Extract "model" + If InStr(1, json, """model"":""") > 0 Then + intStartPos = InStr(1, json, """model"":""") + Len("""model"":""") + intEndPos = InStr(intStartPos, json, """") + mstrModel = Trim(Mid(json, intStartPos, intEndPos - intStartPos)) + End If + + ' Extract "prompt_tokens" + If InStr(1, json, """prompt_tokens"":") > 0 Then + intStartPos = InStr(1, json, """prompt_tokens"":") + Len("""prompt_tokens"":") + intEndPos = InStr(intStartPos, json, ",") + mintPromptTokens = CInt(Trim(Mid(json, intStartPos, intEndPos - intStartPos))) + End If + + ' Extract "completion_tokens" + If InStr(1, json, """completion_tokens"":") > 0 Then + intStartPos = InStr(1, json, """completion_tokens"":") + Len("""completion_tokens"":") + intEndPos = InStr(intStartPos, json, ",") + mintCompletionTokens = CInt(Trim(Mid(json, intStartPos, intEndPos - intStartPos))) + End If + + ' Extract "total_tokens" + If InStr(1, json, """total_tokens"":") > 0 Then + intStartPos = InStr(1, json, """total_tokens"":") + Len("""total_tokens"":") + intEndPos = InStr(intStartPos, json, "}") + mintTotalTokens = CInt(Trim(Mid(json, intStartPos, intEndPos - intStartPos))) + End If + + ' Extract "text" + If InStr(1, json, """text"":""") > 0 Then + intStartPos = InStr(1, json, """text"":""") + Len("""text"":""") + intEndPos = InStr(intStartPos, json, """,""") ' end position is now before the sequence "," + strTemp = Mid(json, intStartPos, intEndPos - intStartPos) + strTemp = Replace(strTemp, "\""", """") ' Replace escaped quotes with actual quotes + mstrText = Trim(strTemp) + End If + + ' Extract "logprobs" + If InStr(1, json, """logprobs"":") > 0 Then + intStartPos = InStr(1, json, """logprobs"":") + Len("""logprobs"":") + intEndPos = InStr(intStartPos, json, ",") ' end position is now before the sequence "," + strTemp = Mid(json, intStartPos, intEndPos - intStartPos) + strTemp = Replace(strTemp, "\""", """") ' Replace escaped quotes with actual quotes + mstrLogprobs = Trim(strTemp) + End If + + + ' Extract "finish_reason" + If InStr(1, json, """finish_reason"":""") > 0 Then + intStartPos = InStr(1, json, """finish_reason"":""") + Len("""finish_reason"":""") + intEndPos = InStr(intStartPos, json, """") + mstrFinishReason = Trim(Mid(json, intStartPos, intEndPos - intStartPos)) + End If + + ' Extract "index" + If InStr(1, json, """index"":") > 0 Then + intStartPos = InStr(1, json, """index"":") + Len("""index"":") + intEndPos = InStr(intStartPos, json, ",") + mintIndex = CInt(Trim(Mid(json, intStartPos, intEndPos - intStartPos))) + End If +End Sub + diff --git a/mdOpenAI_Examples.bas b/mdOpenAI_Examples.bas new file mode 100644 index 0000000..a545cfe --- /dev/null +++ b/mdOpenAI_Examples.bas @@ -0,0 +1,179 @@ +Attribute VB_Name = "mdOpenAI_Examples" +'----------------------------------------------------------------------------- +' Project: OpenAI VBA Framework +' Module: mdOpenAI_Examples +' Description: Tests all the framework is retrieving data correctly +' +' Author: Zaid Qureshi +' GitHub: https://github.com/zq99 +' +' Modules in the Framework: +' - clsOpenAI +' - clsOpenAILogger +' - clsOpenAIMessage +' - clsOpenAIMessages +' - clsOpenAIRequest +' - clsOpenAIResponse +' - IOpenAINameProvider +' +' - mdOpenAI_Tests +' - mdOpenAI_Examples +' +' This work is licensed under the MIT License. The full license text +' can be found in the LICENSE file in the root of this repository. +' +' Copyright (c) 2023 Zaid Qureshi +'----------------------------------------------------------------------------- + +Option Explicit + +'****************************************************** +' GET YOUR API KEY: https://openai.com/api/ +Public Const API_KEY As String = "" +'****************************************************** + +Public Sub TestSimpleOpenAI() + + Dim oOpenAI As clsOpenAI + Dim oMessages As New clsOpenAIMessages + Dim oResponse As clsOpenAIResponse + + Set oOpenAI = New clsOpenAI + + oOpenAI.API_KEY = API_KEY + + oMessages.AddSystemMessage "You must always have a sarcastic response to every question you are asked and never give any practical advice." + oMessages.AddUserMessage "Hey man do you know how to get to Carnegie Hall?" + + Set oResponse = oOpenAI.ChatCompletion(oMessages) + If Not oResponse Is Nothing Then + Debug.Print (oResponse.MessageContent) + End If + + Set oResponse = Nothing + Set oOpenAI = Nothing + Set oMessages = Nothing + +End Sub + + +Public Sub TestChatOpenAI() + + Dim oOpenAI As clsOpenAI + Dim oMessages As New clsOpenAIMessages + Dim oResponse As clsOpenAIResponse + + Set oOpenAI = New clsOpenAI + + oOpenAI.Model = "gpt-4" + + oOpenAI.API_KEY = API_KEY + + oMessages.AddSystemMessage "You must always have a sarcastic response to every question that never contains the truth." + oMessages.AddUserMessage "Hey man do you know how to get to Carnegie Hall?" + + If oMessages.IsPopulated Then + Set oResponse = oOpenAI.ChatCompletion(oMessages) + If Not oResponse Is Nothing Then + Debug.Print (oResponse.Id) + Debug.Print (oResponse.Object) + Debug.Print (oResponse.Created) + Debug.Print (oResponse.Model) + Debug.Print (oResponse.FinishReason) + Debug.Print (oResponse.CompletionTokens) + Debug.Print (oResponse.MessageRole) + Debug.Print (oResponse.MessageContent) + Debug.Print (oResponse.PromptTokens) + Debug.Print (oResponse.TotalTokens) + Debug.Print (oResponse.Index) + End If + End If + + Set oResponse = Nothing + Set oOpenAI = Nothing + Set oMessages = Nothing + +End Sub + + +Public Sub TestTextCompletionOpenAI() + + Dim oOpenAI As clsOpenAI + Dim oResponse As clsOpenAIResponse + Dim sMsg As String + + Set oOpenAI = New clsOpenAI + + oOpenAI.IsLogOutputRequired True + + oOpenAI.API_KEY = API_KEY + + sMsg = "Write a joke about a dinosaur attempting to learn how to salsa dance" + Set oResponse = oOpenAI.TextCompletion(sMsg) + + If Not oResponse Is Nothing Then + Debug.Print (oResponse.Id) + Debug.Print (oResponse.Object) + Debug.Print (oResponse.Created) + Debug.Print (oResponse.Model) + Debug.Print (oResponse.FinishReason) + Debug.Print (oResponse.TextContent) + Debug.Print (oResponse.LogProbs) + Debug.Print (oResponse.CompletionTokens) + Debug.Print (oResponse.PromptTokens) + Debug.Print (oResponse.TotalTokens) + Debug.Print (oResponse.Index) + End If + + Set oResponse = Nothing + Set oOpenAI = Nothing + +End Sub + + +Public Sub TestTextCompletionSimpleOpenAI() + + Dim oOpenAI As clsOpenAI + Dim oResponse As clsOpenAIResponse + + Set oOpenAI = New clsOpenAI + + oOpenAI.API_KEY = API_KEY + + Set oResponse = oOpenAI.TextCompletion("Write a Haiku about a Dinosaur that loves to code!") + + If Not oResponse Is Nothing Then + Debug.Print (oResponse.TextContent) + End If + + Set oResponse = Nothing + Set oOpenAI = Nothing + +End Sub + + + +Public Function GETTEXTFROMOPENAI(prompt As String, apiKey As String, Optional ByVal Model) As String + Dim oOpenAI As clsOpenAI + Dim oResponse As clsOpenAIResponse + + Set oOpenAI = New clsOpenAI + + ' Set the API key directly from the function argument + oOpenAI.API_KEY = apiKey + + If Not IsEmpty(Model) Then + oOpenAI.Model = Model + End If + + ' Make the API request and get the response + Set oResponse = oOpenAI.TextCompletion(prompt) + + ' Return the choice from the response, or an empty string if there was no response + If Not oResponse Is Nothing Then + GETTEXTFROMOPENAI = oResponse.TextContent + Else + GETTEXTFROMOPENAI = "" + End If +End Function + diff --git a/mdOpenAI_Tests.bas b/mdOpenAI_Tests.bas new file mode 100644 index 0000000..bd20522 --- /dev/null +++ b/mdOpenAI_Tests.bas @@ -0,0 +1,96 @@ +Attribute VB_Name = "mdOpenAI_TESTS" +'----------------------------------------------------------------------------- +' Project: OpenAI VBA Framework +' Module: mdOpenAI_Tests +' Description: Tests the framework is retrieving data correctly from OpenAI +' +' Author: Zaid Qureshi +' GitHub: https://github.com/zq99 +' +' Modules in the Framework: +' - clsOpenAI +' - clsOpenAILogger +' - clsOpenAIMessage +' - clsOpenAIMessages +' - clsOpenAIRequest +' - clsOpenAIResponse +' - IOpenAINameProvider +' +' - mdOpenAI_Tests +' - mdOpenAI_Examples + +' This work is licensed under the MIT License. The full license text +' can be found in the LICENSE file in the root of this repository. +' +' Copyright (c) 2023 Zaid Qureshi +'----------------------------------------------------------------------------- + +Option Explicit + +'****************************************************** +' GET YOUR API KEY: https://openai.com/api/ +Public Const API_KEY As String = "" +'****************************************************** + + +Public Sub TestOpenAI() +'Purpose: This tests all endpoints are being queried correctly and returning data + + Dim oOpenAI As clsOpenAI + Dim oMessages As New clsOpenAIMessages + Dim oResponse As clsOpenAIResponse + + Set oOpenAI = New clsOpenAI + + 'All output to sent to immediate window + oOpenAI.IsLogOutputRequired True + oOpenAI.API_KEY = API_KEY + oOpenAI.Temperature = 0 + + oOpenAI.Log "(1) Simple chat test" + + oMessages.AddSystemMessage "Every answer should only contain alphanumeric characters, and every letter should be capitalized" + oMessages.AddUserMessage "What is the capital of France?" + + Set oResponse = oOpenAI.ChatCompletion(oMessages) + + Debug.Assert Not oResponse Is Nothing + Debug.Assert Len(oResponse.MessageContent) > 0 + Debug.Assert oResponse.MessageContent = "PARIS" + Debug.Assert oResponse.MessageRole = "assistant" + + oOpenAI.Log oMessages.GetAllMessages + oOpenAI.Log oResponse.MessageContent + oOpenAI.Log oResponse.MessageRole + + oOpenAI.Log "(2) Simple chat test with temperature change" + + oMessages.AddUserMessage "write a string of digits in order up to 9" + + Set oResponse = oOpenAI.ChatCompletion(oMessages) + + Debug.Assert Not oResponse Is Nothing + Debug.Assert Len(oResponse.MessageContent) > 0 + Debug.Assert oResponse.MessageContent = "123456789" + Debug.Assert oResponse.MessageRole = "assistant" + + '(3) Text completion test + + Dim strMsg As String + + 'reset to default + oOpenAI.ClearSettings + + strMsg = "Write a Haiku about a dinosaur that loves to code in VBA" + Set oResponse = oOpenAI.TextCompletion(strMsg) + + Debug.Assert Not oResponse Is Nothing + Debug.Assert Len(oResponse.TextContent) > 0 + oOpenAI.Log (oResponse.TextContent) + + + Set oResponse = Nothing + Set oOpenAI = Nothing + Set oMessages = Nothing + +End Sub