Skip to content

Commit 51d17ef

Browse files
Fix daemon support and don't default to 1024 tokens
1 parent d8e2a1f commit 51d17ef

File tree

8 files changed

+131
-34
lines changed

8 files changed

+131
-34
lines changed

pkg/cli/gptscript.go

+4-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"github.com/acorn-io/cmd"
1111
"github.com/gptscript-ai/gptscript/pkg/assemble"
1212
"github.com/gptscript-ai/gptscript/pkg/builtin"
13+
"github.com/gptscript-ai/gptscript/pkg/engine"
1314
"github.com/gptscript-ai/gptscript/pkg/input"
1415
"github.com/gptscript-ai/gptscript/pkg/loader"
1516
"github.com/gptscript-ai/gptscript/pkg/monitor"
@@ -98,6 +99,8 @@ func (r *GPTScript) Pre(cmd *cobra.Command, args []string) error {
9899
}
99100

100101
func (r *GPTScript) Run(cmd *cobra.Command, args []string) error {
102+
defer engine.CloseDaemons()
103+
101104
if r.ListModels {
102105
return r.listModels(cmd.Context())
103106
}
@@ -119,7 +122,7 @@ func (r *GPTScript) Run(cmd *cobra.Command, args []string) error {
119122
}
120123

121124
if len(args) == 0 {
122-
return fmt.Errorf("scripts argument required")
125+
return cmd.Help()
123126
}
124127

125128
prg, err := loader.Program(cmd.Context(), args[0], r.SubTool)

pkg/engine/daemon.go

+54-8
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,23 @@ var (
1919

2020
startPort, endPort int64
2121
nextPort int64
22+
daemonCtx context.Context
23+
daemonClose func()
24+
daemonWG sync.WaitGroup
2225
)
2326

27+
func CloseDaemons() {
28+
daemonLock.Lock()
29+
if daemonCtx == nil {
30+
daemonLock.Unlock()
31+
return
32+
}
33+
daemonLock.Unlock()
34+
35+
daemonClose()
36+
daemonWG.Wait()
37+
}
38+
2439
func (e *Engine) getNextPort() int64 {
2540
if startPort == 0 {
2641
startPort = 10240
@@ -32,24 +47,52 @@ func (e *Engine) getNextPort() int64 {
3247
return startPort + nextPort
3348
}
3449

50+
func getPath(instructions string) (string, string) {
51+
instructions = strings.TrimSpace(instructions)
52+
line := strings.TrimSpace(instructions)
53+
54+
if !strings.HasPrefix(line, "(") {
55+
return instructions, ""
56+
}
57+
58+
line, rest, ok := strings.Cut(line[1:], ")")
59+
if !ok {
60+
return instructions, ""
61+
}
62+
63+
path, value, ok := strings.Cut(strings.TrimSpace(line), "=")
64+
if !ok || strings.TrimSpace(path) != "path" {
65+
return instructions, ""
66+
}
67+
68+
return strings.TrimSpace(rest), strings.TrimSpace(value)
69+
}
70+
3571
func (e *Engine) startDaemon(ctx context.Context, tool types.Tool) (string, error) {
3672
daemonLock.Lock()
3773
defer daemonLock.Unlock()
3874

75+
instructions := strings.TrimPrefix(tool.Instructions, types.DaemonPrefix)
76+
instructions, path := getPath(instructions)
77+
3978
port, ok := daemonPorts[tool.ID]
40-
url := fmt.Sprintf("http://127.0.0.1:%d", port)
79+
url := fmt.Sprintf("http://127.0.0.1:%d%s", port, path)
4180
if ok {
4281
return url, nil
4382
}
4483

84+
if daemonCtx == nil {
85+
daemonCtx, daemonClose = context.WithCancel(context.Background())
86+
}
87+
88+
ctx = daemonCtx
4589
port = e.getNextPort()
46-
url = fmt.Sprintf("http://127.0.0.1:%d", port)
90+
url = fmt.Sprintf("http://127.0.0.1:%d%s", port, path)
4791

48-
instructions := types.CommandPrefix + strings.TrimPrefix(tool.Instructions, types.DaemonPrefix)
4992
cmd, close, err := e.newCommand(ctx, []string{
5093
fmt.Sprintf("PORT=%d", port),
5194
},
52-
instructions,
95+
types.CommandPrefix+instructions,
5396
"{}",
5497
)
5598
if err != nil {
@@ -58,7 +101,7 @@ func (e *Engine) startDaemon(ctx context.Context, tool types.Tool) (string, erro
58101

59102
cmd.Stderr = os.Stderr
60103
cmd.Stdout = os.Stdout
61-
log.Infof("launched [%s] port [%d] %v", tool.Name, port, cmd.Args)
104+
log.Infof("launched [%s][%s] port [%d] %v", tool.Name, tool.ID, port, cmd.Args)
62105
if err := cmd.Start(); err != nil {
63106
close()
64107
return url, err
@@ -67,6 +110,7 @@ func (e *Engine) startDaemon(ctx context.Context, tool types.Tool) (string, erro
67110
if daemonPorts == nil {
68111
daemonPorts = map[string]int64{}
69112
}
113+
daemonPorts[tool.ID] = port
70114

71115
killedCtx, cancel := context.WithCancelCause(ctx)
72116
defer cancel(nil)
@@ -85,13 +129,15 @@ func (e *Engine) startDaemon(ctx context.Context, tool types.Tool) (string, erro
85129
delete(daemonPorts, tool.ID)
86130
}()
87131

132+
daemonWG.Add(1)
88133
context.AfterFunc(ctx, func() {
89134
if err := cmd.Process.Kill(); err != nil {
90135
log.Errorf("daemon failed to kill tool [%s] process: %v", tool.Name, err)
91136
}
137+
daemonWG.Done()
92138
})
93139

94-
for range 20 {
140+
for i := 0; i < 20; i++ {
95141
resp, err := http.Get(url)
96142
if err == nil && resp.StatusCode == http.StatusOK {
97143
defer func() {
@@ -112,7 +158,7 @@ func (e *Engine) startDaemon(ctx context.Context, tool types.Tool) (string, erro
112158
return url, fmt.Errorf("timeout waiting for 200 response from GET %s", url)
113159
}
114160

115-
func (e *Engine) runDaemon(ctx context.Context, tool types.Tool, input string) (cmdRet *Return, cmdErr error) {
161+
func (e *Engine) runDaemon(ctx context.Context, prg *types.Program, tool types.Tool, input string) (cmdRet *Return, cmdErr error) {
116162
url, err := e.startDaemon(ctx, tool)
117163
if err != nil {
118164
return nil, err
@@ -121,5 +167,5 @@ func (e *Engine) runDaemon(ctx context.Context, tool types.Tool, input string) (
121167
tool.Instructions = strings.Join(append([]string{
122168
types.CommandPrefix + url,
123169
}, strings.Split(tool.Instructions, "\n")[1:]...), "\n")
124-
return e.runHTTP(ctx, tool, input)
170+
return e.runHTTP(ctx, prg, tool, input)
125171
}

pkg/engine/engine.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -158,9 +158,9 @@ func (e *Engine) Start(ctx Context, input string) (*Return, error) {
158158

159159
if tool.IsCommand() {
160160
if tool.IsHTTP() {
161-
return e.runHTTP(ctx.Ctx, tool, input)
161+
return e.runHTTP(ctx.Ctx, ctx.Program, tool, input)
162162
} else if tool.IsDaemon() {
163-
return e.runDaemon(ctx.Ctx, tool, input)
163+
return e.runDaemon(ctx.Ctx, ctx.Program, tool, input)
164164
}
165165
s, err := e.runCommand(ctx.Ctx, tool, input)
166166
if err != nil {

pkg/engine/http.go

+51-5
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,60 @@ package engine
22

33
import (
44
"context"
5+
"encoding/json"
56
"fmt"
67
"io"
78
"net/http"
9+
"net/url"
810
"strings"
911

1012
"github.com/gptscript-ai/gptscript/pkg/types"
1113
)
1214

13-
func (e *Engine) runHTTP(ctx context.Context, tool types.Tool, input string) (cmdRet *Return, cmdErr error) {
14-
url := strings.Split(tool.Instructions, "\n")[0][2:]
15+
const DaemonURLSuffix = ".daemon.gpt.local"
1516

16-
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, strings.NewReader(input))
17+
func (e *Engine) runHTTP(ctx context.Context, prg *types.Program, tool types.Tool, input string) (cmdRet *Return, cmdErr error) {
18+
toolURL := strings.Split(tool.Instructions, "\n")[0][2:]
19+
20+
parsed, err := url.Parse(toolURL)
21+
if err != nil {
22+
return nil, err
23+
}
24+
25+
if strings.HasSuffix(parsed.Hostname(), DaemonURLSuffix) {
26+
referencedToolName := strings.TrimSuffix(parsed.Hostname(), DaemonURLSuffix)
27+
referencedToolID, ok := tool.ToolMapping[referencedToolName]
28+
if !ok {
29+
return nil, fmt.Errorf("invalid reference [%s] to tool [%s] from [%s], missing \"tools: %s\" parameter", toolURL, referencedToolName, tool.Source, referencedToolName)
30+
}
31+
referencedTool, ok := prg.ToolSet[referencedToolID]
32+
if !ok {
33+
return nil, fmt.Errorf("failed to find tool [%s] for [%s]", referencedToolName, parsed.Hostname())
34+
}
35+
toolURL, err = e.startDaemon(ctx, referencedTool)
36+
if err != nil {
37+
return nil, err
38+
}
39+
toolURLParsed, err := url.Parse(toolURL)
40+
if err != nil {
41+
return nil, err
42+
}
43+
parsed.Host = toolURLParsed.Host
44+
toolURL = parsed.String()
45+
}
46+
47+
req, err := http.NewRequestWithContext(ctx, http.MethodPost, toolURL, strings.NewReader(input))
1748
if err != nil {
1849
return nil, err
1950
}
2051

2152
req.Header.Set("X-GPTScript-Tool-Name", tool.Name)
22-
req.Header.Set("Content-Type", "text/plain")
53+
54+
if err := json.Unmarshal([]byte(input), &map[string]any{}); err == nil {
55+
req.Header.Set("Content-Type", "application/json")
56+
} else {
57+
req.Header.Set("Content-Type", "text/plain")
58+
}
2359

2460
resp, err := http.DefaultClient.Do(req)
2561
if err != nil {
@@ -29,14 +65,24 @@ func (e *Engine) runHTTP(ctx context.Context, tool types.Tool, input string) (cm
2965

3066
if resp.StatusCode > 299 {
3167
_, _ = io.ReadAll(resp.Body)
32-
return nil, fmt.Errorf("error in request to [%s] [%d]: %s", url, resp.StatusCode, resp.Status)
68+
return nil, fmt.Errorf("error in request to [%s] [%d]: %s", toolURL, resp.StatusCode, resp.Status)
3369
}
3470

3571
content, err := io.ReadAll(resp.Body)
3672
if err != nil {
3773
return nil, err
3874
}
3975

76+
if resp.Header.Get("Content-Type") == "application/json" && strings.HasPrefix(string(content), "\"") {
77+
// This is dumb hack when something returns a string in JSON format, just decode it to a string
78+
var s string
79+
if err := json.Unmarshal(content, &s); err == nil {
80+
return &Return{
81+
Result: &s,
82+
}, nil
83+
}
84+
}
85+
4086
s := string(content)
4187
return &Return{
4288
Result: &s,

pkg/loader/loader.go

+6-3
Original file line numberDiff line numberDiff line change
@@ -210,15 +210,15 @@ func readTool(ctx context.Context, prg *types.Program, base *source, targetToolN
210210
}
211211

212212
if i != 0 && tool.Name == "" {
213-
return types.Tool{}, parser.NewErrLine(tool.Source.LineNo, fmt.Errorf("only the first tool in a file can have no name"))
213+
return types.Tool{}, parser.NewErrLine(tool.Source.File, tool.Source.LineNo, fmt.Errorf("only the first tool in a file can have no name"))
214214
}
215215

216216
if targetToolName != "" && tool.Name == targetToolName {
217217
mainTool = tool
218218
}
219219

220220
if existing, ok := localTools[tool.Name]; ok {
221-
return types.Tool{}, parser.NewErrLine(tool.Source.LineNo,
221+
return types.Tool{}, parser.NewErrLine(tool.Source.File, tool.Source.LineNo,
222222
fmt.Errorf("duplicate tool name [%s] in %s found at lines %d and %d", tool.Name, tool.Source.File,
223223
tool.Source.LineNo, existing.Source.LineNo))
224224
}
@@ -313,10 +313,13 @@ func link(ctx context.Context, prg *types.Program, base *source, tool types.Tool
313313
continue
314314
}
315315

316-
toolName, subTool, ok := strings.Cut(targetToolName, " from ")
316+
subTool, toolName, ok := strings.Cut(targetToolName, " from ")
317317
if ok {
318318
toolName = strings.TrimSpace(toolName)
319319
subTool = strings.TrimSpace(subTool)
320+
} else {
321+
toolName = targetToolName
322+
subTool = ""
320323
}
321324

322325
resolvedTool, err := resolve(ctx, prg, base, toolName, subTool)

pkg/openai/client.go

-5
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ import (
2323
const (
2424
DefaultVisionModel = openai.GPT4VisionPreview
2525
DefaultModel = openai.GPT4TurboPreview
26-
DefaultMaxTokens = 1024
2726
DefaultPromptParameter = "defaultPromptParameter"
2827
)
2928

@@ -274,10 +273,6 @@ func (c *Client) Call(ctx context.Context, messageRequest types.CompletionReques
274273
}
275274
}
276275

277-
if request.MaxTokens == 0 {
278-
request.MaxTokens = DefaultMaxTokens
279-
}
280-
281276
if !messageRequest.Vision {
282277
for _, tool := range messageRequest.Tools {
283278
params := tool.Function.Parameters

pkg/parser/parser.go

+13-9
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ func isParam(line string, tool *types.Tool) (_ bool, err error) {
123123
}
124124

125125
type ErrLine struct {
126+
Path string
126127
Line int
127128
Err error
128129
}
@@ -132,11 +133,15 @@ func (e *ErrLine) Unwrap() error {
132133
}
133134

134135
func (e *ErrLine) Error() string {
135-
return fmt.Sprintf("line %d: %v", e.Line, e.Err)
136+
if e.Path == "" {
137+
return fmt.Sprintf("line %d: %v", e.Line, e.Err)
138+
}
139+
return fmt.Sprintf("line %s:%d: %v", e.Path, e.Line, e.Err)
136140
}
137141

138-
func NewErrLine(lineNo int, err error) error {
142+
func NewErrLine(path string, lineNo int, err error) error {
139143
return &ErrLine{
144+
Path: path,
140145
Line: lineNo,
141146
Err: err,
142147
}
@@ -161,7 +166,7 @@ func commentEmbedded(line string) (string, bool) {
161166
prefix := i + "gptscript:"
162167
cut, ok := strings.CutPrefix(line, prefix)
163168
if ok {
164-
return cut, ok
169+
return strings.TrimSpace(cut) + "\n", ok
165170
}
166171
}
167172
return line, false
@@ -183,18 +188,17 @@ func Parse(input io.Reader) ([]types.Tool, error) {
183188
}
184189

185190
line := scan.Text() + "\n"
191+
if embeddedLine, ok := commentEmbedded(line); ok {
192+
// Strip special comments to allow embedding the preamble in python or other interpreted languages
193+
line = embeddedLine
194+
}
186195

187196
if line == "---\n" {
188197
context.finish(&tools)
189198
continue
190199
}
191200

192201
if !context.inBody {
193-
// Strip special comments to allow embedding the preamble in python or other interpreted languages
194-
if newLine, ok := commentEmbedded(line); ok {
195-
line = newLine
196-
}
197-
198202
// If the very first line is #! just skip because this is a unix interpreter declaration
199203
if strings.HasPrefix(line, "#!") && lineNo == 1 {
200204
continue
@@ -212,7 +216,7 @@ func Parse(input io.Reader) ([]types.Tool, error) {
212216

213217
// Look for params
214218
if isParam, err := isParam(line, &context.tool); err != nil {
215-
return nil, NewErrLine(lineNo, err)
219+
return nil, NewErrLine("", lineNo, err)
216220
} else if isParam {
217221
continue
218222
}

pkg/types/tool.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import (
88
)
99

1010
const (
11-
DaemonPrefix = "#!sys.daemon "
11+
DaemonPrefix = "#!sys.daemon"
1212
CommandPrefix = "#!"
1313
)
1414

0 commit comments

Comments
 (0)