Skip to content

Commit 5baad80

Browse files
committed
feat: add Result/Any function variants for better API flexibility
- Added WithPrepFuncAny, WithExecFuncAny, WithPostFuncAny for simpler use cases - Updated all cookbook examples to use appropriate function variants - Updated documentation to explain when to use Result vs Any types - Removed tracing example due to Go 1.25+ compatibility issues with sonic dependency - Cleaned up compiled binaries from cookbook examples Breaking changes: None - all existing code remains compatible
1 parent f97372f commit 5baad80

File tree

30 files changed

+465
-1711
lines changed

30 files changed

+465
-1711
lines changed

README.md

Lines changed: 27 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -167,9 +167,10 @@ processNode := flyt.NewNode(
167167
Actions are strings returned by a node's Post phase that determine what happens next:
168168

169169
```go
170-
func (n *MyNode) Post(ctx context.Context, shared *flyt.SharedStore, prepResult, execResult flyt.Result) (flyt.Action, error) {
171-
// Type-safe access to result
172-
if execResult.AsBoolOr(false) {
170+
func (n *MyNode) Post(ctx context.Context, shared *flyt.SharedStore, prepResult, execResult any) (flyt.Action, error) {
171+
// Convert to Result for type-safe access
172+
result := flyt.R(execResult)
173+
if result.AsBoolOr(false) {
173174
return "success", nil // Go to node connected with "success"
174175
}
175176
return "retry", nil // Go to node connected with "retry"
@@ -245,16 +246,16 @@ user := User{
245246
shared.Set("user", user)
246247

247248
// Later, in a node's Prep function, bind it back to a struct
248-
func (n *MyNode) Prep(ctx context.Context, shared *flyt.SharedStore) (flyt.Result, error) {
249+
func (n *MyNode) Prep(ctx context.Context, shared *flyt.SharedStore) (any, error) {
249250
var user User
250251
err := shared.Bind("user", &user) // Binds stored data to struct
251252
if err != nil {
252-
return flyt.R(nil), err
253+
return nil, err
253254
}
254255
// Or use MustBind (panics on failure - use for required data)
255256
// shared.MustBind("user", &user)
256257

257-
return flyt.R(user), nil
258+
return user, nil
258259
}
259260

260261
// Utility methods
@@ -333,23 +334,25 @@ type CachedAPINode struct {
333334
cache map[string]any
334335
}
335336

336-
func (n *CachedAPINode) ExecFallback(prepResult flyt.Result, err error) (flyt.Result, error) {
337+
func (n *CachedAPINode) ExecFallback(prepResult any, err error) (any, error) {
337338
// Return cached data when API fails
338-
key := prepResult.MustString()
339+
result := flyt.R(prepResult)
340+
key := result.MustString()
339341
if cached, ok := n.cache[key]; ok {
340-
return flyt.R(cached), nil
342+
return cached, nil
341343
}
342344
// Return default value if no cache
343-
return flyt.R(map[string]any{"status": "unavailable"}), nil
345+
return map[string]any{"status": "unavailable"}, nil
344346
}
345347

346-
func (n *CachedAPINode) Exec(ctx context.Context, prepResult flyt.Result) (flyt.Result, error) {
347-
key := prepResult.MustString()
348+
func (n *CachedAPINode) Exec(ctx context.Context, prepResult any) (any, error) {
349+
result := flyt.R(prepResult)
350+
key := result.MustString()
348351
data, err := callAPI(key)
349352
if err == nil {
350353
n.cache[key] = data // Update cache on success
351354
}
352-
return flyt.R(data), err
355+
return data, err
353356
}
354357
```
355358

@@ -397,13 +400,13 @@ func NewRateLimitedNode(rps int) *RateLimitedNode {
397400
}
398401
}
399402

