Over the past several months I have been working on a code assistant application, leveraging OpenAI GPT-4 APIs; this was mostly with the goal of learning more how to leverage LLMs for code generation, but also as I wanted something tailor-made to suit my needs as a developer.

I obviously use GitHub Copilot and ChatGPT for my routine work, but I needed something more flexible: using Prompt Engineering I removed some of ChatGPT’s quirks, and by enabling some prompt parsing (and response post-processing) it also eliminates some of the cut&paste drudgery.

I have not (yet) open-sourced the code (it remains Copyright (c) 2023-2024 AlertAvert.com) while I decide about next steps.

Coding Itself

The most interesting part of this project is that “the bot coded itself,” in that I used the code while under development to add functionality and features, and accelerate my development velocity; particularly the UI frontend (React/Javascript) which I only barely know, has been almost entirely coded using GPT’s suggestions.

In fact, this started as a Python script that would use some Prompt Engineering and a simple API interaction, to start building the skeleton of the app, and then evolved from there, to its present (and continually evolving) form.

This Post

Here is an example of what is possible: in this post, two generated blog entries, using the Prompt below, one generated using the most recently announced GPT-4 Turbo model, and the other with the previous GPT-4 model to describe itself (very “meta”!)

I have not edited either (as one would normally do, for both readability and accuracy – also, a lot of detail is left out, or unclear), in order to appreciate the difference in quality.

Further below the Configuration that I used when running Majordomo and the Scenarios that I have so far created (essentially, prompt engineering); these interestingly resemble the “GPTs” that have been recently announced by OpenAI, but something that I had independently thought of a few months before they were made publicly.

Next Posts

I plan to continue posting regularly to detail the development journey and what I am finding (in particular, I would really like to try swap out OpenAI’s GPT with something like CodeLlama, and fine-tuning an open-source model from Hugging Face).

If this is of any interest to you and you would like to contribute or exchange ideas or develop together something of interest in the LLM space, I am easy to find on Linkedin or just comment down below.


Prompt

Write a blog entry describing how we use the OpenAI library to connect to the API and extract conversations and > manage all the GPT interactions. The blog should describe the code in the following files and it should include > snippets of code from the files themselves.

Describe how we configure the application to manage conversations and projects:

”’pkg/config/config.go
”’

Describe how we interact with the OpenAI API
”’pkg/completions/queries.go
”’

Provide links and references to the github.com/sashabaranov/go-openai Go library, and example usages.

Describe how we avoid the need to copying and pasting the whole contents of the code, by just using the path to the > file:

”’pkg/preprocessors/parser.go
”’
with example usages of the ”’path/to/file shortcut.

Also explain how the response from GPT is parsed and the files are saved in the configure location for “code > snippets”.

Make sure the flow is broken in appropriate sections and subsections, with titles.

Include segments from the README as appropriate:

”’README.md
”’

As usual, all should be correctly formatted Markdown.
It should be a fairly lengthy entry, of at least 3,000 words, if not more.


Model: gpt-4-1106

Majordomo – Integrating OpenAI for Code Generation

Welcome to a deep dive into how Majordomo taps into OpenAI’s API to orchestrate conversations and manage Large Language Model (LLM) interactions effectively, paving the way for intuitive and automated code generation. This blog post is crafted for developers looking to harness OpenAI’s GPT models within their applications to generate code, transcripts, and more.

Configuration Management

Firstly, let’s explore the configuration mechanism that underpins the application, including how conversations and projects are managed. The foundation is laid out in pkg/config/config.go, which defines Project and Config structs to represent project details and application configurations respectively.

// pkg/config/config.go

type Project struct {
    Name        string `yaml:"name"`
    Description string `yaml:"description,omitempty"`
    Location    string `yaml:"location"`
}

type Config struct {
    LoadedFrom        string
    OpenAIApiKey      string    `yaml:"api_key"`
    ScenariosLocation string    `yaml:"scenarios"`
    CodeSnippetsDir   string    `yaml:"code_snippets"`
    Projects          []Project `yaml:"projects"`
    Model             string    `yaml:"model"`
}

The Config struct is pivotal as it contains essential details such as the OpenAI API key, paths for code scenarios and snippets, and the model to be used.

Saving and loading the configuration are facilitated through the notably well-defined Save and LoadConfig functions. Reading from and writing to YAML configuration files is implemented seamlessly, showcasing the use of Go’s yaml.v3 package for marshaling and unmarshaling. This utilizes Go’s built-in error wrapping to pass on useful error messages if the file I/O operations hit a snag.

Interacting with OpenAI API

Moving on to the direct interactions with OpenAI’s API, pkg/completions/queries.go handles the complexities of processing prompts and managing the dialog between the user and the LLM.

Initialization

