Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Start hacking on some code for dealing with long files. #340

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions app/pkg/agent/edit_file_prompt.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Edit File Snippet Task

You will be given a snippet of a file. You will be asked to make changes to that snippet.
Output the modified snippet and nothing else.
Do not make any changes that you aren't explicitly asked to make.
Do not add additional markdown formatting such as putting the snippet in a code block.

<changes>
{{.Changes}}
</changes>

<snippet>
{{.Text}}
</snippet>
62 changes: 62 additions & 0 deletions app/pkg/agent/long_files.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package agent

import (
"bufio"
_ "embed"
"github.com/jlewi/monogo/helpers"

Check failure on line 6 in app/pkg/agent/long_files.go

View workflow job for this annotation

GitHub Actions / golang test & build

File is not `goimports`-ed (goimports)
"os"
"strings"
"text/template"
)

//go:embed edit_file_prompt.tmpl
var editPrompt string

var (
editTemplate = template.Must(template.New("prompt").Parse(editPrompt))
)

type editPromptInput struct {
Changes string
Text string
}

// FileSnippet holds the start and end lines and the actual text of the segment.
type FileSnippet struct {
StartLine int
EndLine int
Text string
}

// ReadFileSegment reads the file at filePath and returns the lines between startLine and endLine (inclusive) as a FileSegment.
func ReadFileSegment(filePath string, startLine, endLine int) (FileSnippet, error) {
file, err := os.Open(filePath)
if err != nil {
return FileSnippet{}, err
}
defer helpers.DeferIgnoreError(file.Close)

scanner := bufio.NewScanner(file)
var textBuilder strings.Builder
currentLine := 0

for scanner.Scan() {
currentLine++
if currentLine >= startLine && currentLine <= endLine {
textBuilder.WriteString(scanner.Text() + "\n")
}
if currentLine > endLine {
break
}
}

if err := scanner.Err(); err != nil {
return FileSnippet{}, err
}

return FileSnippet{
StartLine: startLine,
EndLine: endLine,
Text: textBuilder.String(),
}, nil
}
118 changes: 118 additions & 0 deletions app/pkg/agent/long_files_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package agent

import (
"context"
"fmt"

Check failure on line 5 in app/pkg/agent/long_files_test.go

View workflow job for this annotation

GitHub Actions / golang test & build

File is not `goimports`-ed (goimports)
"github.com/jlewi/foyle/app/pkg/config"
"github.com/jlewi/foyle/app/pkg/oai"
"github.com/pkg/errors"
"github.com/sashabaranov/go-openai"
"github.com/sergi/go-diff/diffmatchpatch"
"go.uber.org/zap"
"os"
"strings"
"testing"
)

func run() error {
inputFile := "/Users/jlewi/tmp/clusters.tf"

if err := config.InitViper(nil); err != nil {
return errors.Wrapf(err, "Error initializing viper")
}

lConfig := zap.NewDevelopmentConfig()
l, err := lConfig.Build()
if err != nil {
return errors.Wrapf(err, "Error creating logger")
}

zap.ReplaceGlobals(l)

cfg := config.GetConfig()

client, err := oai.NewClient(*cfg)

if err != nil {
return errors.Wrapf(err, "Error creating OpenAI client")
}

segment, err := ReadFileSegment(inputFile, 5570, 5594)
if err != nil {
return errors.Wrapf(err, "Error reading file segment")
}

var sb strings.Builder
input := editPromptInput{
Changes: "Change the cluster u35 to use the region spaincentral. Add the label owner=foo to the cluster",
Text: segment.Text,
}

if err := editTemplate.Execute(&sb, input); err != nil {
return errors.Wrapf(err, "Failed to execute prompt template")
}

messages := []openai.ChatCompletionMessage{
{Role: openai.ChatMessageRoleUser,
Content: sb.String(),
},
}
request := openai.ChatCompletionRequest{
Model: openai.GPT4oMini,
Messages: messages,
MaxTokens: 8000,
Temperature: temperature,
}

resp, err := client.CreateChatCompletion(context.Background(), request)
if err != nil {
return errors.Wrapf(err, "Error calling OpenAI")
}

if len(resp.Choices) != 1 {
return errors.Errorf("Expected 1 choice but got %v", len(resp.Choices))
}

output := resp.Choices[0].Message.Content
fmt.Printf("ChatGPT Output:\n%v\n", output)

// Compute a diff between the original text and the output
dmp := diffmatchpatch.New()

//diffs := dmp.DiffMain(segment.Text, output, false)
//
//// Create a patch
//patches := dmp.PatchMake(segment.Text, diffs)

patches := dmp.PatchMake(segment.Text, output)

//// Convert the diff to unified format
//patch, err := diff.ToUnified(ud, unifiedDiff)
//if err != nil {
// fmt.Println("Error generating unified diff:", err)
// return
//}
for _, p := range patches {
fmt.Printf("Patch:\n%v\n", p.String())
}

// TODO(jeremy): How do we adjust the patch to be relative to the original file?
// Maybe we should just replace the original lines with the output lines.
// if we wanted to we could then compute a diff between the modified and aligned file.
// So our patch format should be a start and end line that will be replaced with our fragment.
// This is human readable.

return nil
}

func Test_LongFiles(t *testing.T) {
// The purpose of this test is to experiment with different patterns for modifying very long (e.g. 8K-10K lines)
// files. Such as Terraform files
if os.Getenv("GITHUB_ACTIONS") != "" {
t.Skipf("Test is skipped in GitHub actions")
}

if err := run(); err != nil {
t.Fatalf("Error running test: %v", err)
}
}
Loading