logo
Published on

Using Google's Bard LLM for Rapid Web Development

Authors
drawing

An active area of current research is evaluating how human preferences align with the preferences generated by Large Language Models (LLM) like OpenAI's GPT-4. For small scale experiments, in order to gather human preference data it may be necessary to build web based applications that easily allow participants to view and record their preferences, which can be used either as training data for a model, or as test data for evaluating how well a LLM is able to match the preferences recorded by the human participants.

The world of web development is always quickly evolving, and AI Researchers who don't normally stay up to date on the latest web development trends may find it difficult to quickly produce an application that uses modern standards and design without needing to read up on all the newest features available. Although frameworks like Streamlit and Gradio are excellent for loading and demonstrating the capabilities of the LLMs that researchers are training, they quickly become unwieldy when they are used for gathering interaction data.

I began my career in software as a web developer, so although I have a decent grasp of the basic concepts of HTML, CSS, and Javascript, I haven't stayed up to date on the latest features of frameworks like React and Flask for a number of years, and was looking for ways to quickly create an application that would help have human participants compare two baking recipes and tell me which one they like better. In this blog post I will walk through a quick and simple way to create a web application to do this, using the Google Bard LLM to help guide me.

Framework Selection

Since I have some experience in web development, I had a general idea of what I wanted. The backend would run in python: this is because I wanted to store the output data in a format that could be easily processed and reviewed as a pandas DataFrame. This makes it much simpler to turn the data collected into a huggingface dataset for later evaluation and training. I'll save it as a CSV file to make it easy to read for this demo.

Given my experience with React as a javascript framework, I decided to use React for my web frontend.

At the end of the project, I will use React to generate my website ("index.html" and other assets), and then the flask server will serve that index.html file at the root location / and will also host the other routes for saving and updating the collected human preference data. If this was a large scale experiment I would also utilize a database for storing the information, however, since my application would only need to be used by less than 50 reviewers, I chose to have a file on the server store and update the data, removing the need for a database. This of course makes it difficult to scale up the server, but for my purposes a single server will provide more than enough compute resources.

Application Features

Frontend Webpage (React)

The goal of the application is to collect feedback from a handful of users regarding which of two recipes they prefer. In my experiment, I stored a collection of recipes created by two generative models, and display the two recipes side by side and ask the user to select whether they like one over the other, or they like them both the same. The recipe will be for the same thing (for example, pumpkin pie), but if one recipe says (Add 10 grams of hot sauce) while the other says (Insert 5 grams of chocolate chips), I would expect the user to select the recipe that mentions chocolate chips, since a pumpkin pie recipe with hot sauce in it would likely not be tasty.

When a user initially loads the page, I will have a dropdown page where they select their name. This is a simple "login" type page. Since it's only for a small number of users, I won't bother to add any sort of password authentication.

After they select their name, the application will then start to show them the recipe side-by-side comparisons, with three buttons at the top that allow them to select whether they like one recipe better than the other.

Backend Server (Flask)

On the backend, we will need to create the following routes:

  • A route to serve the built React application (At the root address) (GET)
  • A route to provide the list of reviewer names (GET)
  • A route to receive a reviewer name and provide them a new recipe to review (POST)
  • A route to receive a reviewer's review and store it into the dataframe (POST)

Generating the application code using the Google Bard LLM

For this task, I chose to use Google Bard. ChatGPT should also work, but I arbitrarily decided to try out Bard this time. We could try out the same in ChatGPT and see how that compares, but this time around I'll focus on just using Bard.

Bard

I directly pasted the above "Framework Selection" and "Application Selection" sections of text into Bard, and received this response after I added the additional prompt:

Please walk me through the creation of the above application, step by step. The application should use the latest version of Flask as well as the latest version of React. Please show me how to create all of the needed javascript and python files.

Bard Output

To create a Flask server with a React frontend, you will need to:

Install the required dependencies:

pip install flask
npm install

Create a new directory for your project and navigate to it:

mkdir flask-react-app
cd flask-react-app