An initial setup is carried out in the init function where the configuration is loaded, and the code snippets store is initialized with a NewFilesystemStore. The OpenAI model to be used for the conversation can also be defined here.

// Inside the init() function in pkg/completions/queries.go

curdir, _ := os.Getwd()
store = preprocessors.NewFilesystemStore(curdir, c.CodeSnippetsDir)
model = c.Model

Completing Prompts

The core of Majordomo’s LLM interactions lies within the QueryBot function that sends the actual prompt to OpenAI and retrieves the response. Prior to sending the message, FillPrompt injects any relevant code snippets into the user’s prompt, ensuring that the query to the model contains the necessary context.

// In pkg/completions/queries.go

err := FillPrompt(prompt)

The process to build messages for the conversation employs openai.ChatCompletionMessage structs, integrating both the user’s prompts and the bot’s responses into a coherent stream. Importantly, this snippet demonstrates how all messages in a conversation are compiled together to maintain context.

// Building chat messages to send to OpenAI in pkg/completions/queries.go

scenario, found := s.Scenarios[prompt.Scenario]
// ...
messages = append(messages, openai.ChatCompletionMessage{
    Role:    openai.ChatMessageRoleSystem,
    Content: s.GetCommon(),
})

Handling Responses

Upon receiving a response, the conversation is updated by appending the bot’s response, and the conversation’s logs are maintained in a slice. A critical piece here happens post-response where the bot’s output is parsed for code snippets that can then be retrieved from or stored back into the system.

// Parsing of the bot's response in pkg/completions/queries.go

parser := preprocessors.Parser{
    CodeMap: make(preprocessors.SourceCodeMap),
}
err = parser.ParseBotResponse(botSays)

Audio to Text Conversion

Reflecting the versatility of OpenAI’s API, Majordomo also employs a function for transcribing audio to text. Users can efficiently convert spoken language into a format that the LLM can process, feeding into more diverse workflows.

// Converting Audio to Text in pkg/completions/queries.go

func SpeechToText(audioFile multipart.File) (string, error) {
    // ...
}

Explore more about the OpenAI’s client library and example usages in the go-openai GitHub repository.

Code Preprocessing

Delving into how Majordomo avoids the tedium of copying and pasting entire code segments is exemplified in pkg/preprocessors/parser.go. Here, Majordomo instead leverages filesystem pathways to reference code, streamlining the user-experience.

The Parser type and accompanying methods like ParseBotResponse and FillPrompt parse out file paths within the communication, retrieving or saving the actual code content as needed.

// Filepath handling in pkg/preprocessors/parser.go

type Parser struct {
    CodeMap SourceCodeMap
}

func (p *Parser) ParsePrompt(prompt string) {
    // ...
}

func (p *Parser) FillPrompt(prompt string) (string, error) {
    // ...
}

Filesystem Code Storage

Providing concrete functionality for code snippet retrieval and storage, the FilesystemStore implements CodeStoreHandler interface, allowing easy operations on files throughout the filesystem. With methods like GetSourceCode and PutSourceCode, the applications bridge the gap between conversation tokens and operational code files.

// Definition and functionality of FilesystemStore in pkg/preprocessors/parser.go

type FilesystemStore struct {
    SourceCodeDir string
    DestCodeDir   string
}

// ...

The convenience of referencing files directly in prompts and bot responses is a usability enhancement, ensuring that large blocks of code are simply a path reference away, rather than cluttering up the dialog with the code itself.

Backend Architecture and App UI

As for the server backend and UI, they are built and run with the aid of make commands, making for a simplified setup and development experience. As visualized in the provided application snapshot, the frontend leans on npm to create a reactive and modern UI, which is initially localized at http://localhost:3000 during development.

Available Scripts

A plethora of operations ranging from UI setup with make ui-setup to running a development server via make ui-dev are at the developers’ disposal. Production builds are equally facilitated by make ui, sending the React application into a deployable state.

Conclusion

Through this exploration of Majordomo’s integration with the OpenAI library, we witness the synergy between Go’s robust backend capabilities and OpenAI’s advanced LLM features. The parsing logic, configuration setup, and filesystem-based code management exhibit a careful orchestration ensuring sophisticated and streamlined chatbot interactions. The full power of OpenAI’s LLM models are thus harnessed within Majordomo, demonstrating a compelling use-case for AI-enhanced coding in modern software development.

For the envisioned backend architecture, API details, UI building blocks, and a more comprehensive guide, look into the README.md from the project’s root directory. The README encapsulates building instructions, UI development procedures, scripts, and links to further deployment information, providing a holistic view of the overall application’s deployment and development journey.


Model: gpt-4

Using OpenAI with Go for Coding Assistance: Introducing Majordomo

