aboutsummaryrefslogtreecommitdiffstats
path: root/pkg/aflow/llm_tool_test.go
blob: ddc732967e6f9ad4cb9274491be32b1abb40c09f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
// Copyright 2026 syzkaller project authors. All rights reserved.
// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.

package aflow

import (
	"net/http"
	"strings"
	"testing"

	"github.com/stretchr/testify/assert"
	"google.golang.org/genai"
)

func TestLLMTool(t *testing.T) {
	type inputs struct {
		Input int
	}
	type outputs struct {
		Reply string
	}
	type toolArgs struct {
		Something string `jsonschema:"something"`
	}
	testFlow[inputs, outputs](t, map[string]any{"Input": 42}, map[string]any{"Reply": "YES"},
		Pipeline(
			&LLMAgent{
				Name:        "smarty",
				Model:       "model",
				TaskType:    FormalReasoningTask,
				Reply:       "Reply",
				Instruction: "Do something!",
				Prompt:      "Prompt",
				Tools: []Tool{
					&LLMTool{
						Name:        "researcher",
						Model:       "sub-agent-model",
						TaskType:    FormalReasoningTask,
						Description: "researcher description",
						Instruction: "researcher instruction",
						Tools: []Tool{
							NewFuncTool("researcher-tool", func(ctx *Context, state inputs, args toolArgs) (struct{}, error) {
								// State passed all the way from the workflow inputs.
								assert.Equal(t, state.Input, 42)
								assert.True(t, strings.HasPrefix(args.Something, "subtool input"),
									"args.Something=%q", args.Something)
								return struct{}{}, nil
							}, "researcher-tool description"),
						},
					},
				},
			},
		),
		[]any{
			// Main agent calls the tool sub-agent.
			&genai.Part{
				FunctionCall: &genai.FunctionCall{
					ID:   "id0",
					Name: "researcher",
					Args: map[string]any{
						"Question": "What do you think?",
					},
				},
			},
			// Sub-agent calls own tool.
			&genai.Part{
				FunctionCall: &genai.FunctionCall{
					ID:   "id1",
					Name: "researcher-tool",
					Args: map[string]any{
						"Something": "subtool input 1",
					},
				},
			},
			// Sub-agent returns result.
			genai.NewPartFromText("Nothing."),
			// Repeat the same one more time.
			&genai.Part{
				FunctionCall: &genai.FunctionCall{
					ID:   "id2",
					Name: "researcher",
					Args: map[string]any{
						"Question": "But really?",
					},
				},
			},
			&genai.Part{
				FunctionCall: &genai.FunctionCall{
					ID:   "id3",
					Name: "researcher-tool",
					Args: map[string]any{
						"Something": "subtool input 2",
					},
				},
			},
			// Now model input token overflow.
			&genai.Part{
				FunctionCall: &genai.FunctionCall{
					ID:   "id4",
					Name: "researcher-tool",
					Args: map[string]any{
						"Something": "subtool input 3",
					},
				},
			},
			genai.APIError{
				Code:    http.StatusBadRequest,
				Message: "The input token count exceeds the maximum number of tokens allowed 1048576.",
			},
			genai.NewPartFromText("Still nothing."),
			// Main returns result.
			genai.NewPartFromText("YES"),
		},
		nil,
	)
}