Create a new Flask app:

from flask import Flask

app = Flask(__name__)

Create a new React project:

npx create-react-app frontend

Create a route to serve the built React application:

@app.route("/")
def serve_react_app():
    return send_from_directory("frontend/build", "index.html")

Create a route to provide the list of reviewer names:

@app.route("/reviewers", methods=["GET"])
def get_reviewers():
    reviewers = []
    # Load the list of reviewer names from a file or database
    for reviewer in reviewers:
        reviewers.append(reviewer)

    return jsonify(reviewers)

Create a route to receive a reviewer name and provide them a new recipe to review:

@app.route("/review/<reviewer_name>", methods=["POST"])
def get_new_recipe(reviewer_name):
    # Load the next recipe to review for the given reviewer
    recipe = {}

    return jsonify(recipe)

Create a route to receive a reviewer's review and store it into the dataframe:

@app.route("/review/<reviewer_name>/submit", methods=["POST"])
def submit_review(reviewer_name):
    review = request.get_json()

    # Store the review into the dataframe
    # ...

    return jsonify({"success": True})

Create a file to store the dataframe:

import pandas as pd

df = pd.DataFrame()

# Save the dataframe to a file
def save_dataframe():
    df.to_csv("preferences.csv", index=False)

# Load the dataframe from a file
def load_dataframe():
    global df
    df = pd.read_csv("preferences.csv")

# Start the Flask server
if __name__ == "__main__":
    app.run(debug=True)

Create a React component to display the recipe side-by-side comparisons and allow users to select their preference:

import React, { useState } from 'react'

const RecipeComparison = ({ recipe1, recipe2 }) => {
  const [selectedRecipe, setSelectedRecipe] = useState(null)

  const onRecipeSelected = (recipe) => {
    setSelectedRecipe(recipe)
  }

  return (
    <div>
      <div>Recipe 1: {recipe1}</div>
      <div>Recipe 2: {recipe2}</div>
      <div>
        <button onClick={() => onRecipeSelected(recipe1)}>Recipe 1</button>
        <button onClick={() => onRecipeSelected(recipe2)}>Recipe 2</button>
      </div>
    </div>
  )
}

export default RecipeComparison

Update the App component to use the RecipeComparison component and fetch the next recipe to review from the backend:

import React, { useState, useEffect } from "react";
import RecipeComparison from "./RecipeComparison";

