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 {
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())
messages := []Message{

View File

@ -35,11 +35,8 @@ type AvailableTool struct {
Impl func(arguments map[string]interface{}) (string, error)
}
var AvailableTools = map[string]AvailableTool{
"read_dir": {
Tool: openai.Tool{Type: "function", Function: openai.FunctionDefinition{
Name: "read_dir",
Description: `Return the contents of the CWD (current working directory).
const (
READ_DIR_DESCRIPTION = `Return the contents of the CWD (current working directory).
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 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{
Type: "object",
Properties: map[string]FunctionParameter{
@ -79,16 +115,7 @@ For directories, size represents the number of entries in that directory.`,
"read_file": {
Tool: openai.Tool{Type: "function", Function: openai.FunctionDefinition{
Name: "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:
{
"message": "success", // if successful, or a different message indicating failure
"result": "1\tthe contents\n2\tof the file\n"
}`,
Description: READ_FILE_DESCRIPTION,
Parameters: FunctionParameters{
Type: "object",
Properties: map[string]FunctionParameter{
@ -115,12 +142,7 @@ Example:
"write_file": {
Tool: openai.Tool{Type: "function", Function: openai.FunctionDefinition{
Name: "write_file",
Description: `Write the provided contents to a file relative to the current working directory.
Result is returned as JSON in the following format:
{
"message": "success", // if successful, or a different message indicating failure
}`,
Description: WRITE_FILE_DESCRIPTION,
Parameters: FunctionParameters{
Type: "object",
Properties: map[string]FunctionParameter{
@ -156,28 +178,10 @@ Result is returned as JSON in the following format:
return WriteFile(path, content), nil
},
},
"modify_file": {
"file_insert_lines": {
Tool: openai.Tool{Type: "function", Function: openai.FunctionDefinition{
Name: "modify_file",
Description: `Perform complex line-based modifications to a file.
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"}`,
Name: "file_insert_lines",
Description: FILE_INSERT_LINES_DESCRIPTION,
Parameters: FunctionParameters{
Type: "object",
Properties: map[string]FunctionParameter{
@ -185,24 +189,16 @@ Examples:
Type: "string",
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, remove, replace`,
},
"start_line": {
"position": {
Type: "integer",
Description: `(Optional) Where to start making a modification (insert, remove, and replace).`,
},
"end_line": {
Type: "integer",
Description: `(Optional) Where to stop making a modification (remove or replace, end of file if omitted).`,
Description: `Which line to insert content *before*.`,
},
"content": {
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) {
@ -214,13 +210,61 @@ Examples:
if !ok {
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 {
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 {
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
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{})
}
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)
if !ok {
return resultToJson(*res)
@ -469,42 +513,16 @@ func ModifyFile(path string, operation string, content string, startLine int, en
data = []byte{}
}
if startLine < 0 {
return resultToJson(FunctionResult{Message: "start_line cannot be less than 0"})
if position < 1 {
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")
contentLines := strings.Split(strings.Trim(content, "\n"), "\n")
switch operation {
case "insert":
// 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:]
before := lines[:position-1]
after := lines[position-1:]
lines = append(before, append(contentLines, after...)...)
default:
return resultToJson(FunctionResult{Message: fmt.Sprintf("Invalid operation: %s", operation)})
}
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})
}
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})
}