How to Create Advanced OpenAI GPTs For Your Website

Introduction

Welcome to this online tutorial, where we explore the exciting world of building Advanced Custom GPTs and integrating them into any website. This tutorial is designed for those who have developed GPTs using the OpenAI’s Builder or a simple Custom GPT and are now ready to take a step further.

We will dive deep into the process of creating and embedding your GPTs onto websites, providing a comprehensive understanding of how OpenAI has paved the way for integrating GPTs into various aspects of our digital lives. This opportunity is ripe for exploration, and this tutorial aims to position you at the forefront of this technological advancement.

This tutorial follows YouTube tutorial by Liam Ottley.

My GPTs vs Assistant API

It’s important to begin with an essential distinction between GPTs built on the ChatGPT website and those developed using OpenAI’s Assistant API. The former are often seen as ‘consumer toys’—intriguing, yet limited in scope. They offer a basic user interface for creating and showcasing GPTs but lack the capability for programmatic operation. In contrast, the Assistant API opens up a more robust and business-oriented avenue, allowing for the programmatic creation and operation of advanced GPTs.

This tutorial will guide you through creating an assistant using the Assistant API, a process streamlined for ease, without the need for extensive coding knowledge. You’ll learn how to imbue your assistant with knowledge and functionality, and then integrate it into a website through a chat widget interface. This approach not only enhances user engagement but also leverages the full potential of GPTs in real-world applications.

Overview

In this tutorial, we delve into the creation of a custom knowledge chatbot, tailored specifically for integration into a Solar Energy company’s website. This chatbot is engineered to serve as a virtual assistant, offering customers detailed insights on solar products and potential energy savings. It achieves this by considering unique factors such as the customer’s address and roof size. Additionally, the chatbot is designed with a lead generation feature, effectively collecting and storing customer contact information in a CRM system. This dual functionality not only enhances customer engagement by providing relevant information but also aids in lead acquisition for the business.

The tutorial comprises two main components:

  1. Developing a Replit to manage conversations with the Assistant.
  2. Linking this system to a frontend platform, Voiceflow.

The Replit setup involves preparing a knowledge base and establishing API endpoints for effective conversation management. You’ll learn how to create an Assistant, save its unique ID for subsequent uses, and thereby minimize API-related expenses.

After establishing the backend, we move to configuring the chatbot frontend in Voiceflow. Once operational in Voiceflow, we’ll demonstrate how to incorporate it into a website. This step entails embedding specific code into your website’s script section to create an interactive chat bubble widget.

Replit setup (backend)

The backend setup in this guide involves four key files: main.py, functions.py, prompts.py and knowledge.docx. The code, inspired by Liam Ottley’s work, involves starting a Flask app, initializing an OpenAI client, and creating or loading an assistant. The guide discusses how to start conversation threads and generate responses using API calls.

A link to Replit project.

Main.py

import json
import os
import time
from flask import Flask, request, jsonify
import openai
from openai import OpenAI
import functions

# Check OpenAI version compatibility
from packaging import version

required_version = version.parse("1.1.1")
current_version = version.parse(openai.__version__)
OPENAI_API_KEY = os.environ['OPENAI_API_KEY']
if current_version < required_version:
  raise ValueError(
      f"Error: OpenAI version {openai.__version__} is less than the required version 1.1.1"
  )
else:
  print("OpenAI version is compatible.")

# Create Flask app
app = Flask(__name__)

# Initialize OpenAI client
client = OpenAI(api_key=OPENAI_API_KEY)

# Create or load assistant
assistant_id = functions.create_assistant(
    client)  # this function comes from "functions.py"


# Start conversation thread
@app.route('/start', methods=['GET'])
def start_conversation():
  print("Starting a new conversation...")
  thread = client.beta.threads.create()
  print(f"New thread created with ID: {thread.id}")
  return jsonify({"thread_id": thread.id})


