Compare commits

..

15 Commits

Author SHA1 Message Date
3859084fd8 Refine tool descriptions 2023-11-27 05:21:41 +00:00
a805c92131 Update file_insert_lines
Renamed `start_line` parameter to `position`
2023-11-27 05:21:41 +00:00
3b20e00330 Removed file_remove_lines in favor of a generalized file_replace_lines 2023-11-27 05:21:41 +00:00
8e262c4839 Replace modify_file with individual functions
- file_insert_lines
- file_replace_lines
- file_remove_lines
2023-11-27 05:21:41 +00:00
d1c11b41d8 Lift tool description out to constant to reduce clutter 2023-11-27 05:21:41 +00:00
2f6c8006d0 Ranamed modify_file's 'insert' operation to 'insert_before' 2023-11-27 05:21:41 +00:00
5c1f7e2594 Add modify_file tool 2023-11-27 05:21:41 +00:00
b89cecf89e Adjust read_file so it returns line numbers 2023-11-27 05:21:41 +00:00
bf9a80e336 Small fixes 2023-11-27 05:21:41 +00:00
07cc8306c1 Add read_file and write_file tools
Also improve `read_dir` description, and make it skip hidden files
2023-11-27 05:21:41 +00:00
4ae5c5e717 Adjust read_dir description and return value 2023-11-27 05:21:41 +00:00
5ff763ecda Only allow read_dir (and other file access) within current working dir
Hopefully, anyway :)
2023-11-27 05:21:41 +00:00
3e59702c80 Add tool calling support to streamed requests 2023-11-27 05:21:41 +00:00
bf1f23b1d6 Add initial support for tools
So far only supported on the non-streaming endpoint.

Added the `read_dir` tool for reading contents from paths relative to the
current working directory.
2023-11-27 05:21:41 +00:00
1e63c09907 Update prompt used to generate conversation title 2023-11-27 05:21:41 +00:00
2 changed files with 160 additions and 96 deletions

View File