400-
func (n *RateLimitedNode) Exec(ctx context.Context, prepResult flyt.Result) (flyt.Result, error) {
403+
func (n *RateLimitedNode) Exec(ctx context.Context, prepResult any) (any, error) {
401404
if err := n.limiter.Wait(ctx); err != nil {
402-
return flyt.R(nil), err
405+
return nil, err
403406
}
404407
// Process with rate limiting
405-
data, err := process(prepResult.Value())
406-
return flyt.R(data), err
408+
data, err := process(prepResult)
409+
return data, err
407410
}
408411
```
409412

@@ -430,10 +433,10 @@ func (n *CustomRetryNode) GetWait() time.Duration {
430433
return time.Duration(n.attempts) * time.Second
431434
}
432435

433-
func (n *CustomRetryNode) Exec(ctx context.Context, prepResult flyt.Result) (flyt.Result, error) {
436+
func (n *CustomRetryNode) Exec(ctx context.Context, prepResult any) (any, error) {
434437
n.attempts++
435-
data, err := callAPI(prepResult.Value())
436-
return flyt.R(data), err
438+
data, err := callAPI(prepResult)
439+
return data, err
437440
}
438441
```
439442

@@ -443,9 +446,9 @@ Process multiple items concurrently:
443446

444447
```go
445448
// Simple batch node for processing items
446-
processFunc := func(ctx context.Context, item any) (flyt.Result, error) {
449+
processFunc := func(ctx context.Context, item any) (any, error) {
447450
// Process each item
448-
return flyt.R(fmt.Sprintf("processed: %v", item)), nil
451+
return fmt.Sprintf("processed: %v", item), nil
449452
}
450453

451454
batchNode := flyt.NewBatchNode(processFunc, true) // true for concurrent
@@ -465,9 +468,9 @@ config := &flyt.BatchConfig{
465468
CountKey: "total", // Custom key for processed count
466469
}
467470

468-
processFunc := func(ctx context.Context, item any) (flyt.Result, error) {
471+
processFunc := func(ctx context.Context, item any) (any, error) {
469472
data, err := processItem(item)
470-
return flyt.R(data), err
473+
return data, err
471474
}
472475

473476
batchNode := flyt.NewBatchNodeWithConfig(processFunc, true, config)

cookbook/agent/nodes.go

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import (
1313
// It uses an LLM to analyze the question and context to make this decision.
1414
func NewDecideActionNode(llm *LLM) flyt.Node {
1515
return flyt.NewNode(
16-
flyt.WithPrepFunc(func(ctx context.Context, shared *flyt.SharedStore) (any, error) {
16+
flyt.WithPrepFuncAny(func(ctx context.Context, shared *flyt.SharedStore) (any, error) {
1717
question := shared.GetString("question")
1818
context := shared.GetStringOr("context", "No previous search")
1919
searchCount := shared.GetInt("search_count")
@@ -24,7 +24,7 @@ func NewDecideActionNode(llm *LLM) flyt.Node {
2424
"search_count": searchCount,
2525
}, nil
2626
}),
27-
flyt.WithExecFunc(func(ctx context.Context, prepResult any) (any, error) {
27+
flyt.WithExecFuncAny(func(ctx context.Context, prepResult any) (any, error) {
2828
data := prepResult.(map[string]any)
2929
question := data["question"].(string)
3030
contextStr := data["context"].(string)
@@ -72,7 +72,7 @@ Example responses:
7272
"searchCount": searchCount,
7373
}, nil
7474
}),
75-
flyt.WithPostFunc(func(ctx context.Context, shared *flyt.SharedStore, prepResult, execResult any) (flyt.Action, error) {
75+
flyt.WithPostFuncAny(func(ctx context.Context, shared *flyt.SharedStore, prepResult, execResult any) (flyt.Action, error) {
7676
data := execResult.(map[string]any)
7777
response := data["response"].(string)
7878

@@ -104,14 +104,14 @@ Example responses:
104104
// NewSearchWebNode creates a node that searches the web for information
105105
func NewSearchWebNode(searcher Searcher) flyt.Node {
106106
return flyt.NewNode(
107-
flyt.WithPrepFunc(func(ctx context.Context, shared *flyt.SharedStore) (any, error) {
107+
flyt.WithPrepFuncAny(func(ctx context.Context, shared *flyt.SharedStore) (any, error) {
108108
query := shared.GetString("search_query")
109109

110110
return map[string]any{
111111
"query": query,
112112
}, nil
113113
}),
114-
flyt.WithExecFunc(func(ctx context.Context, prepResult any) (any, error) {
114+
flyt.WithExecFuncAny(func(ctx context.Context, prepResult any) (any, error) {
115115
data := prepResult.(map[string]any)
116116
query := data["query"].(string)
117117

@@ -128,7 +128,7 @@ func NewSearchWebNode(searcher Searcher) flyt.Node {
128128
"query": query,
129129
}, nil
130130
}),
131-
flyt.WithPostFunc(func(ctx context.Context, shared *flyt.SharedStore, prepResult, execResult any) (flyt.Action, error) {
131+
flyt.WithPostFuncAny(func(ctx context.Context, shared *flyt.SharedStore, prepResult, execResult any) (flyt.Action, error) {
132132
data := execResult.(map[string]any)
133133
results := data["results"].(string)
134134
query := data["query"].(string)
@@ -148,7 +148,7 @@ func NewSearchWebNode(searcher Searcher) flyt.Node {
148148
// NewAnswerQuestionNode creates a node that generates the final answer
149149
func NewAnswerQuestionNode(llm *LLM) flyt.Node {
150150
return flyt.NewNode(
151-
flyt.WithPrepFunc(func(ctx context.Context, shared *flyt.SharedStore) (any, error) {
151+
flyt.WithPrepFuncAny(func(ctx context.Context, shared *flyt.SharedStore) (any, error) {
152152
question := shared.GetString("question")
153153
context := shared.GetString("context")
154154

@@ -157,7 +157,7 @@ func NewAnswerQuestionNode(llm *LLM) flyt.Node {
157157
"context": context,
158158
}, nil
159159
}),
160-
flyt.WithExecFunc(func(ctx context.Context, prepResult any) (any, error) {
160+
flyt.WithExecFuncAny(func(ctx context.Context, prepResult any) (any, error) {
161161
data := prepResult.(map[string]any)
162162
question := data["question"].(string)
163163
contextStr := ""
@@ -181,7 +181,7 @@ Provide a short concise answer using the research results.`, question, contextSt
181181

182182
return answer, nil
183183
}),
184-
flyt.WithPostFunc(func(ctx context.Context, shared *flyt.SharedStore, prepResult, execResult any) (flyt.Action, error) {
184+
flyt.WithPostFuncAny(func(ctx context.Context, shared *flyt.SharedStore, prepResult, execResult any) (flyt.Action, error) {
185185
answer := execResult.(string)
186186

187187
shared.Set("answer", answer)

cookbook/chat/main.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import (
1414
// CreateChatNode creates an interactive chat node using the NewNode helper
1515
func CreateChatNode(llm *LLM) flyt.Node {
1616
return flyt.NewNode(
17-
flyt.WithPrepFunc(func(ctx context.Context, shared *flyt.SharedStore) (any, error) {
17+
flyt.WithPrepFuncAny(func(ctx context.Context, shared *flyt.SharedStore) (any, error) {
1818
// Initialize messages if this is the first run
1919
messages, ok := shared.Get("messages")
2020
if !ok {
@@ -47,7 +47,7 @@ func CreateChatNode(llm *LLM) flyt.Node {
4747

4848
return messageList, nil
4949
}),
50-
flyt.WithExecFunc(func(ctx context.Context, prepResult any) (any, error) {
50+
flyt.WithExecFuncAny(func(ctx context.Context, prepResult any) (any, error) {
5151
if prepResult == nil {
5252
return nil, nil
5353
}
@@ -62,7 +62,7 @@ func CreateChatNode(llm *LLM) flyt.Node {
6262

6363
return response, nil
6464
}),
65-
flyt.WithPostFunc(func(ctx context.Context, shared *flyt.SharedStore, prepResult, execResult any) (flyt.Action, error) {
65+
flyt.WithPostFuncAny(func(ctx context.Context, shared *flyt.SharedStore, prepResult, execResult any) (flyt.Action, error) {
6666
if prepResult == nil || execResult == nil {
6767
fmt.Println("\nGoodbye!")
6868
return "end", nil

cookbook/llm-streaming/main.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import (
1414
// CreateStreamNode creates a node that streams LLM responses
1515
func CreateStreamNode(llm *LLM) flyt.Node {
1616
return flyt.NewNode(
17-
flyt.WithPrepFunc(func(ctx context.Context, shared *flyt.SharedStore) (any, error) {
17+
flyt.WithPrepFuncAny(func(ctx context.Context, shared *flyt.SharedStore) (any, error) {
1818
// Get messages from shared store
1919
messages, ok := shared.Get("messages")
2020
if !ok {
@@ -25,7 +25,7 @@ func CreateStreamNode(llm *LLM) flyt.Node {
2525
"messages": messages.([]Message),
2626
}, nil
2727
}),
28-
flyt.WithExecFunc(func(ctx context.Context, prepResult any) (any, error) {
28+
flyt.WithExecFuncAny(func(ctx context.Context, prepResult any) (any, error) {
2929
data := prepResult.(map[string]any)
3030
messages := data["messages"].([]Message)
3131

@@ -48,7 +48,7 @@ func CreateStreamNode(llm *LLM) flyt.Node {
4848

4949
return response.String(), nil
5050
}),
51-
flyt.WithPostFunc(func(ctx context.Context, shared *flyt.SharedStore, prepResult, execResult any) (flyt.Action, error) {
51+
flyt.WithPostFuncAny(func(ctx context.Context, shared *flyt.SharedStore, prepResult, execResult any) (flyt.Action, error) {
5252
if execResult != nil {
5353
// Add assistant's response to messages
5454
messages, _ := shared.Get("messages")
@@ -69,7 +69,7 @@ func CreateStreamNode(llm *LLM) flyt.Node {
6969
func CreateInteractiveChatFlow(llm *LLM) *flyt.Flow {
7070
// Create input node
7171
inputNode := flyt.NewNode(
72-
flyt.WithPrepFunc(func(ctx context.Context, shared *flyt.SharedStore) (any, error) {
72+
flyt.WithPrepFuncAny(func(ctx context.Context, shared *flyt.SharedStore) (any, error) {
7373
// Initialize messages if this is the first run
7474
messages, ok := shared.Get("messages")
7575
if !ok {
@@ -79,7 +79,7 @@ func CreateInteractiveChatFlow(llm *LLM) *flyt.Flow {
7979
}
8080
return nil, nil
8181
}),
82-
flyt.WithExecFunc(func(ctx context.Context, prepResult any) (any, error) {
82+
flyt.WithExecFuncAny(func(ctx context.Context, prepResult any) (any, error) {
8383
reader := bufio.NewReader(os.Stdin)
8484
fmt.Print("\n💭 You: ")
8585
input, err := reader.ReadString('\n')
@@ -94,7 +94,7 @@ func CreateInteractiveChatFlow(llm *LLM) *flyt.Flow {
9494

9595
return input, nil
9696
}),
97-
flyt.WithPostFunc(func(ctx context.Context, shared *flyt.SharedStore, prepResult, execResult any) (flyt.Action, error) {
97+
flyt.WithPostFuncAny(func(ctx context.Context, shared *flyt.SharedStore, prepResult, execResult any) (flyt.Action, error) {
9898
if execResult == nil {
9999
fmt.Println("\n👋 Goodbye!")
100100
return "exit", nil

cookbook/mcp/nodes.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import (
1212
// NewGetToolsNode creates a node that discovers available tools from the MCP server
1313
func NewGetToolsNode(mcpClient *MCPClient) flyt.Node {
1414
return flyt.NewNode(
15-
flyt.WithExecFunc(func(ctx context.Context, input any) (any, error) {
15+
flyt.WithExecFuncAny(func(ctx context.Context, input any) (any, error) {
1616
fmt.Println("🔍 Getting available tools...")
1717

1818
tools, err := mcpClient.GetTools(ctx)
@@ -29,7 +29,7 @@ func NewGetToolsNode(mcpClient *MCPClient) flyt.Node {
2929

3030
return flyt.R(tools), nil
3131
}),
32-
flyt.WithPostFunc(func(ctx context.Context, shared *flyt.SharedStore, prepResult, execResult any) (flyt.Action, error) {
32+
flyt.WithPostFuncAny(func(ctx context.Context, shared *flyt.SharedStore, prepResult, execResult any) (flyt.Action, error) {
3333
if result, ok := execResult.(flyt.Result); ok {
3434
shared.Set("tools", result.Value())
3535
} else {
@@ -43,7 +43,7 @@ func NewGetToolsNode(mcpClient *MCPClient) flyt.Node {
4343
// NewDecideToolNode creates a node that uses LLM with function calling to select appropriate tool
4444
func NewDecideToolNode(llm *LLM) flyt.Node {
4545
return flyt.NewNode(
46-
flyt.WithPrepFunc(func(ctx context.Context, shared *flyt.SharedStore) (any, error) {
46+
flyt.WithPrepFuncAny(func(ctx context.Context, shared *flyt.SharedStore) (any, error) {
4747
tools, _ := shared.Get("tools")
4848
messages, ok := shared.Get("messages")
4949
if !ok {
@@ -55,7 +55,7 @@ func NewDecideToolNode(llm *LLM) flyt.Node {
5555
"messages": messages.([]map[string]interface{}),
5656
}), nil
5757
}),
58-
flyt.WithExecFunc(func(ctx context.Context, prepResult any) (any, error) {
58+
flyt.WithExecFuncAny(func(ctx context.Context, prepResult any) (any, error) {
5959
prepRes, ok := prepResult.(flyt.Result)
6060
if !ok {
6161
// Handle legacy case
@@ -119,7 +119,7 @@ func NewDecideToolNode(llm *LLM) flyt.Node {
119119
"done": true,
120120
}), nil
121121
}),
122-
flyt.WithPostFunc(func(ctx context.Context, shared *flyt.SharedStore, prepResult, execResult any) (flyt.Action, error) {
122+
flyt.WithPostFuncAny(func(ctx context.Context, shared *flyt.SharedStore, prepResult, execResult any) (flyt.Action, error) {
123123
var data map[string]interface{}
124124
if result, ok := execResult.(flyt.Result); ok {
125125
data = result.MustMap()
@@ -144,15 +144,15 @@ func NewDecideToolNode(llm *LLM) flyt.Node {
144144
// NewExecuteToolNode creates a node that executes the selected tool with parameters
145145
func NewExecuteToolNode(mcpClient *MCPClient) flyt.Node {
146146
return flyt.NewNode(
147-
flyt.WithPrepFunc(func(ctx context.Context, shared *flyt.SharedStore) (any, error) {
147+
flyt.WithPrepFuncAny(func(ctx context.Context, shared *flyt.SharedStore) (any, error) {
148148
functionCall, _ := shared.Get("function_call")
149149
messages, _ := shared.Get("messages")
150150
return flyt.R(map[string]interface{}{
151151
"function_call": functionCall.(map[string]interface{}),
152152
"messages": messages.([]map[string]interface{}),
153153
}), nil
154154
}),
155-
flyt.WithExecFunc(func(ctx context.Context, prepResult any) (any, error) {
155+
flyt.WithExecFuncAny(func(ctx context.Context, prepResult any) (any, error) {
156156
prepRes, ok := prepResult.(flyt.Result)
157157
if !ok {
158158
prepRes = flyt.R(prepResult)
@@ -187,7 +187,7 @@ func NewExecuteToolNode(mcpClient *MCPClient) flyt.Node {
187187
"messages": messages,
188188
}), nil
189189
}),
190-
flyt.WithPostFunc(func(ctx context.Context, shared *flyt.SharedStore, prepResult, execResult any) (flyt.Action, error) {
190+
flyt.WithPostFuncAny(func(ctx context.Context, shared *flyt.SharedStore, prepResult, execResult any) (flyt.Action, error) {
191191
var data map[string]interface{}
192192
if result, ok := execResult.(flyt.Result); ok {
193193
data = result.MustMap()

0 commit comments

Comments
 (0)