# Generate response
@app.route('/chat', methods=['POST'])
def chat():
  data = request.json
  thread_id = data.get('thread_id')
  user_input = data.get('message', '')

  if not thread_id:
    print("Error: Missing thread_id")
    return jsonify({"error": "Missing thread_id"}), 400

  print(f"Received message: {user_input} for thread ID: {thread_id}")

  # Add the user's message to the thread
  client.beta.threads.messages.create(thread_id=thread_id,
                                      role="user",
                                      content=user_input)

  # Run the Assistant
  run = client.beta.threads.runs.create(thread_id=thread_id,
                                        assistant_id=assistant_id)

  # Check if the Run requires action (function call)
  while True:
    run_status = client.beta.threads.runs.retrieve(thread_id=thread_id,
                                                   run_id=run.id)
    # print(f"Run status: {run_status.status}")
    if run_status.status == 'completed':
      break
    elif run_status.status == 'requires_action':
      # Handle the function call
      for tool_call in run_status.required_action.submit_tool_outputs.tool_calls:
        if tool_call.function.name == "solar_panel_calculations":
          # Process solar panel calculations
          arguments = json.loads(tool_call.function.arguments)
          output = functions.solar_panel_calculations(
              arguments["address"], arguments["monthly_bill"])
          client.beta.threads.runs.submit_tool_outputs(thread_id=thread_id,
                                                       run_id=run.id,
                                                       tool_outputs=[{
                                                           "tool_call_id":
                                                           tool_call.id,
                                                           "output":
                                                           json.dumps(output)
                                                       }])
        elif tool_call.function.name == "create_lead":
          # Process lead creation
          arguments = json.loads(tool_call.function.arguments)
          output = functions.create_lead(arguments["name"], arguments["phone"],
                                         arguments["address"])
          client.beta.threads.runs.submit_tool_outputs(thread_id=thread_id,
                                                       run_id=run.id,
                                                       tool_outputs=[{
                                                           "tool_call_id":
                                                           tool_call.id,
                                                           "output":
                                                           json.dumps(output)
                                                       }])
      time.sleep(1)  # Wait for a second before checking again

  # Retrieve and return the latest message from the assistant
  messages = client.beta.threads.messages.list(thread_id=thread_id)
  response = messages.data[0].content[0].text.value

  print(f"Assistant response: {response}")
  return jsonify({"response": response})


if __name__ == '__main__':
  app.run(host='0.0.0.0', port=8080)

Functions.py

import json
import requests
import os
from openai import OpenAI
from prompts import formatter_prompt, assistant_instructions

OPENAI_API_KEY = os.environ['OPENAI_API_KEY']
GOOGLE_CLOUD_API_KEY = os.environ['GOOGLE_CLOUD_API_KEY']
AIRTABLE_API_KEY = os.environ['AIRTABLE_API_KEY']

# Init OpenAI Client
client = OpenAI(api_key=OPENAI_API_KEY)


# Add lead to Airtable
def create_lead(name, phone, address):
  url = "https://api.airtable.com/v0/appM1yx0NobvowCAg/Leads"  # Change this to your Airtable API URL
  headers = {
      "Authorization": f"Bearer {AIRTABLE_API_KEY}",
      "Content-Type": "application/json"
  }
  data = {
      "records": [{
          "fields": {
              "Name": name,
              "Phone": phone,
              "Address": address
          }
      }]
  }
  response = requests.post(url, headers=headers, json=data)
  if response.status_code == 200:
    print("Lead created successfully.")
    return response.json()
  else:
    print(f"Failed to create lead: {response.text}")


# Get coordidinates from address via Geocoding API
def get_coordinates(address):
  geocoding_url = f"https://maps.googleapis.com/maps/api/geocode/json?address={address}&key={GOOGLE_CLOUD_API_KEY}"
  response = requests.get(geocoding_url)
  if response.status_code == 200:
    location = response.json().get('results')[0].get('geometry').get(
        'location')
    print(f"Coordinates for {address}: {location}")
    return location['lat'], location['lng']
  else:
    print(f"Error getting coordinates: {response.text}")


# Get solar data for coordinate from Solar API
def get_solar_data(lat, lng):
  solar_api_url = f"https://solar.googleapis.com/v1/buildingInsights:findClosest?location.latitude={lat}&location.longitude={lng}&requiredQuality=HIGH&key={GOOGLE_CLOUD_API_KEY}"
  response = requests.get(solar_api_url)
  if response.status_code == 200:
    print("Solar data retrieved successfully.")
    return response.json()
  else:
    print(f"Error getting solar data: {response.text}")


# Extract financial data LIST from solar data
def extract_financial_analyses(solar_data):
  try:
    return solar_data.get('solarPotential', {}).get('financialAnalyses', [])
  except KeyError as e:
    print(f"Data extraction error: {e}")


# Get financial data for the address
def get_financial_data_for_address(address):
  lat, lng = get_coordinates(address)
  if not lat or not lng:
    return {"error": "Could not get coordinates for the address provided."}
  return extract_financial_analyses(get_solar_data(lat, lng))


# Match user's bill to scenario in the financial data
def find_closest_financial_analysis(user_bill, financial_analyses):
  closest_match = None
  smallest_difference = float('inf')
  for analysis in financial_analyses:
    bill_amount = int(analysis.get('monthlyBill', {}).get('units', 0))
    difference = abs(bill_amount - user_bill)
    if difference < smallest_difference:
      smallest_difference = difference
      closest_match = analysis
  return closest_match


