Skip to content

Collect calls #23

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 4, 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
4 changes: 4 additions & 0 deletions client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,10 @@ func TestSimpleEvaluate(t *testing.T) {
if !strings.Contains(out, "Washington") {
t.Errorf("Unexpected output: %s", out)
}

if run.Program() == nil {
t.Error("Run program not set")
}
}

func TestEvaluateWithContext(t *testing.T) {
Expand Down
77 changes: 73 additions & 4 deletions run.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"fmt"
"io"
"log/slog"
"maps"
"net/http"
"os/exec"
"strconv"
Expand All @@ -26,10 +27,14 @@ type Run struct {
wait func()
basicCommand bool

rawOutput map[string]any
output, errput string
events chan Frame
lock sync.Mutex
program *Program
callsLock sync.RWMutex
calls map[string]CallFrame
parentCallFrameID string
rawOutput map[string]any
output, errput string
events chan Frame
lock sync.Mutex
}

// Text returns the text output of the gptscript. It blocks until the output is ready.
Expand Down Expand Up @@ -59,6 +64,49 @@ func (r *Run) Err() error {
return r.err
}

// Program returns the gptscript program for the run.
func (r *Run) Program() *Program {
r.lock.Lock()
defer r.lock.Unlock()
return r.program
}

// RespondingTool returns the name of the tool that produced the output.
func (r *Run) RespondingTool() Tool {
r.lock.Lock()
defer r.lock.Unlock()

if r.program == nil {
return Tool{}
}

s, ok := r.rawOutput["toolID"].(string)
if !ok {
return Tool{}
}

return r.program.ToolSet[s]
}

// Calls will return a flattened array of the calls for this run.
func (r *Run) Calls() map[string]CallFrame {
r.callsLock.RLock()
defer r.callsLock.RUnlock()
return maps.Clone(r.calls)
}

// ParentCallFrame returns the CallFrame for the top-level or "parent" call. The boolean indicates whether there is a parent CallFrame.
func (r *Run) ParentCallFrame() (CallFrame, bool) {
r.callsLock.RLock()
defer r.callsLock.RUnlock()

if r.parentCallFrameID == "" {
return CallFrame{}, false
}

return r.calls[r.parentCallFrameID], true
}

// ErrorOutput returns the stderr output of the gptscript.
// Should only be called after Bytes or Text has returned an error.
func (r *Run) ErrorOutput() string {
Expand Down Expand Up @@ -143,6 +191,10 @@ func (r *Run) NextChat(ctx context.Context, input string) (*Run, error) {
}

func (r *Run) request(ctx context.Context, payload any) (err error) {
if r.state.IsTerminal() {
return fmt.Errorf("run is in terminal state and cannot be run again: state %q", r.state)
}

var (
req *http.Request
url = fmt.Sprintf("%s/%s", r.url, r.requestPath)
Expand Down Expand Up @@ -205,6 +257,10 @@ func (r *Run) request(ctx context.Context, payload any) (err error) {
r.lock.Unlock()
}()

r.callsLock.Lock()
r.calls = make(map[string]CallFrame)
r.callsLock.Unlock()

for n := 0; n != 0 || err == nil; n, err = resp.Body.Read(buf) {
for _, line := range bytes.Split(bytes.TrimSpace(append(frag, buf[:n]...)), []byte("\n\n")) {
line = bytes.TrimSpace(bytes.TrimPrefix(line, []byte("data: ")))
Expand Down Expand Up @@ -287,6 +343,19 @@ func (r *Run) request(ctx context.Context, payload any) (err error) {
return
}

if event.Call != nil {
r.callsLock.Lock()
r.calls[event.Call.ID] = *event.Call
if r.parentCallFrameID == "" && event.Call.ParentID == "" {
r.parentCallFrameID = event.Call.ID
}
r.callsLock.Unlock()
} else if event.Run != nil && event.Run.Type == EventTypeRunStart {
r.callsLock.Lock()
r.program = &event.Run.Program
r.callsLock.Unlock()
}

if r.opts.IncludeEvents {
r.events <- event
}
Expand Down