Skip to content

Commit

Permalink
Introduce chat mode, refactor API (#32)
Browse files Browse the repository at this point in the history
This commit introduces major changes to the library:

1. Chat mode is introduced, allowing conversing with the API in chat
   models, asking the API to modify previously generated code. After
   a response is returned, users can hit the "c" key to send further
   instructions to the API.

2. The `--full` flag is removed in favor of `--readme-file`. This
   allows storing both the code, and the full output, as separate
   files, rather than one or the other. The `--readme-file` is
   optional, the full output will not be saved if not provided.

3. The command line flags are simplified. The `-s` flag is removed.
   The `-q` flag now enabled non-interactive mode, but will still
   honor the `--output-file` and `--readme-file` flags.

4. More models are now supported. The output from the `list-models`
   command is now returned in tabular form, with more information.
   Every model now has its own maximum tokens value. The
   code-davinci-002 model is removed as it is about to be removed
   by OpenAI as well.

5. The library now includes separate methods for completion models
   and chat models. The former use the `Complete` method, the latter
   use the `Chat` method, with one or more `Send` method calls on
   the resulting object. The previous `GenerateCode` method still
   exists as a simple wrapper around these two.

6. The signature of the `GenerateCode` method is changed. It now
   requires the model to use (whereas previously it was a Client
   attribute). Instead of simply returning the generated code
   and an error, it returns a Response object that contains the
   generated code, the full output, the API key used, and the
   number of tokens utilized by the request.

7. Requests to the API now send a temperature value of 0.2 for
   more deterministic responses.

8. A `version` command is added to the CLI that prints the
   `aiac` version.

9. The README file is updated with a new demo and updated
   instructions, including how to use aiac as a library.

Due to the backwards-incompatible changes in this commit, the
major version of the project is bumped to 3.
  • Loading branch information
ido50 committed Mar 22, 2023
1 parent c457361 commit 94a936e
Show file tree
Hide file tree
Showing 13 changed files with 659 additions and 348 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
aiac
.env*
106 changes: 84 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,17 @@ Generator.
* [Use Cases and Example Prompts](#use-cases-and-example-prompts)
* [Generate IaC](#generate-iac)
* [Generate Configuration Files](#generate-configuration-files)
* [Generate CICD Pipelines](#generate-cicd-pipelines)
* [Generate CI/CD Pipelines](#generate-cicd-pipelines)
* [Generate Policy as Code](#generate-policy-as-code)
* [Generate Utilities](#generate-utilities)
* [Command Line Builder](#command-line-builder)
* [Query Builder](#query-builder)
* [Instructions](#instructions)
* [Installation](#installation)
* [Usage](#usage)
* [Choosing a Different Model](#choosing-a-different-model)
* [Command Line](#command-line)
* [Via Docker](#via-docker)
* [As a Library](#as-a-library)
* [Example Output](#example-output)
* [Troubleshooting](#troubleshooting)
* [Support Channels](#support-channels)
Expand Down Expand Up @@ -50,7 +52,7 @@ different models.
- `aiac get dockerfile for a secured nginx`
- `aiac get k8s manifest for a mongodb deployment`

### Generate CICD Pipelines
### Generate CI/CD Pipelines

- `aiac get jenkins pipeline for building nodejs`
- `aiac get github action that plans and applies terraform and sends a slack notification`
Expand Down Expand Up @@ -79,7 +81,7 @@ different models.

You will need to provide an OpenAI API key in order for `aiac` to work. Refer to
[OpenAI's pricing model](https://openai.com/pricing?trk=public_post-text) for
more information. As of this writing, you get $5 in free credits upon signin up,
more information. As of this writing, you get $5 in free credits upon signing up,
but generally speaking, this is a paid API.

### Installation
Expand All @@ -94,7 +96,7 @@ Using `docker`:

Using `go install`:

go install github.com/gofireflyio/aiac/v2@latest
go install github.com/gofireflyio/aiac/v3@latest

Alternatively, clone the repository and build from source:

Expand All @@ -107,30 +109,90 @@ Alternatively, clone the repository and build from source:
2. Click “Create new secret key” and copy it.
3. Provide the API key via the `OPENAI_API_KEY` environment variable or via the `--api-key` command line flag.

By default, aiac prints the extracted code to standard output and asks if it
should save the code, regenerate it, or modify the prompt:
#### Command Line

By default, aiac prints the extracted code to standard output and opens an
interactive shell that allows retrying requests, enabling chat mode (for chat
models), saving output to files, and more:

aiac get terraform for AWS EC2

To store the resulting code to a file:
You can ask it to also store the code to a specific file with a flag:

aiac -o aws_ec2.tf get terraform for AWS EC2
aiac get terraform for eks --output-file=eks.tf

To run using `docker`:
You can use a flag to save the complete Markdown output as well:

docker run \
-it \
-e OPENAI_API_KEY=[PUT YOUR KEY HERE] \
ghcr.io/gofireflyio/aiac get terraform for ec2
aiac get terraform for eks --output-file=eks.tf --readme-file=eks.md

You can use aiac in non-interactive mode, simply printing the generated code
to standard output, and optionally saving it to files with the above flags,
by providing the `-q` or `--quiet` flag:

aiac get terraform for eks -q

By default, aiac uses the gpt-3.5-turbo chat model, but other models are
supported. You can list all supported models:

If you want to receive and/or store the complete Markdown output from OpenAI,
including explanations (if any), use the `--full` flag.
aiac list-models

### Choosing a Different Model
To generate code with a different model, provide the `--model` flag:

Use the `--model` flag to select a different model than the default (currently
"gpt-3.5-turbo"). Not all OpenAI models are supported, use `aiac list-models`
to get a list of all supported models.
aiac get terraform for eks --model="text-davinci-003"

#### Via Docker

All the same instructions apply, except you execute a `docker` image:

docker run \
-it \
-e OPENAI_API_KEY=[PUT YOUR KEY HERE] \
ghcr.io/gofireflyio/aiac get terraform for ec2

#### As a Library

You can use aiac as a library:

```go
package main

import (
"context"
"os"

"github.com/gofireflyio/aiac/v3/libaiac"
)

func main() {
client := libaiac.NewClient(os.Getenv("OPENAI_API_KEY"))
ctx := context.TODO()

// use the model-agnostic wrapper
res, err := client.GenerateCode(
ctx,
libaiac.ModelTextDaVinci3,
"generate terraform for ec2",
)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed generating code: %s\n", err)
os.Exit(1)
}

fmt.Fprintln(os.Stdout, res.Code)

// use the completion API (for completion-only models)
res, err = client.Complete(
ctx,
libaiac.ModelTextDaVinci3,
"generate terraform for ec2",
)

// converse via a chat model
chat := client.Chat(libaiac.ModelGPT35Turbo)
res, err = chat.Send(ctx, "generate terraform for eks")
res, err = chat.Send(ctx, "region must be eu-central-1")
}
```

## Example Output

Expand Down Expand Up @@ -175,8 +237,8 @@ to encounter are coming from this API. Some common errors you may encounter are:
- "[tokens] Rate limit reached...":
The OpenAI API employs rate limiting as [described here](https://platform.openai.com/docs/guides/rate-limits/request-increase). `aiac` only performs
individual requests and cannot workaround or prevent these rate limits. If
you are using `aiac` in an application, you will have to implement throttling
yourself.
you are using `aiac` in programmatically, you will have to implement throttling
yourself. See [here](https://github.com/openai/openai-cookbook/blob/main/examples/How_to_handle_rate_limits.ipynb) for tips.

## Support Channels

Expand Down
Binary file removed authentication.jpg
Binary file not shown.
Binary file modified demo.gif
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added demo.mp4
Binary file not shown.
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
module github.com/gofireflyio/aiac/v2
module github.com/gofireflyio/aiac/v3

go 1.19

Expand All @@ -14,6 +14,7 @@ require (
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect
github.com/mattn/go-colorable v0.1.2 // indirect
github.com/mattn/go-isatty v0.0.16 // indirect
github.com/rodaine/table v1.1.0 // indirect
go.uber.org/atomic v1.10.0 // indirect
go.uber.org/multierr v1.8.0 // indirect
go.uber.org/zap v1.24.0 // indirect
Expand Down
5 changes: 5 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
Expand Down Expand Up @@ -149,20 +150,24 @@ github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVc
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/rodaine/table v1.1.0 h1:/fUlCSdjamMY8VifdQRIu3VWZXYLY7QHFkVorS8NTr4=
github.com/rodaine/table v1.1.0/go.mod h1:Qu3q5wi1jTQD6B6HsP6szie/S4w1QUQ8pq22pz9iL8g=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/spf13/afero v1.9.2 h1:j49Hj62F0n+DaZ1dDCvhABaPNSGNkt32oRFxI33IEMw=
github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
Expand Down
2 changes: 1 addition & 1 deletion goreleaser.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ builds:
ldflags:
- -s -w
- "-extldflags '-static'"
- -X main.version={{.Version}}
- -X 'github.com/gofireflyio/aiac/v3/libaiac.Version={{.Version}}'
env:
- CGO_ENABLED=0
goos:
Expand Down
99 changes: 99 additions & 0 deletions libaiac/chat.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package libaiac

import (
"context"
"fmt"
"strings"
)

// Conversation is a struct used to converse with an OpenAI chat model. It
// maintains all messages sent/received in order to maintain context just like
// using ChatGPT.
type Conversation struct {
client *Client
model Model
messages []message
}

type message struct {
Role string `json:"role"`
Content string `json:"content"`
}

type chatResponse struct {
Choices []struct {
Message message `json:"message"`
Index int64 `json:"index"`
FinishReason string `json:"finish_reason"`
} `json:"choices"`
Usage struct {
TotalTokens int64 `json:"total_tokens"`
} `json:"usage"`
}

// Chat initiates a conversation with an OpenAI chat model. A conversation
// maintains context, allowing to send further instructions to modify the output
// from previous requests, just like using the ChatGPT website.
func (client *Client) Chat(model Model) *Conversation {
if model.Type != ModelTypeChat {
return nil
}

return &Conversation{
client: client,
model: model,
}
}

// Send sends the provided message to the API and returns a Response object.
// To maintain context, all previous messages (whether from you to the API or
// vice-versa) are sent as well, allowing you to ask the API to modify the
// code it already generated.
func (conv *Conversation) Send(ctx context.Context, prompt string) (
res Response,
err error,
) {
var answer chatResponse

conv.messages = append(conv.messages, message{
Role: "user",
Content: prompt,
})

err = conv.client.NewRequest("POST", "/chat/completions").
JSONBody(map[string]interface{}{
"model": conv.model.Name,
"messages": conv.messages,
"temperature": 0.2,
}).
Into(&answer).
RunContext(ctx)
if err != nil {
return res, fmt.Errorf("failed sending prompt: %w", err)
}

if len(answer.Choices) == 0 {
return res, ErrNoResults
}

if answer.Choices[0].FinishReason != "stop" {
return res, fmt.Errorf(
"%w: %s",
ErrResultTruncated,
answer.Choices[0].FinishReason,
)
}

conv.messages = append(conv.messages, answer.Choices[0].Message)

res.FullOutput = strings.TrimSpace(answer.Choices[0].Message.Content)
res.APIKeyUsed = conv.client.apiKey
res.TokensUsed = answer.Usage.TotalTokens

var ok bool
if res.Code, ok = ExtractCode(res.FullOutput); !ok {
res.Code = res.FullOutput
}

return res, nil
}
64 changes: 64 additions & 0 deletions libaiac/completion.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package libaiac

import (
"context"
"fmt"
"strings"
)

type completionResponse struct {
Choices []struct {
Text string `json:"text"`
Index int64 `json:"index"`
FinishReason string `json:"finish_reason"`
} `json:"choices"`
Usage struct {
TotalTokens int64 `json:"total_tokens"`
} `json:"usage"`
}

// Complete sends a request to OpenAI's Completions API using the provided model
// and prompt, and returns the response
func (client *Client) Complete(
ctx context.Context,
model Model,
prompt string,
) (res Response, err error) {
var answer completionResponse

err = client.NewRequest("POST", "/completions").
JSONBody(map[string]interface{}{
"model": model.Name,
"prompt": prompt,
"max_tokens": model.MaxTokens - len(prompt),
"temperature": 0.2,
}).
Into(&answer).
RunContext(ctx)
if err != nil {
return res, fmt.Errorf("failed sending prompt: %w", err)
}

if len(answer.Choices) == 0 {
return res, ErrNoResults
}

if answer.Choices[0].FinishReason != "stop" {
return res, fmt.Errorf(
"%w: %s",
ErrResultTruncated,
answer.Choices[0].FinishReason,
)
}

res.FullOutput = strings.TrimSpace(answer.Choices[0].Text)
res.APIKeyUsed = client.apiKey
res.TokensUsed = answer.Usage.TotalTokens

var ok bool
if res.Code, ok = ExtractCode(res.FullOutput); !ok {
res.Code = res.FullOutput
}

return res, nil
}

0 comments on commit 94a936e

Please sign in to comment.