@ -22,7 +22,7 @@ func (m *Message) FriendlyRole() string {
} }
func (c *Conversation) GenerateTitle() error { func (c *Conversation) GenerateTitle() error {
const header = "Generate a short title for the conversation below." const header = "Generate a consise 4-5 word title for the conversation below."
prompt := fmt.Sprintf("%s\n\n---\n\n%s", header, c.FormatForExternalPrompting()) prompt := fmt.Sprintf("%s\n\n---\n\n%s", header, c.FormatForExternalPrompting())
messages := []Message{ messages := []Message{

View File

@ -35,11 +35,8 @@ type AvailableTool struct {
Impl func(arguments map[string]interface{}) (string, error) Impl func(arguments map[string]interface{}) (string, error)
} }
var AvailableTools = map[string]AvailableTool{ const (
"read_dir": { READ_DIR_DESCRIPTION = `Return the contents of the CWD (current working directory).
Tool: openai.Tool{Type: "function", Function: openai.FunctionDefinition{
Name: "read_dir",
Description: `Return the contents of the CWD (current working directory).
Results are returned as JSON in the following format: Results are returned as JSON in the following format:
{ {
@ -53,7 +50,46 @@ Results are returned as JSON in the following format:
} }
For files, size represents the size (in bytes) of the file. For files, size represents the size (in bytes) of the file.
For directories, size represents the number of entries in that directory.`, For directories, size represents the number of entries in that directory.`
READ_FILE_DESCRIPTION = `Read the contents of a text file relative to the current working directory.
Each line of the file is prefixed with its line number and a tabs (\t) to make
it make it easier to see which lines to change for other modifications.
Example result:
{
"message": "success", // if successful, or a different message indicating failure
"result": "1\tthe contents\n2\tof the file\n"
}`
WRITE_FILE_DESCRIPTION = `Write the provided contents to a file relative to the current working directory.
Note: only use this tool when you've been explicitly asked to create or write to a file.
When using this function, you do not need to share the content you intend to write with the user first.
Example result:
{
"message": "success", // if successful, or a different message indicating failure
}`
FILE_INSERT_LINES_DESCRIPTION = `Insert lines into a file, must specify path.
Make sure your inserts match the flow and indentation of surrounding content.`
FILE_REPLACE_LINES_DESCRIPTION = `Replace or remove a range of lines within a file, must specify path.
Useful for re-writing snippets/blocks of code or entire functions.
Be cautious with your edits. When replacing, ensure the replacement content matches the flow and indentation of surrounding content.`
)
var AvailableTools = map[string]AvailableTool{
"read_dir": {
Tool: openai.Tool{Type: "function", Function: openai.FunctionDefinition{
Name: "read_dir",
Description: READ_DIR_DESCRIPTION,
Parameters: FunctionParameters{ Parameters: FunctionParameters{
Type: "object", Type: "object",
Properties: map[string]FunctionParameter{ Properties: map[string]FunctionParameter{
@ -79,16 +115,7 @@ For directories, size represents the number of entries in that directory.`,
"read_file": { "read_file": {
Tool: openai.Tool{Type: "function", Function: openai.FunctionDefinition{ Tool: openai.Tool{Type: "function", Function: openai.FunctionDefinition{
Name: "read_file", Name: "read_file",
Description: `Read the contents of a text file relative to the current working directory. Description: READ_FILE_DESCRIPTION,
Each line of the file is prefixed with its line number and a tabs (\t) to make
it make it easier to see which lines to change for other modifications.
Example:
{
"message": "success", // if successful, or a different message indicating failure
"result": "1\tthe contents\n2\tof the file\n"
}`,
Parameters: FunctionParameters{ Parameters: FunctionParameters{
Type: "object", Type: "object",
Properties: map[string]FunctionParameter{ Properties: map[string]FunctionParameter{
@ -115,12 +142,7 @@ Example:
"write_file": { "write_file": {
Tool: openai.Tool{Type: "function", Function: openai.FunctionDefinition{ Tool: openai.Tool{Type: "function", Function: openai.FunctionDefinition{
Name: "write_file", Name: "write_file",
Description: `Write the provided contents to a file relative to the current working directory. Description: WRITE_FILE_DESCRIPTION,
Result is returned as JSON in the following format:
{
"message": "success", // if successful, or a different message indicating failure
}`,
Parameters: FunctionParameters{ Parameters: FunctionParameters{
Type: "object", Type: "object",
Properties: map[string]FunctionParameter{ Properties: map[string]FunctionParameter{
@ -156,28 +178,10 @@ Result is returned as JSON in the following format:
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: `Perform complex line-based modifications to a file. Description: FILE_INSERT_LINES_DESCRIPTION,
Line ranges are inclusive. If 'start_line' is specified but 'end_line' is not,
'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*
Examples:
* Insert the lines "hello<new line>world" at line 10, preserving other content:
{"path": "myfile", "operation": "insert", "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"}`,
Parameters: FunctionParameters{ Parameters: FunctionParameters{
Type: "object", Type: "object",
Properties: map[string]FunctionParameter{ Properties: map[string]FunctionParameter{
@ -185,24 +189,16 @@ Examples:
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": { "position": {
Type: "string",
Description: `The the type of modification to make to the file. One of: insert, remove, replace`,
},
"start_line": {
Type: "integer", Type: "integer",
Description: `(Optional) Where to start making a modification (insert, remove, and replace).`, Description: `Which line to insert content *before*.`,
},
"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", "position", "content"},
}, },
}}, }},
Impl: func(args map[string]interface{}) (string, error) { Impl: func(args map[string]interface{}) (string, error) {
@ -214,13 +210,61 @@ Examples:
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 position int
tmp, ok = args["position"]
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 position in function arguments: %v", tmp)
} }
operation, ok := tmp.(string) position = 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, position, content), nil
},
},
"file_replace_lines": {
Tool: openai.Tool{Type: "function", Function: openai.FunctionDefinition{
Name: "file_replace_lines",
Description: FILE_REPLACE_LINES_DESCRIPTION,
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).`,
},
"end_line": {
Type: "integer",
Description: `Line number which specifies the end of the replacement range (inclusive). If unset, range extends to end of file.`,
},
"content": {
Type: "string",
Description: `Content to replace specified range. Omit to remove the specified range.`,
},
},
Required: []string{"path", "start_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 var start_line int
tmp, ok = args["start_line"] tmp, ok = args["start_line"]
@ -249,7 +293,7 @@ Examples:
} }
} }
return ModifyFile(path, operation, content, start_line, end_line), nil return FileReplaceLines(path, start_line, end_line, content), nil
}, },
}, },
} }
@ -450,7 +494,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, position int, content string) string {
ok, res := isPathWithinCWD(path) ok, res := isPathWithinCWD(path)
if !ok { if !ok {
return resultToJson(*res) return resultToJson(*res)
@ -469,42 +513,16 @@ func ModifyFile(path string, operation string, content string, startLine int, en
data = []byte{} data = []byte{}
} }
if startLine < 0 { if position < 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 { before := lines[:position-1]
case "insert": after := lines[position-1:]
// Insert new lines
before := lines[:startLine-1]
after := append(contentLines, 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")
@ -516,3 +534,49 @@ 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})
}