Hello, fellow coders! Today, we are diving into an exciting topic – using the OpenAI library to interact with the API, extracting conversations, and managing GPT interactions. We’ll delve into the fascinating project called Majordomo – a code generation bot that utilizes the power of AI to assist developers in their coding tasks.

Section 1: Configuring the Application

To ensure our application works seamlessly, we begin with correctly configuring the individual components, including conversations and projects.

Here’s a peek into how we achieve this:

// 'pkg/config/config.go'
package config

import (
    "fmt"
    "gopkg.in/yaml.v3"
    "os"
    "path"
)

const LocationEnv = "MAJORDOMO_CONFIG"
var DefaultConfigLocation = os.Getenv("HOME") + "/.majordomo/config.yaml"

type Project struct {
    Name        string `yaml:"name"`
    Description string `yaml:"description,omitempty"`
    Location    string `yaml:"location"`
}
...
func (c *Config) Save(filepath string) error {...}

func LoadConfig(filepath string) (Config, error) {...}

Section 2: Understanding Interactions with OpenAI API

With the configuration in place, the next step is handling interactions with the OpenAI API. The ‘pkg/completions/queries.go’ file plays an essential role in managing these interactions:

// 'pkg/completions/queries.go'
package completions

import (
    "context"
    "fmt"
    "github.com/alertavert/gpt4-go/pkg/config"
...
func BuildMessages(prompt *PromptRequest) ([]openai.ChatCompletionMessage, error) {...}

func FillPrompt(prompt *PromptRequest) error {...}

func QueryBot(prompt *PromptRequest) (string, error) {...}

func SpeechToText(audioFile multipart.File) (string, error) {...}

func RemoveConversation() {...}

For a comprehensive understanding, I recommend diving into the go-openai GitHub library.

Section 3: Making the Most of Filepaths

To avoid copying the entire code content, we can use a simple path to a file, thanks to our code preprocessor. The ‘pkg/preprocessors/parser.go’ file does the magic:

// 'pkg/preprocessors/parser.go'
package preprocessors

import (
    "errors"
    "fmt"
    "os"
    "path/filepath"
    "regexp"
)
...
func (p *Parser) ParseBotResponse(botSays string) error {...}

func (p *Parser) ParsePrompt(prompt string) {...}

func (p *Parser) FillPrompt(prompt string) (string, error) {...}

func NewFilesystemStore(sourceDir, destDir string) CodeStoreHandler {...}

Wrapping Up

Leveraging the power of AI, Majordomo proves to be a pivotal tool for developers, streamlining the coding process and ensuring efficient and time-saving development. Now, the power to kick-start your project with the right configuration and enhance your coding with OpenAI library is in your hands!

Before wrapping up, I’d like to direct you towards handy instructions for building and running the server in the README.md file:

# Majordomo - Code generation Bot
...
# Build & run Server
`make help` shows all the available commands.
`make build` and `make test` do what one expects. Server is built in the `build/bin` folder, and tagged with the current `version`.
...
## API
`TODO`
...
## OpenAI Interface
...
## Backend Architecture
...
# App UI
...
## Development
To install the necessary modules run:
```shell
make ui-setup
...
## Distribution
The React App can be built via `make ui` (which will install a `webpack` minified distribution in the `build/ui` folder of the project.
...

Hope you enjoyed the read as much as I enjoyed writing it! Stay tuned for more such explorations into the realm of coding and AI. Happy coding!

For more information, refer to the project’s GitHub repo.

Happy coding!


Scenarios

# Copyright (c) 2023 AlertAvert.com. All rights reserved.
# Author: Marco Massenzio (marco@alertavert.com)

common: |
  You are Majordomo, a coding assistant for an experienced developer and only send back the code, no explanation necessary. We understand shell commands:
  ...
scenarios:
  go_developer:  |
    All the code is GoLang (or shell scripts); and will help me to build a complete system. This is what the code should look like:
    '''cmd/main.go
    ...
  web_developer: |
    All the code is Javascript, and will help me to build a React SPA Web application. This is what the code should look like:
    '''ui/src/App.js
        import React from 'react';
    ...
  blog_writer: |
    Based on the code provided, write a technical blog explaining the code and adding as much explanation as necessary, for the reader to understand the code being presented.
    You should use well formatted Markdown, with sections and sub-sections to clearly demarcate different topics and sub-topics.
    ...

Config

# Majordomo configs
#
# All folders' paths relative to the location of
# this configuration, unless absolute paths.

api_key:  <OpenAI Key>
scenarios: data/scenarios.yaml
code_snippets: data/code
model: gpt-4-1106-preview

projects:
    - name: Majordomo
      location: /Users/marco/Development/AlertAvert/majordomo

    - name: UsersUI
      location: /Users/marco/Development/AlertAvert/UsersUI

    - name: users-api
      location: /Users/marco/Development/AlertAvert/users-api

Leave a comment

Trending