Replace modify_file with individual functions

- file_insert_lines
- file_replace_lines
- file_remove_lines
This commit is contained in:
Matt Low 2023-11-26 17:33:33 +00:00
parent d1c11b41d8
commit 8e262c4839

View File

@ -70,25 +70,11 @@ Result is returned as JSON in the following format:
"message": "success", // if successful, or a different message indicating failure "message": "success", // if successful, or a different message indicating failure
}` }`
MODIFY_FILE_DESCRIPTION = `Perform complex line-based modifications to a file. FILE_INSERT_LINES = `Insert lines into a file.`
Line ranges are inclusive. If 'start_line' is specified but 'end_line' is not, FILE_REPLACE_LINES = `Replace an (inclusive) range of lines within a file.`
'end_line' gets set to the last line of the file.
To replace or remove a single line, *set start_line and end_line to the same value* FILE_REMOVE_LINES = `Remove an (inclusive) range of lines from a file.`
Examples:
* Insert the lines "hello<new line>world" at line 10, preserving other content:
{"path": "myfile", "operation": "insert_before", "start_line": 10, "content": "hello\nworld"}
* Remove lines 45 up to and including 54:
{"path": "myfile", "operation": "remove", "start_line": 45, "end_line": 54}
* Replace content from line 10 to 25:
{"path": "myfile", "operation": "replace", "start_line": 10, "end_line": 25, "content": "i\nwas\nhere"}
* Replace contents of entire the file:
{"path": "myfile", "operation": "replace", "start_line": 0, "content": "i\nwas\nhere"}`
) )
var AvailableTools = map[string]AvailableTool{ var AvailableTools = map[string]AvailableTool{
@ -184,10 +170,10 @@ var AvailableTools = map[string]AvailableTool{
return WriteFile(path, content), nil return WriteFile(path, content), nil
}, },
}, },
"modify_file": { "file_insert_lines": {
Tool: openai.Tool{Type: "function", Function: openai.FunctionDefinition{ Tool: openai.Tool{Type: "function", Function: openai.FunctionDefinition{
Name: "modify_file", Name: "file_insert_lines",
Description: MODIFY_FILE_DESCRIPTION, Description: FILE_INSERT_LINES,
Parameters: FunctionParameters{ Parameters: FunctionParameters{
Type: "object", Type: "object",
Properties: map[string]FunctionParameter{ Properties: map[string]FunctionParameter{
@ -195,24 +181,16 @@ var AvailableTools = map[string]AvailableTool{
Type: "string", Type: "string",
Description: "Path of the file to be modified, relative to the current working directory.", Description: "Path of the file to be modified, relative to the current working directory.",
}, },
"operation": {
Type: "string",
Description: `The the type of modification to make to the file. One of: insert_before, remove, replace`,
},
"start_line": { "start_line": {
Type: "integer", Type: "integer",
Description: `(Optional) Where to start making a modification (insert, remove, and replace).`, Description: `Which line to begin inserting lines at (existing line will be moved to after content).`,
},
"end_line": {
Type: "integer",
Description: `(Optional) Where to stop making a modification (remove or replace, end of file if omitted).`,
}, },
"content": { "content": {
Type: "string", Type: "string",
Description: `(Optional) The content to insert, or replace with.`, Description: `The content to insert.`,
}, },
}, },
Required: []string{"path", "operation"}, Required: []string{"path", "start", "content"},
}, },
}}, }},
Impl: func(args map[string]interface{}) (string, error) { Impl: func(args map[string]interface{}) (string, error) {
@ -224,13 +202,61 @@ var AvailableTools = map[string]AvailableTool{
if !ok { if !ok {
return "", fmt.Errorf("Invalid path in function arguments: %v", tmp) return "", fmt.Errorf("Invalid path in function arguments: %v", tmp)
} }
tmp, ok = args["operation"] var start_line int
tmp, ok = args["start_line"]
if ok {
tmp, ok := tmp.(float64)
if !ok { if !ok {
return "", fmt.Errorf("operation parameter to modify_file was not included.") return "", fmt.Errorf("Invalid start_line in function arguments: %v", tmp)
} }
operation, ok := tmp.(string) start_line = int(tmp)
}
var content string
tmp, ok = args["content"]
if ok {
content, ok = tmp.(string)
if !ok { if !ok {
return "", fmt.Errorf("Invalid operation in function arguments: %v", tmp) return "", fmt.Errorf("Invalid content in function arguments: %v", tmp)
}
}
return FileInsertLines(path, start_line, content), nil
},
},
"file_replace_lines": {
Tool: openai.Tool{Type: "function", Function: openai.FunctionDefinition{
Name: "file_replace_lines",
Description: FILE_REPLACE_LINES,
Parameters: FunctionParameters{
Type: "object",
Properties: map[string]FunctionParameter{
"path": {
Type: "string",
Description: "Path of the file to be modified, relative to the current working directory.",
},
"start_line": {
Type: "integer",
Description: `Line number which specifies the start of the replacement range (inclusive, this line will be replaced)`,
},
"end_line": {
Type: "integer",
Description: `Line number which specifies the end of the replacement range (inclusive, this line will be replaced). If unset, will be set to the end of the file.`,
},
"content": {
Type: "string",
Description: `Content which will replace specified range.`,
},
},
Required: []string{"path", "start_line", "content"},
},
}},
Impl: func(args map[string]interface{}) (string, error) {
tmp, ok := args["path"]
if !ok {
return "", fmt.Errorf("path parameter to write_file was not included.")
}
path, ok := tmp.(string)
if !ok {
return "", fmt.Errorf("Invalid path in function arguments: %v", tmp)
} }
var start_line int var start_line int
tmp, ok = args["start_line"] tmp, ok = args["start_line"]
@ -259,7 +285,61 @@ var AvailableTools = map[string]AvailableTool{
} }
} }
return ModifyFile(path, operation, content, start_line, end_line), nil return FileReplaceLines(path, start_line, end_line, content), nil
},
},
"file_remove_lines": {
Tool: openai.Tool{Type: "function", Function: openai.FunctionDefinition{
Name: "file_remove_lines",
Description: FILE_REMOVE_LINES,
Parameters: FunctionParameters{
Type: "object",
Properties: map[string]FunctionParameter{
"path": {
Type: "string",
Description: "Path of the file to be modified, relative to the current working directory.",
},
"start_line": {
Type: "integer",
Description: `Line number which specifies the start of the removal range (inclusive, this line will be removed)`,
},
"end_line": {
Type: "integer",
Description: `Line number which specifies the end of the removal range (inclusive, this line will be removed).`,
},
},
Required: []string{"path", "start_line", "end_line"},
},
}},
Impl: func(args map[string]interface{}) (string, error) {
tmp, ok := args["path"]
if !ok {
return "", fmt.Errorf("path parameter to write_file was not included.")
}
path, ok := tmp.(string)
if !ok {
return "", fmt.Errorf("Invalid path in function arguments: %v", tmp)
}
var start_line int
tmp, ok = args["start_line"]
if ok {
tmp, ok := tmp.(float64)
if !ok {
return "", fmt.Errorf("Invalid start_line in function arguments: %v", tmp)
}
start_line = int(tmp)
}
var end_line int
tmp, ok = args["end_line"]
if ok {
tmp, ok := tmp.(float64)
if !ok {
return "", fmt.Errorf("Invalid end_line in function arguments: %v", tmp)
}
end_line = int(tmp)
}
return FileRemoveLines(path, start_line, end_line), nil
}, },
}, },
} }
@ -460,7 +540,7 @@ func WriteFile(path string, content string) string {
return resultToJson(FunctionResult{}) return resultToJson(FunctionResult{})
} }
func ModifyFile(path string, operation string, content string, startLine int, endLine int) string { func FileInsertLines(path string, startLine int, content string) string {
ok, res := isPathWithinCWD(path) ok, res := isPathWithinCWD(path)
if !ok { if !ok {
return resultToJson(*res) return resultToJson(*res)
@ -479,42 +559,16 @@ func ModifyFile(path string, operation string, content string, startLine int, en
data = []byte{} data = []byte{}
} }
if startLine < 0 { if startLine < 1 {
return resultToJson(FunctionResult{Message: "start_line cannot be less than 0"}) return resultToJson(FunctionResult{Message: "start_line cannot be less than 1"})
} }
// Split the content by newline to process lines
lines := strings.Split(string(data), "\n") lines := strings.Split(string(data), "\n")
contentLines := strings.Split(strings.Trim(content, "\n"), "\n") contentLines := strings.Split(strings.Trim(content, "\n"), "\n")
switch operation {
case "insert_before":
// Insert new lines
before := lines[:startLine-1] before := lines[:startLine-1]
after := append(contentLines, lines[startLine-1:]...) after := lines[startLine-1:]
lines = append(before, after...)
case "remove":
// Remove lines
if endLine == 0 || endLine > len(lines) {
endLine = len(lines)
}
lines = append(lines[:startLine-1], lines[endLine:]...)
case "replace":
// Replace the lines between start_line and end_line
if endLine == 0 || endLine > len(lines) {
endLine = len(lines)
}
if startLine == 0 {
// model likely trying to replace contents, must start at line 1
startLine = 1
}
before := lines[:startLine-1]
after := lines[endLine:]
lines = append(before, append(contentLines, after...)...) lines = append(before, append(contentLines, after...)...)
default:
return resultToJson(FunctionResult{Message: fmt.Sprintf("Invalid operation: %s", operation)})
}
newContent := strings.Join(lines, "\n") newContent := strings.Join(lines, "\n")
@ -526,3 +580,90 @@ func ModifyFile(path string, operation string, content string, startLine int, en
return resultToJson(FunctionResult{Result: newContent}) return resultToJson(FunctionResult{Result: newContent})
} }
func FileReplaceLines(path string, startLine int, endLine int, content string) string {
ok, res := isPathWithinCWD(path)
if !ok {
return resultToJson(*res)
}
// Read the existing file's content
data, err := os.ReadFile(path)
if err != nil {
if !os.IsNotExist(err) {
return resultToJson(FunctionResult{Message: fmt.Sprintf("Could not read path: %s", err.Error())})
}
_, err = os.Create(path)
if err != nil {
return resultToJson(FunctionResult{Message: fmt.Sprintf("Could not create new file: %s", err.Error())})
}
data = []byte{}
}
if startLine < 1 {
return resultToJson(FunctionResult{Message: "start_line cannot be less than 1"})
}
lines := strings.Split(string(data), "\n")
contentLines := strings.Split(strings.Trim(content, "\n"), "\n")
if endLine == 0 || endLine > len(lines) {
endLine = len(lines)
}
before := lines[:startLine-1]
after := lines[endLine:]
lines = append(before, append(contentLines, after...)...)
newContent := strings.Join(lines, "\n")
// Join the lines and write back to the file
err = os.WriteFile(path, []byte(newContent), 0644)
if err != nil {
return resultToJson(FunctionResult{Message: fmt.Sprintf("Could not write to path: %s", err.Error())})
}
return resultToJson(FunctionResult{Result: newContent})
}
func FileRemoveLines(path string, startLine int, endLine int) string {
ok, res := isPathWithinCWD(path)
if !ok {
return resultToJson(*res)
}
// Read the existing file's content
data, err := os.ReadFile(path)
if err != nil {
if !os.IsNotExist(err) {
return resultToJson(FunctionResult{Message: fmt.Sprintf("Could not read path: %s", err.Error())})
}
_, err = os.Create(path)
if err != nil {
return resultToJson(FunctionResult{Message: fmt.Sprintf("Could not create new file: %s", err.Error())})
}
data = []byte{}
}
if startLine < 1 {
return resultToJson(FunctionResult{Message: "start_line cannot be less than 1"})
}
lines := strings.Split(string(data), "\n")
if endLine == 0 || endLine > len(lines) {
endLine = len(lines)
}
lines = append(lines[:startLine-1], lines[endLine:]...)
newContent := strings.Join(lines, "\n")
// Join the lines and write back to the file
err = os.WriteFile(path, []byte(newContent), 0644)
if err != nil {
return resultToJson(FunctionResult{Message: fmt.Sprintf("Could not write to path: %s", err.Error())})
}
return resultToJson(FunctionResult{Result: newContent})
}