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 2025 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 trajectory
import (
"fmt"
"slices"
"strings"
"time"
)
// Span describes one step in an aflow workflow execution.
// Spans can be finished/unfinished (Finished field), and nested (Nesting field).
type Span struct {
// Seq is monotomically increasing for each new span in a workflow starting from 0.
Seq int
// Nesting represents hierarchical relation between spans.
// For example, SpanTool spans within SpanAgent have +1 nesting level.
Nesting int
Type SpanType
Name string // flow/action/tool name
Started time.Time
Finished time.Time
Error string // relevant if Finished is set
// Args/results for actions/tools.
Args map[string]any
Results map[string]any
// Agent invocation.
Instruction string
Prompt string
Reply string
// LLM invocation.
Thoughts string
}
type SpanType string
// Note: don't change string values of these consts w/o a good reason.
// They are stored in the dashboard database as strings.
const (
SpanFlow = SpanType("flow") // always the first outermost span
SpanAction = SpanType("action")
SpanAgent = SpanType("agent")
SpanLLM = SpanType("llm")
SpanTool = SpanType("tool")
// Logical grouping of several invocations of the same agent.
SpanAgentCandidates = SpanType("agent-candidates")
)
func (span *Span) String() string {
// This is used for console logging only.
sb := new(strings.Builder)
if span.Finished.IsZero() {
fmt.Fprintf(sb, "starting %v %v (%v/%v)...\n",
span.Type, span.Name, span.Nesting, span.Seq)
switch span.Type {
case SpanFlow:
case SpanAction:
case SpanAgent:
fmt.Fprintf(sb, "instruction:\n%v\nprompt:\n%v\n", span.Instruction, span.Prompt)
case SpanLLM:
case SpanTool:
printMap(sb, span.Args, "args")
default:
panic(fmt.Sprintf("unhandled span type %v", span.Type))
}
} else {
fmt.Fprintf(sb, "finished %v %v (%v/%v) in %v\n",
span.Type, span.Name, span.Nesting, span.Seq, span.Finished.Sub(span.Started))
switch span.Type {
case SpanFlow:
printMap(sb, span.Results, "results")
case SpanAction:
printMap(sb, span.Results, "results")
case SpanAgent:
if span.Results != nil {
printMap(sb, span.Results, "results")
}
fmt.Fprintf(sb, "reply:\n%v\n", span.Reply)
case SpanLLM:
if span.Thoughts != "" {
fmt.Fprintf(sb, "thoughts:\n%v\n", span.Thoughts)
}
case SpanTool:
printMap(sb, span.Results, "results")
default:
panic(fmt.Sprintf("unhandled span type %v", span.Type))
}
}
if span.Error != "" {
fmt.Fprintf(sb, "error:\n%v\n", span.Error)
}
return sb.String()
}
func printMap(sb *strings.Builder, m map[string]any, what string) {
fmt.Fprintf(sb, "%v:\n", what)
type nameVal struct {
name string
val any
}
var sorted []nameVal
for k, v := range m {
sorted = append(sorted, nameVal{k, v})
}
slices.SortFunc(sorted, func(a, b nameVal) int {
return strings.Compare(a.name, b.name)
})
for _, kv := range sorted {
fmt.Fprintf(sb, "\t%v: %v\n", kv.name, kv.val)
}
}
|