# Use GPT completion to extract most relevant data from financial analysis
def simplify_financial_data(data):
  try:

    data_str = json.dumps(data, indent=2)

    # Getting formatter prompt from "prompts.py" file
    system_prompt = formatter_prompt

    # Replace 'client' with your actual OpenAI client initialization.
    completion = client.chat.completions.create(
        model="gpt-4-1106-preview",
        messages=[
            {
                "role": "system",
                "content":
                system_prompt  # Getting prompt from "prompts.py" file
            },
            {
                "role":
                "user",
                "content":
                f"Here is some data, parse and format it exactly as shown in the example: {data_str}"
            }
        ],
        temperature=0)

    simplified_data = json.loads(completion.choices[0].message.content)
    print("Simplified Data:", simplified_data)
    return simplified_data

  except Exception as e:
    print("Error simplifying data:", e)
    return None


# Main calculation function for solar data output
def solar_panel_calculations(address, monthly_bill):
  print(
      f"Calculating solar panel potential for {address} with bill amount {monthly_bill}."
  )
  financial_analyses = get_financial_data_for_address(address)
  if "error" in financial_analyses:
    print(financial_analyses["error"])
    return financial_analyses
  closest_financial_analysis = find_closest_financial_analysis(
      int(monthly_bill), financial_analyses)
  if closest_financial_analysis:
    return simplify_financial_data(closest_financial_analysis)
  else:
    print("No suitable financial analysis found.")
    return {
        "error": "No suitable financial analysis found for the given bill."
    }


# Create or load assistant
def create_assistant(client):
  assistant_file_path = 'assistant.json'

  # If there is an assistant.json file already, then load that assistant
  if os.path.exists(assistant_file_path):
    with open(assistant_file_path, 'r') as file:
      assistant_data = json.load(file)
      assistant_id = assistant_data['assistant_id']
      print("Loaded existing assistant ID.")
  else:
    # If no assistant.json is present, create a new assistant using the below specifications

    # To change the knowledge document, modifiy the file name below to match your document
    # If you want to add multiple files, paste this function into ChatGPT and ask for it to add support for multiple files
    file = client.files.create(file=open("knowledge.docx", "rb"),
                               purpose='assistants')

    assistant = client.beta.assistants.create(
        # Getting assistant prompt from "prompts.py" file, edit on left panel if you want to change the prompt
        instructions=assistant_instructions,
        model="gpt-4-1106-preview",
        tools=[
            {
                "type": "retrieval"  # This adds the knowledge base as a tool
            },
            {
                "type": "function",  # This adds the solar calculator as a tool
                "function": {
                    "name": "solar_panel_calculations",
                    "description":
                    "Calculate solar potential based on a given address and monthly electricity bill in USD.",
                    "parameters": {
                        "type": "object",
                        "properties": {
                            "address": {
                                "type":
                                "string",
                                "description":
                                "Address for calculating solar potential."
                            },
                            "monthly_bill": {
                                "type":
                                "integer",
                                "description":
                                "Monthly electricity bill in USD for savings estimation."
                            }
                        },
                        "required": ["address", "monthly_bill"]
                    }
                }
            },
            {
                "type": "function",  # This adds the lead capture as a tool
                "function": {
                    "name": "create_lead",
                    "description":
                    "Capture lead details and save to Airtable.",
                    "parameters": {
                        "type": "object",
                        "properties": {
                            "name": {
                                "type": "string",
                                "description": "Name of the lead."
                            },
                            "phone": {
                                "type": "string",
                                "description": "Phone number of the lead."
                            },
                            "address": {
                                "type": "string",
                                "description": "Address of the lead."
                            }
                        },
                        "required": ["name", "phone", "address"]
                    }
                }
            }
        ],
        file_ids=[file.id])

    # Create a new assistant.json file to load on future runs
    with open(assistant_file_path, 'w') as file:
      json.dump({'assistant_id': assistant.id}, file)
      print("Created a new assistant and saved the ID.")

    assistant_id = assistant.id

  return assistant_id

Prompts.py

formatter_prompt = """
You are a helpful data parsing assistant. You are given JSON with financial data 
and you filter it down to only a set of keys we want. This is the exact structure we need:

{
  "monthlyBill": "200",
  "federalIncentive": "6815",
  "stateIncentive": "4092",
  "utilityIncentive": "3802",
  "totalCostWithoutSolar": "59520",
  "solarCoveragePercentage": 99.33029,
  "leasingOption": {
    "annualCost": "1539",
    "firstYearSavings": "745",
    "twentyYearSavings": "23155",
    "presentValueTwentyYear": "14991"
  },
  "cashPurchaseOption": {
    "outOfPocketCost": "30016",
    "paybackYears": 7.75,
    "firstYearSavings": "2285",
    "twentyYearSavings": "53955",
    "presentValueTwentyYear": "17358"
  },
  "financedPurchaseOption": {
    "annualLoanPayment": "1539",
    "firstYearSavings": "745",
    "twentyYearSavings": "23155",
    "presentValueTwentyYear": "14991"
  }
}

If you cannot find a value for the key, then use "None Found". Please double check before using this fallback.
Process ALL the input data provided by the user and output our desired JSON format exactly, ready to be converted into valid JSON with Python. 
Ensure every value for every key is included, particularly for each of the incentives.
"""