const App = () => {
  const [reviewerName, setReviewerName] = useState("");
  const [currentRecipe, setCurrentRecipe] = useState(null);

  useEffect(() => {
    fetch(`/review/<span class="math-inline">\{reviewerName\}\`\)
\.then\(\(response\) \=\> response\.json\(\)\)
\.then\(\(recipe\) \=\> setCurrentRecipe\(recipe\)\);
\}, \[reviewerName\]\);
const onSubmitReview \= \(\) \=\> \{
fetch\(\`/review/</span>{reviewerName}/submit`, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.

Fixing the Bard Output

This is where we can all breathe a little sigh of relief that an LLM is not going to replace all software developers in the next year. Although it is going to save us a lot of time from typing, using this code requires knowledge of python and javascript and React and Flask, and knowing which parts of the code are wrong and/or omitted.

I could probably get Bard to correct itself by giving it some directed feedback about what it output, but since doing that already sort of requires that I know what should be done, it's faster just to do it myself than trying to get Bard to output the exactly correct thing. The point of using Bard is to speed up my workflow, not to test whether or not it can output exactly the correct thing.

Project Setup

Bard gave us a few bash commands for setting up the project. Here are all the commands we need to run to set up this project (I'll add comments next to the lines that I inserted and Bard left out). This project assumes that you already have python3 and node.js installed. It doesn't mention it, but you should create a python virtual environment to isolate your dependencies (I use conda)

conda create -n webapp python=3.10 -y # MY CODE
conda activate webapp # MY CODE
pip install flask pandas # MY CODE ADDING PANDAS
# WRONG: Bard said to run the following, but this shouldn't yet be run: "npm install"
mkdir flask-react-app
cd flask-react-app
touch app.py # MY CODE (this is where the flask python code will go)
npx create-react-app frontend
cd frontend # MY CODE
npm install axios # MY CODE: axios is a lib to make http requests easier. Bard said to do npm install earlier, but it was wrong, it needs to be done here
touch src/RecipeComparison.js # MY CODE

Backend

Now that the project is all set up, we put together all the code that Bard gave us. For the backend code, this is the full python file app.py. I added some comments for the the parts I had to edit/add

import os
from flask import Flask, jsonify, request, send_from_directory  # Imports were missing

app = Flask(
    __name__, static_folder="frontend/build"
)  # Bard missed the static_folder setting

import pandas as pd

df = pd.DataFrame()


# Bard missed the path routing that is needed to serve from the frontend folder
@app.route("/", defaults={"path": ""})
@app.route("/<path:path>")
def serve(path):
    if path != "" and os.path.exists(app.static_folder + "/" + path):
        return send_from_directory(app.static_folder, path)
    else:
        return send_from_directory("frontend/build", "index.html")


@app.route("/reviewers", methods=["GET"])
def get_reviewers():
    reviewers = []
    # Load the list of reviewer names from a file or database
    reviewers = (
        df["reviewer_name"].unique().tolist()
    )  # Bard required me to add this, which is fine

    return jsonify(reviewers)


@app.route("/review/<reviewer_name>", methods=["POST"])
def get_new_recipe(reviewer_name):
    # Load the next recipe to review for the given reviewer
    # I had to add all of this logic because I didn't tell Bard
    #   what the structure of the DataFrame was.
    recipe = {}
    author_df = df[df["reviewer_name"] == reviewer_name]
    not_yet_rated_df = author_df[author_df["rating"].isnull()]
    if not_yet_rated_df.shape[0] == 0:
        return jsonify(recipe)
    item = not_yet_rated_df.iloc[0]
    recipe["recipe_a"] = item["recipe_a"]
    recipe["recipe_b"] = item["recipe_b"]
    recipe["recipe_id"] = str(item["recipe_id"])
    return jsonify(recipe)


@app.route("/review/<reviewer_name>/submit", methods=["POST"])
def submit_review(reviewer_name):
    review = request.get_json()
    recipe_id = int(review["recipe_id"])

    # Store the review into the dataframe
    # ...
    item = df[df["reviewer_name"] == reviewer_name][df["recipe_id"] == recipe_id]
    assert item.shape[0] == 1
    idx = item.index[0]
    print(review["rating"])

    df.at[idx, "rating"] = review["rating"]
    save_dataframe()
    return jsonify({"success": True})


# Save the dataframe to a file
def save_dataframe():
    # if preferences.csv does not exist, create it
    df.to_csv("preferences.csv", index=False)


# Load the dataframe from a file
def load_dataframe():
    global df
    # A simple file to create if the csv file doesn't already exist (useful for testing)
    if not os.path.isfile("preferences.csv"):
        with open("preferences.csv", "w") as f:
            f.write("reviewer_name,recipe_a,recipe_b,recipe_id,rating\n")
            f.write(
                "nathan,'Recipe for Pumpkin Pie: 1 gram chocolate chips' ,'A terrible recipe for pumpkin pie: 1 gram hot sauce',1,recipe_a\n"
            )
            f.write(
                "nathan,'Recipe for Pumpkin Pie: 1 gram chocolate chips' ,'A terrible recipe for pumpkin pie: 1 gram hot sauce',2,\n"
            )
    df = pd.read_csv("preferences.csv")


# Start the Flask server
if __name__ == "__main__":
    load_dataframe()  # Bard didn't have this, so the global df never would have been set
    app.run(debug=True, port=8000)


I had to add a few lines to fill in some of the logic gaps, but overall, not bad!

Frontend

Now lets fill in the gaps on the frontend.

First, the App.js file. Bard left out most of the logic for making a call to the backend as well as how to load the RecipeComponent itself. We'll pass into the RecipeComponent information about the recipes, as well as a callback that will allow the component to request the next recipe.

import React, { useState, useEffect } from 'react'
import axios from 'axios'
import RecipeComparison from './RecipeComparison'

function App() {
  const [recipeData, setRecipeData] = useState(null)
  const [name, setName] = useState('None')
  const [recipeId, setRecipeId] = useState(0)
  const [reviewers, setReviewers] = useState(['None'])

  const getNextRecipe = (name) => {
    axios
      .post(`/review/${name}`) // Flask route to get hypothesis
      .then((response) => {
        setRecipeData(response.data)
        setRecipeId(response.data['recipe_id'])
      })
      .catch((error) => {
        console.error('Error fetching hypothesis:', error)
      })
  }

  useEffect(() => {
    if (name !== 'None') {
      getNextRecipe(name)
    }
  }, [name])

  useEffect(() => {
    axios
      .get('/reviewers') // Flask route to get hypothesis
      .then((response) => {
        response.data.unshift('None')
        setReviewers(response.data)
      })
      .catch((error) => {
        console.error('Error fetching hypothesis:', error)
      })
  }, [])
  return (
    <div className="App">
      <div>
        <label htmlFor="name">Select your name:</label>
        <select
          value={name}
          onChange={(e) => {
            setName(e.target.value)
          }}
        >
          {reviewers.map((name) => (
            <option value={name}>{name}</option>
          ))}
        </select>
      </div>
      {recipeData !== null && name !== 'None' && (
        <RecipeComparison
          recipeA={recipeData['recipe_a']}
          recipeB={recipeData['recipe_b']}
          recipeId={recipeId}
          nextRecipe={getNextRecipe}
          reviewerName={name}
        />
      )}
    </div>
  )
}

export default App

Now, filling in the RecipeComparison.js file. I'll need to add all the logic to actually make the calls to the backend because that was omitted.

import React from 'react'
import axios from 'axios'

const RecipeComparison = ({ recipeA, recipeB, recipeId, reviewerName, nextRecipe }) => {
  const onRecipeSelected = (choice) => {
    axios
      .post(`/review/${reviewerName}/submit`, { recipe_id: recipeId, rating: choice })
      .then(() => {
        nextRecipe()
      })
      .catch((error) => {
        console.error('Error marking:', error)
      })
  }

  return (
    <div>
      <div>Recipe 1: {recipeA}</div>
      <div>Recipe 2: {recipeB}</div>
      <div>
        <button onClick={() => onRecipeSelected('recipe_a')}>Recipe A</button>
        <button onClick={() => onRecipeSelected('recipe_b')}>Recipe B</button>
      </div>
    </div>
  )
}

export default RecipeComparison

So overall I would say that the Bard output for the frontend was relatively incomplete and left much to be desired. If I had done a few more back-and-forths with Bard I could probably have gotten a better frontend code to use as a starting point. For instance, the code that Bard generated for me had no CSS settings. I would have liked the Recipes to Be displayed side-by-side vertically instead of horizontally, but since I didn't specify that in the initial prompt, it makes sense that Bard didn't generate that CSS on its own.

Run It

This following bash command sequence will build the React frontend (placing it in the frontend/build folder), and will run the Python Flask server, on port 8000 of your local PC. It can then be accessed by going to http://127.0.0.1:8000 in your browser window.

cd frontend && npm run build & cd .. && python app.py

Conclusion

Although Bard made my job of starting the project much easier, it was far from a blind copy-paste effort. Although some of the content that Bard omitted it had a comment about what code needed to be inserted by us, there were lots of places where Bard simply failed to output the correct output. It missed things as simple as python imports, but also more complex things like how to load a React build folder and index.html file. Overall, I'll continue to use Bard and other LLMs for high level coding, but the details of getting a project to work require (or greatly benefit from) human expertise.

Cover art generated in Canva with prompt "An artificial intelligence robot rapidly creating code"