Skip to content

fix: better error reporting #32

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

Merged
merged 1 commit into from
Jun 19, 2024
Merged
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
27 changes: 13 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,7 @@ These are optional options that can be passed to the various `exec` functions.
None of the options is required, and the defaults will reduce the number of calls made to the Model API.
As noted above, the Global Options are also available to specify here. These options would take precedence.

- `cache`: Enable or disable caching. Default (true).
- `cacheDir`: Specify the cache directory.
- `disableCache`: Enable or disable caching. Default (false).
- `subTool`: Use tool of this name, not the first tool
- `input`: Input arguments for the tool run
- `workspace`: Directory to use for the workspace, if specified it will not be deleted on exit
Expand Down Expand Up @@ -88,7 +87,7 @@ import (
)

func listModels(ctx context.Context) ([]string, error) {
g, err := gptscript.NewGPTScript()
g, err := gptscript.NewGPTScript(gptscript.GlobalOptions{})
if err != nil {
return nil, err
}
Expand All @@ -111,7 +110,7 @@ import (
)

func parse(ctx context.Context, fileName string) ([]gptscript.Node, error) {
g, err := gptscript.NewGPTScript()
g, err := gptscript.NewGPTScript(gptscript.GlobalOptions{})
if err != nil {
return nil, err
}
Expand All @@ -135,7 +134,7 @@ import (
)

func parseTool(ctx context.Context, contents string) ([]gptscript.Node, error) {
g, err := gptscript.NewGPTScript()
g, err := gptscript.NewGPTScript(gptscript.GlobalOptions{})
if err != nil {
return nil, err
}
Expand All @@ -159,7 +158,7 @@ import (
)

func parse(ctx context.Context, nodes []gptscript.Node) (string, error) {
g, err := gptscript.NewGPTScript()
g, err := gptscript.NewGPTScript(gptscript.GlobalOptions{})
if err != nil {
return "", err
}
Expand Down Expand Up @@ -187,7 +186,7 @@ func runTool(ctx context.Context) (string, error) {
Instructions: "who was the president of the united states in 1928?",
}

g, err := gptscript.NewGPTScript()
g, err := gptscript.NewGPTScript(gptscript.GlobalOptions{})
if err != nil {
return "", err
}
Expand Down Expand Up @@ -221,7 +220,7 @@ func runFile(ctx context.Context) (string, error) {
Input: "--input hello",
}

g, err := gptscript.NewGPTScript()
g, err := gptscript.NewGPTScript(gptscript.GlobalOptions{})
if err != nil {
return "", err
}
Expand All @@ -238,7 +237,7 @@ func runFile(ctx context.Context) (string, error) {

### Streaming events

In order to stream events, you must set `IncludeEvents` option to `true`. You if you don't set this and try to stream events, then it will succeed, but you will not get any events. More importantly, if you set `IncludeEvents` to `true`, you must stream the events for the script to complete.
In order to stream events, you must set `IncludeEvents` option to `true`. If you don't set this and try to stream events, then it will succeed, but you will not get any events. More importantly, if you set `IncludeEvents` to `true`, you must stream the events for the script to complete.

```go
package main
Expand All @@ -250,13 +249,13 @@ import (
)

func streamExecTool(ctx context.Context) error {
opts := gptscript.Opts{
opts := gptscript.Options{
DisableCache: &[]bool{true}[0],
IncludeEvents: true,
Input: "--input world",
}

g, err := gptscript.NewGPTScript()
g, err := gptscript.NewGPTScript(gptscript.GlobalOptions{})
if err != nil {
return "", err
}
Expand All @@ -278,7 +277,7 @@ func streamExecTool(ctx context.Context) error {

### Confirm

Using the `confirm: true` option allows a user to inspect potentially dangerous commands before they are run. The caller has the ability to allow or disallow their running. In order to do this, a caller should look for the `CallConfirm` event. This also means that `IncludeEvent` should be `true`.
Using the `Confirm: true` option allows a user to inspect potentially dangerous commands before they are run. The caller has the ability to allow or disallow their running. In order to do this, a caller should look for the `CallConfirm` event. This also means that `IncludeEvent` should be `true`.

```go
package main
Expand All @@ -297,7 +296,7 @@ func runFileWithConfirm(ctx context.Context) (string, error) {
IncludeEvents: true,
}

g, err := gptscript.NewGPTScript()
g, err := gptscript.NewGPTScript(gptscript.GlobalOptions{})
if err != nil {
return "", err
}
Expand Down Expand Up @@ -351,7 +350,7 @@ func runFileWithPrompt(ctx context.Context) (string, error) {
IncludeEvents: true,
}

g, err := gptscript.NewGPTScript()
g, err := gptscript.NewGPTScript(gptscript.GlobalOptions{})
if err != nil {
return "", err
}
Expand Down
14 changes: 13 additions & 1 deletion gptscript.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func NewGPTScript(opts GlobalOptions) (GPTScript, error) {
defer lock.Unlock()
gptscriptCount++

disableServer := os.Getenv("GPT_SCRIPT_DISABLE_SERVER") == "true"
disableServer := os.Getenv("GPTSCRIPT_DISABLE_SERVER") == "true"

if serverURL == "" && disableServer {
serverURL = os.Getenv("GPTSCRIPT_URL")
Expand Down Expand Up @@ -165,6 +165,10 @@ func (g *gptscript) Parse(ctx context.Context, fileName string) ([]Node, error)
return nil, err
}

for _, node := range doc.Nodes {
node.TextNode.process()
}

return doc.Nodes, nil
}

Expand All @@ -180,11 +184,19 @@ func (g *gptscript) ParseTool(ctx context.Context, toolDef string) ([]Node, erro
return nil, err
}

for _, node := range doc.Nodes {
node.TextNode.process()
}

return doc.Nodes, nil
}

// Fmt will format the given nodes into a string.
func (g *gptscript) Fmt(ctx context.Context, nodes []Node) (string, error) {
for _, node := range nodes {
node.TextNode.combine()
}

out, err := g.runBasicCommand(ctx, "fmt", Document{Nodes: nodes})
if err != nil {
return "", err
Expand Down
52 changes: 24 additions & 28 deletions gptscript_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ func TestEvaluateWithContext(t *testing.T) {
},
}

run, err := g.Evaluate(context.Background(), Options{DisableCache: true, IncludeEvents: true}, tool)
run, err := g.Evaluate(context.Background(), Options{}, tool)
if err != nil {
t.Errorf("Error executing tool: %v", err)
}
Expand Down Expand Up @@ -214,7 +214,7 @@ func TestEvaluateWithToolList(t *testing.T) {
Tools: []string{"sys.exec"},
Description: "Echoes the input",
Arguments: ObjectSchema("input", "The string input to echo"),
Instructions: shebang + "\n echo ${input}",
Instructions: shebang + "\necho ${input}",
},
}

Expand Down Expand Up @@ -410,9 +410,12 @@ func TestParseToolWithTextNode(t *testing.T) {
t.Fatalf("No text node found")
}

if tools[1].TextNode.Text != "!markdown\nhello\n" {
if tools[1].TextNode.Text != "hello\n" {
t.Errorf("Unexpected text: %s", tools[1].TextNode.Text)
}
if tools[1].TextNode.Fmt != "markdown" {
t.Errorf("Unexpected fmt: %s", tools[1].TextNode.Fmt)
}
}

func TestFmt(t *testing.T) {
Expand Down Expand Up @@ -484,7 +487,8 @@ func TestFmtWithTextNode(t *testing.T) {
},
{
TextNode: &TextNode{
Text: "!markdown\nWe now echo hello there\n",
Fmt: "markdown",
Text: "We now echo hello there\n",
},
},
{
Expand Down Expand Up @@ -686,14 +690,12 @@ func TestToolWithGlobalTools(t *testing.T) {

func TestConfirm(t *testing.T) {
var eventContent string
tools := []ToolDef{
{
Instructions: "List the files in the current directory",
Tools: []string{"sys.exec"},
},
tools := ToolDef{
Instructions: "List the files in the current directory",
Tools: []string{"sys.exec"},
}

run, err := g.Evaluate(context.Background(), Options{IncludeEvents: true, Confirm: true}, tools...)
run, err := g.Evaluate(context.Background(), Options{IncludeEvents: true, Confirm: true}, tools)
if err != nil {
t.Errorf("Error executing tool: %v", err)
}
Expand Down Expand Up @@ -771,14 +773,12 @@ func TestConfirm(t *testing.T) {

func TestConfirmDeny(t *testing.T) {
var eventContent string
tools := []ToolDef{
{
Instructions: "List the files in the current directory",
Tools: []string{"sys.exec"},
},
tools := ToolDef{
Instructions: "List the files in the current directory",
Tools: []string{"sys.exec"},
}

run, err := g.Evaluate(context.Background(), Options{IncludeEvents: true, Confirm: true}, tools...)
run, err := g.Evaluate(context.Background(), Options{IncludeEvents: true, Confirm: true}, tools)
if err != nil {
t.Errorf("Error executing tool: %v", err)
}
Expand Down Expand Up @@ -843,14 +843,12 @@ func TestConfirmDeny(t *testing.T) {

func TestPrompt(t *testing.T) {
var eventContent string
tools := []ToolDef{
{
Instructions: "Use the sys.prompt user to ask the user for 'first name' which is not sensitive. After you get their first name, say hello.",
Tools: []string{"sys.prompt"},
},
tools := ToolDef{
Instructions: "Use the sys.prompt user to ask the user for 'first name' which is not sensitive. After you get their first name, say hello.",
Tools: []string{"sys.prompt"},
}

run, err := g.Evaluate(context.Background(), Options{IncludeEvents: true, Prompt: true}, tools...)
run, err := g.Evaluate(context.Background(), Options{IncludeEvents: true, Prompt: true}, tools)
if err != nil {
t.Errorf("Error executing tool: %v", err)
}
Expand Down Expand Up @@ -926,14 +924,12 @@ func TestPrompt(t *testing.T) {
}

func TestPromptWithoutPromptAllowed(t *testing.T) {
tools := []ToolDef{
{
Instructions: "Use the sys.prompt user to ask the user for 'first name' which is not sensitive. After you get their first name, say hello.",
Tools: []string{"sys.prompt"},
},
tools := ToolDef{
Instructions: "Use the sys.prompt user to ask the user for 'first name' which is not sensitive. After you get their first name, say hello.",
Tools: []string{"sys.prompt"},
}

run, err := g.Evaluate(context.Background(), Options{IncludeEvents: true}, tools...)
run, err := g.Evaluate(context.Background(), Options{IncludeEvents: true}, tools)
if err != nil {
t.Errorf("Error executing tool: %v", err)
}
Expand Down
8 changes: 4 additions & 4 deletions run.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ func (r *Run) Text() (string, error) {
r.lock.Lock()
defer r.lock.Unlock()
if r.err != nil {
return "", r.err
return "", fmt.Errorf("run encounterd an error: %w with error output: %s", r.err, r.errput)
}

return r.output, nil
Expand Down Expand Up @@ -237,11 +237,11 @@ func (r *Run) request(ctx context.Context, payload any) (err error) {

if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusBadRequest {
r.state = Error
r.err = fmt.Errorf("unexpected response status: %s", resp.Status)
return r.err
r.err = fmt.Errorf("run encountered an error")
} else {
r.state = Running
}

r.state = Running
r.events = make(chan Frame, 100)
r.lock.Lock()
go func() {
Expand Down
20 changes: 19 additions & 1 deletion tool.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package gptscript

import (
"fmt"
"strings"

"github.com/getkin/kin-openapi/openapi3"
)

Expand Down Expand Up @@ -56,11 +59,26 @@ type Node struct {
}

type TextNode struct {
Fmt string `json:"fmt,omitempty"`
Text string `json:"text,omitempty"`
}

func (n *TextNode) combine() {
if n != nil && n.Fmt != "" {
n.Text = fmt.Sprintf("!%s\n%s", n.Fmt, n.Text)
n.Fmt = ""
}
}

func (n *TextNode) process() {
if n != nil && strings.HasPrefix(n.Text, "!") {
n.Fmt, n.Text, _ = strings.Cut(strings.TrimPrefix(n.Text, "!"), "\n")
}
}

type ToolNode struct {
Tool Tool `json:"tool,omitempty"`
Fmt string `json:"fmt,omitempty"`
Tool Tool `json:"tool,omitempty"`
}

type Tool struct {
Expand Down