assistant_instructions = """
    The assistant has been programmed to help customers of RayHarvest Solar to learn more about solar for their single-family home and to calculate estimated savings for them if they were to install solar on their home. The assistant is placed on the RayHarvest Solar website for customers to learn more about solar and the company's offerings.

    A document has been provided with information on solar for single-family homes which can be used to answer the customer's questions. When using this information in responses, the assistant keeps answers short and relevant to the user's query.
    Additionally, the assistant can perform solar savings calculations based on a given address and their monthly electricity bill in USD. When outputting their solar savings and key info, markdown formatting should be used for bolding key figures.
    After the assistant has provided the user with their solar caluclations, they should ask for their name and phone number so that one of the team can get in contact with them about installing solar for their home.

    With this information, the assistant can add the lead to the company CRM via the create_lead function, also pulling in the user's address that was mentioned prior. This should provide the name, email, and address of the customer to the create_lead function.
"""

Knowledge.docx

The document contains information on RayHarvest Solar’s products and offers as well as general information on Solar panels.

API keys

The following API keys should be provided in Secrets section in Repl project:

  • OpenAI (for text understanding and text generation)
  • Google (to calculate savings due to solar energy at specific address)
  • Airtable (to store user’s contact details)

OpenAI API key

OpenAI API key can be obtainend by navigating to platform.openai.com/api-keys. You can use an existing API key or create a new one. Paste the key to Replit.

Google API key

Google API key can be obtainend by navigating to console.cloud.google.com. In the console create a new project. Go to ‘View all APIs’. In the API Library Search bar type ‘geocoding’ and select ‘Geocoding API’. Click ‘Enable’. Copy API key and paste it to Replit.

In the ‘API library’ find and enable ‘Solar API’.

Airtable API

In your Airtable account go to ‘Developer hub’ and create a new token with Scopes ‘data.records:read’ and ‘data.records:write’ and Access to the table. Copy the token and paste it to Replit.

Airtable base url

In your Airtable account go to ‘Developer hub’. Navigate to ‘Developer docs’. There choose ‘Web API’ and then select your table. In the ‘LEADS TABLE’ find ‘Create Leads records’. Copy the similar looking url after curl command: https://api.airtable.com/v0/appsXXpZXCffkO6z4/Leads. Paste the link to ‘create_lead’ function in functions.py in Replit project.

Run Repl

When you click Run, the app will start. It will create a new assitant and store assitant ID to assistant.json file. The backend is ready.

Click new tab. Copy URL address. You will need it later.

Voiceflow setup (frontend)

In the frontend setup, the tutorial walks you through configuring Voiceflow to manage user interactions. This includes API call setups for starting conversations, capturing user input, and generating responses. Special focus is given to formatting the assistant’s responses for clarity. The credit for the Voceflow template below goes to Liam Ottley. Voiceflow template can be found here.

Create Thread

Here you should replace https://YOUR_REPLIT_URL by the link you copied after running Repl. Don’t forget to add /start in the end.

If API call is successful chat flow proceeds to ‘Capture User Input’ otherwise it raises an error.

Capture User Input

Here the chatbot greets the user and stores a request to the variable.

Generate Response

Here you should replace https://YOUR_REPLIT_URL by the link you copied after running Repl. Don’t forget to add /chat in the end.

If API call is successful chat flow proceeds to ‘Send Response’ otherwise it raises an error.

Send Response

The final message is displayed to the user.

Testing

Run the chat bot and ask it a few questions. It pools answers from the knoledge base.

Back at Replit you can see output log generated by the backend. It might be helpful in case of debugging.

Adding chatbot to a website

In Voiceflow click Publish at the top right corner. Then click Embed Widget.

On the next page you will have a possibility to customise the appearence of you chat widget. Once you are done with customisation you should copy JavaScript code and paste it to your website. Here is a tutorial on how it can be done in case of WordPress.

Conclusion

This tutorial provides a step-by-step guide to integrating a custom GPT chatbot into any website, leveraging the capabilities of OpenAI’s Assistant API. Whether you’re a developer looking to enhance your website’s interactivity or exploring new AI-driven solutions, this guide lays the groundwork for your endeavors.

Comments

Leave a Reply