Skip to content

Commit 1bee58c

Browse files
committed
pkg/aflow: add package
Add infrastructure for defining LLM agent workflows. The workflows can be executed using the tools/syz-agent tool from the command line, or programmatically.
1 parent 4e1406b commit 1bee58c

File tree

15 files changed

+1122
-3
lines changed

15 files changed

+1122
-3
lines changed

go.mod

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ require (
1818
github.com/google/flatbuffers v25.9.23+incompatible
1919
github.com/google/generative-ai-go v0.20.1
2020
github.com/google/go-cmp v0.7.0
21+
github.com/google/jsonschema-go v0.3.0
2122
github.com/google/uuid v1.6.0
2223
github.com/gorilla/handlers v1.5.2
2324
github.com/ianlancetaylor/demangle v0.0.0-20250628045327-2d64ad6b7ec5
@@ -32,8 +33,10 @@ require (
3233
golang.org/x/sync v0.17.0
3334
golang.org/x/sys v0.37.0
3435
golang.org/x/tools v0.38.0
36+
google.golang.org/adk v0.0.0-20251024065725-37e04037159e
3537
google.golang.org/api v0.252.0
3638
google.golang.org/appengine/v2 v2.0.6
39+
google.golang.org/genai v1.32.0
3740
google.golang.org/genproto v0.0.0-20250603155806-513f23925822
3841
google.golang.org/grpc v1.76.0
3942
google.golang.org/protobuf v1.36.10
@@ -172,6 +175,7 @@ require (
172175
github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
173176
github.com/googleapis/gax-go/v2 v2.15.0 // indirect
174177
github.com/gordonklaus/ineffassign v0.1.0 // indirect
178+
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect
175179
github.com/gostaticanalysis/analysisutil v0.7.1 // indirect
176180
github.com/gostaticanalysis/comment v1.5.0 // indirect
177181
github.com/gostaticanalysis/forcetypeassert v0.2.0 // indirect
@@ -268,7 +272,7 @@ require (
268272
github.com/spf13/afero v1.14.0 // indirect
269273
github.com/spf13/cast v1.9.2 // indirect
270274
github.com/spf13/cobra v1.9.1 // indirect
271-
github.com/spf13/pflag v1.0.7 // indirect
275+
github.com/spf13/pflag v1.0.10 // indirect
272276
github.com/spf13/viper v1.20.1 // indirect
273277
github.com/spiffe/go-spiffe/v2 v2.6.0 // indirect
274278
github.com/ssgreg/nlreturn/v2 v2.2.1 // indirect
@@ -336,6 +340,8 @@ require (
336340
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 // indirect
337341
mvdan.cc/gofumpt v0.8.0 // indirect
338342
mvdan.cc/unparam v0.0.0-20250301125049-0df0534333a4 // indirect
343+
rsc.io/omap v1.2.0 // indirect
344+
rsc.io/ordered v1.1.1 // indirect
339345
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect
340346
sigs.k8s.io/randfill v1.0.0 // indirect
341347
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect

go.sum

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1033,6 +1033,8 @@ github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN
10331033
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
10341034
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
10351035
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
1036+
github.com/google/jsonschema-go v0.3.0 h1:6AH2TxVNtk3IlvkkhjrtbUc4S8AvO0Xii0DxIygDg+Q=
1037+
github.com/google/jsonschema-go v0.3.0/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE=
10361038
github.com/google/keep-sorted v0.6.1 h1:LNEdDKYxoXOrn4ZXC+FdUfJCVbUjhb2QPIBs5XISXCI=
10371039
github.com/google/keep-sorted v0.6.1/go.mod h1:JYy9vljs7P8b3QdPOQkywA+4u36FUHwsNITZIpJyPkE=
10381040
github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no=
@@ -1095,6 +1097,8 @@ github.com/gordonklaus/ineffassign v0.1.0 h1:y2Gd/9I7MdY1oEIt+n+rowjBNDcLQq3RsH5
10951097
github.com/gordonklaus/ineffassign v0.1.0/go.mod h1:Qcp2HIAYhR7mNUVSIxZww3Guk4it82ghYcEXIAk+QT0=
10961098
github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE=
10971099
github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w=
1100+
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo=
1101+
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA=
10981102
github.com/gostaticanalysis/analysisutil v0.7.1 h1:ZMCjoue3DtDWQ5WyU16YbjbQEQ3VuzwxALrpYd+HeKk=
10991103
github.com/gostaticanalysis/analysisutil v0.7.1/go.mod h1:v21E3hY37WKMGSnbsw2S/ojApNWb6C1//mXO48CXbVc=
11001104
github.com/gostaticanalysis/comment v1.4.1/go.mod h1:ih6ZxzTHLdadaiSnF5WY3dxUoXfXAlTaRzuaNDlSado=
@@ -1396,8 +1400,8 @@ github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
13961400
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
13971401
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
13981402
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
1399-
github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M=
1400-
github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
1403+
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
1404+
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
14011405
github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4=
14021406
github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4=
14031407
github.com/spiffe/go-spiffe/v2 v2.6.0 h1:l+DolpxNWYgruGQVV0xsfeya3CsC7m8iBzDnMpsbLuo=
@@ -1966,6 +1970,8 @@ gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6d
19661970
gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc=
19671971
gonum.org/v1/plot v0.9.0/go.mod h1:3Pcqqmp6RHvJI72kgb8fThyUnav364FOsdDo2aGW5lY=
19681972
gonum.org/v1/plot v0.10.1/go.mod h1:VZW5OlhkL1mysU9vaqNHnsy86inf6Ot+jB3r+BczCEo=
1973+
google.golang.org/adk v0.0.0-20251024065725-37e04037159e h1:5ZdMBo/z3nbrSjB/s5ajewcLDKIuBDEUOmiSimNgxk0=
1974+
google.golang.org/adk v0.0.0-20251024065725-37e04037159e/go.mod h1:ToWN2tT+8VZb7zxH4trB9Vxetlvig3u5uwtQpNCM8ZM=
19691975
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
19701976
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
19711977
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
@@ -2034,6 +2040,8 @@ google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCID
20342040
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
20352041
google.golang.org/appengine/v2 v2.0.6 h1:LvPZLGuchSBslPBp+LAhihBeGSiRh1myRoYK4NtuBIw=
20362042
google.golang.org/appengine/v2 v2.0.6/go.mod h1:WoEXGoXNfa0mLvaH5sV3ZSGXwVmy8yf7Z1JKf3J3wLI=
2043+
google.golang.org/genai v1.32.0 h1:kku/m3kWOncjnw8EIa2sgmrPLhaxFHaP+uqOq5ZckvI=
2044+
google.golang.org/genai v1.32.0/go.mod h1:7pAilaICJlQBonjKKJNhftDFv3SREhZcTe9F6nRcjbg=
20372045
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
20382046
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
20392047
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
@@ -2312,6 +2320,10 @@ mvdan.cc/gofumpt v0.8.0/go.mod h1:vEYnSzyGPmjvFkqJWtXkh79UwPWP9/HMxQdGEXZHjpg=
23122320
mvdan.cc/unparam v0.0.0-20250301125049-0df0534333a4 h1:WjUu4yQoT5BHT1w8Zu56SP8367OuBV5jvo+4Ulppyf8=
23132321
mvdan.cc/unparam v0.0.0-20250301125049-0df0534333a4/go.mod h1:rthT7OuvRbaGcd5ginj6dA2oLE7YNlta9qhBNNdCaLE=
23142322
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
2323+
rsc.io/omap v1.2.0 h1:c1M8jchnHbzmJALzGLclfH3xDWXrPxSUHXzH5C+8Kdw=
2324+
rsc.io/omap v1.2.0/go.mod h1:C8pkI0AWexHopQtZX+qiUeJGzvc8HkdgnsWK4/mAa00=
2325+
rsc.io/ordered v1.1.1 h1:1kZM6RkTmceJgsFH/8DLQvkCVEYomVDJfBRLT595Uak=
2326+
rsc.io/ordered v1.1.1/go.mod h1:evAi8739bWVBRG9aaufsjVc202+6okf8u2QeVL84BCM=
23152327
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
23162328
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
23172329
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=

pkg/aflow/action.go

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
// Copyright 2025 syzkaller project authors. All rights reserved.
2+
// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
3+
4+
package aflow
5+
6+
import (
7+
"fmt"
8+
"maps"
9+
"slices"
10+
11+
"google.golang.org/adk/agent"
12+
"google.golang.org/adk/agent/workflowagents/sequentialagent"
13+
"google.golang.org/adk/model"
14+
"google.golang.org/genai"
15+
)
16+
17+
type Action interface {
18+
create(*createContext) (agent.Agent, error)
19+
verify(*verifyContext)
20+
}
21+
22+
type Pipeline struct {
23+
// For logging/debugging.
24+
Name string
25+
// These actions are invoked sequentially,
26+
// but dataflow across actions is specified by their use
27+
// of variables in args/instructions/prompts.
28+
Actions []Action
29+
}
30+
31+
type createContext struct {
32+
llm model.LLM
33+
contentConfig *genai.GenerateContentConfig
34+
}
35+
36+
type verifyContext struct {
37+
state map[string]bool
38+
err error
39+
}
40+
41+
func (vctx *verifyContext) errorf(who, msg string, args ...any) {
42+
if vctx.err == nil {
43+
vctx.err = fmt.Errorf(fmt.Sprintf("action %v: %v", who, msg), args...)
44+
}
45+
}
46+
47+
func (vctx *verifyContext) requireNotEmpty(who, name, value string) {
48+
if value == "" {
49+
vctx.errorf(who, "%v must not be empty", name)
50+
}
51+
}
52+
53+
func (vctx *verifyContext) requireInput(who, name string) {
54+
if !vctx.state[name] {
55+
vctx.errorf(who, "no input %v, available inputs: %v",
56+
name, slices.Collect(maps.Keys(vctx.state)))
57+
}
58+
}
59+
60+
func (vctx *verifyContext) provideOutput(who, name string, unique bool) {
61+
if unique && vctx.state[name] {
62+
vctx.errorf(who, "output %v is already set", name)
63+
}
64+
vctx.state[name] = true
65+
}
66+
67+
func (p *Pipeline) create(cctx *createContext) (agent.Agent, error) {
68+
var agents []agent.Agent
69+
for _, sub := range p.Actions {
70+
subAgent, err := sub.create(cctx)
71+
if err != nil {
72+
return nil, err
73+
}
74+
agents = append(agents, subAgent)
75+
}
76+
return sequentialagent.New(sequentialagent.Config{
77+
AgentConfig: agent.Config{
78+
Name: p.Name,
79+
SubAgents: agents,
80+
},
81+
})
82+
}
83+
84+
func (p *Pipeline) verify(vctx *verifyContext) {
85+
for _, a := range p.Actions {
86+
a.verify(vctx)
87+
}
88+
}

pkg/aflow/execute.go

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
// Copyright 2025 syzkaller project authors. All rights reserved.
2+
// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
3+
4+
package aflow
5+
6+
import (
7+
"context"
8+
9+
"google.golang.org/adk/agent"
10+
"google.golang.org/adk/model"
11+
"google.golang.org/adk/model/gemini"
12+
"google.golang.org/adk/runner"
13+
"google.golang.org/adk/session"
14+
"google.golang.org/genai"
15+
)
16+
17+
const (
18+
largeModelName = "gemini-2.5-pro"
19+
smallModelName = "gemini-2.0-flash-lite" // cheaper/faster (for smoke testing)
20+
maxLLMCalls = 100
21+
)
22+
23+
type Callback interface {
24+
OnRequest(agentName string, req *model.LLMRequest) error
25+
OnEvent(*session.Event) error
26+
}
27+
28+
type Context interface {
29+
Workdir() string
30+
onRequest(agentName string, req *model.LLMRequest) error
31+
}
32+
33+
type executeContext struct {
34+
callback Callback
35+
workdir string
36+
}
37+
38+
func (ectx *executeContext) Workdir() string {
39+
return ectx.workdir
40+
}
41+
42+
func (ectx *executeContext) onRequest(agentName string, req *model.LLMRequest) error {
43+
return ectx.callback.OnRequest(agentName, req)
44+
}
45+
46+
type executeContextKeyType int
47+
48+
var executeContextKey executeContextKeyType = 0
49+
50+
func (flow *Flow) Execute(ctx context.Context, largeModel bool, workdir string,
51+
inputs any, events []*session.Event, cb Callback) (any, error) {
52+
modelName := smallModelName
53+
var thinkingConfig *genai.ThinkingConfig
54+
if largeModel {
55+
modelName = largeModelName
56+
thinkingConfig = &genai.ThinkingConfig{
57+
IncludeThoughts: true,
58+
ThinkingBudget: genai.Ptr[int32](-1),
59+
}
60+
}
61+
ectx := &executeContext{
62+
callback: cb,
63+
workdir: workdir,
64+
}
65+
ctx = context.WithValue(ctx, executeContextKey, ectx)
66+
llm, err := gemini.NewModel(ctx, modelName, &genai.ClientConfig{})
67+
if err != nil {
68+
return nil, err
69+
}
70+
cctx := &createContext{
71+
llm: llm,
72+
contentConfig: &genai.GenerateContentConfig{
73+
Temperature: genai.Ptr[float32](0),
74+
ThinkingConfig: thinkingConfig,
75+
},
76+
}
77+
root, err := flow.Root.create(cctx)
78+
if err != nil {
79+
return nil, err
80+
}
81+
sessions := session.InMemoryService()
82+
const (
83+
userID = "user"
84+
sessionID = "session"
85+
)
86+
createReq := &session.CreateRequest{
87+
AppName: flow.Name,
88+
UserID: userID,
89+
SessionID: sessionID,
90+
}
91+
createResp, err := sessions.Create(ctx, createReq)
92+
if err != nil {
93+
return nil, err
94+
}
95+
session := createResp.Session
96+
if flow.convertInputs(session.State(), inputs); err != nil {
97+
return nil, err
98+
}
99+
for _, ev := range events {
100+
if err := sessions.AppendEvent(ctx, session, ev); err != nil {
101+
return nil, err
102+
}
103+
}
104+
r, err := runner.New(runner.Config{
105+
AppName: flow.Name,
106+
Agent: root,
107+
SessionService: sessions,
108+
})
109+
if err != nil {
110+
return nil, err
111+
}
112+
cfg := agent.RunConfig{
113+
MaxLLMCalls: maxLLMCalls,
114+
}
115+
for ev, err := range r.Run(ctx, userID, sessionID, nil, cfg) {
116+
if err != nil {
117+
return nil, err
118+
}
119+
if err := cb.OnEvent(ev); err != nil {
120+
return nil, err
121+
}
122+
}
123+
return flow.extractOutputs(session.State())
124+
}

0 commit comments

Comments
 (0)