Skip to content

Commit d5a4524

Browse files
authored
agent: add generic getter for typed runtime state retrieval (#765)
- Implemented GetRuntimeStateValue to retrieve typed values from the runtime state in RunOptions. - Added comprehensive unit tests covering various scenarios including key not found, nil options, type matching, type mismatch, and complex types. - Enhanced test coverage for runtime state retrieval with different data types such as strings, integers, booleans, slices, maps, and structs.
1 parent 86472bd commit d5a4524

File tree

2 files changed

+159
-0
lines changed

2 files changed

+159
-0
lines changed

agent/invocation.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,35 @@ func WithRuntimeState(state map[string]any) RunOption {
139139
}
140140
}
141141

142+
// GetRuntimeStateValue retrieves a typed value from the runtime state.
143+
//
144+
// Returns the typed value and true if the key exists and the type matches,
145+
// or the zero value and false otherwise.
146+
//
147+
// Example:
148+
//
149+
// if userID, ok := GetRuntimeStateValue[string](&inv.RunOptions, "user_id"); ok {
150+
// log.Printf("User ID: %s", userID)
151+
// }
152+
// if roomID, ok := GetRuntimeStateValue[int](&inv.RunOptions, "room_id"); ok {
153+
// log.Printf("Room ID: %d", roomID)
154+
// }
155+
func GetRuntimeStateValue[T any](opts *RunOptions, key string) (T, bool) {
156+
var zero T
157+
if opts == nil || opts.RuntimeState == nil {
158+
return zero, false
159+
}
160+
val, ok := opts.RuntimeState[key]
161+
if !ok {
162+
return zero, false
163+
}
164+
typedVal, ok := val.(T)
165+
if !ok {
166+
return zero, false
167+
}
168+
return typedVal, true
169+
}
170+
142171
// WithKnowledgeFilter sets the metadata filter for the RunOptions.
143172
func WithKnowledgeFilter(filter map[string]any) RunOption {
144173
return func(opts *RunOptions) {

agent/invocation_options_test.go

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,136 @@ func TestWithInvocationRunOptions(t *testing.T) {
100100
assert.Equal(t, "value1", inv.RunOptions.RuntimeState["key1"])
101101
}
102102

103+
func TestGetRuntimeStateValue(t *testing.T) {
104+
t.Run("key not found", func(t *testing.T) {
105+
opts := &RunOptions{
106+
RuntimeState: map[string]any{},
107+
}
108+
val, ok := GetRuntimeStateValue[string](opts, "nonexistent")
109+
assert.False(t, ok)
110+
assert.Equal(t, "", val)
111+
})
112+
113+
t.Run("nil RunOptions", func(t *testing.T) {
114+
var opts *RunOptions
115+
val, ok := GetRuntimeStateValue[string](opts, "key")
116+
assert.False(t, ok)
117+
assert.Equal(t, "", val)
118+
})
119+
120+
t.Run("nil RuntimeState", func(t *testing.T) {
121+
opts := &RunOptions{}
122+
val, ok := GetRuntimeStateValue[string](opts, "key")
123+
assert.False(t, ok)
124+
assert.Equal(t, "", val)
125+
})
126+
127+
t.Run("matching type", func(t *testing.T) {
128+
opts := &RunOptions{
129+
RuntimeState: map[string]any{
130+
"user_id": "12345",
131+
"room_id": 678,
132+
"config": true,
133+
"score": 3.14,
134+
},
135+
}
136+
137+
// Test string.
138+
userID, ok := GetRuntimeStateValue[string](opts, "user_id")
139+
assert.True(t, ok)
140+
assert.Equal(t, "12345", userID)
141+
142+
// Test int.
143+
roomID, ok := GetRuntimeStateValue[int](opts, "room_id")
144+
assert.True(t, ok)
145+
assert.Equal(t, 678, roomID)
146+
147+
// Test bool.
148+
config, ok := GetRuntimeStateValue[bool](opts, "config")
149+
assert.True(t, ok)
150+
assert.Equal(t, true, config)
151+
152+
// Test float64.
153+
score, ok := GetRuntimeStateValue[float64](opts, "score")
154+
assert.True(t, ok)
155+
assert.Equal(t, 3.14, score)
156+
})
157+
158+
t.Run("type mismatch", func(t *testing.T) {
159+
opts := &RunOptions{
160+
RuntimeState: map[string]any{
161+
"value": "hello",
162+
},
163+
}
164+
165+
// Try to get as int when it's actually string.
166+
intVal, ok := GetRuntimeStateValue[int](opts, "value")
167+
assert.False(t, ok)
168+
assert.Equal(t, 0, intVal)
169+
170+
// Try to get as string when it's actually int.
171+
opts.RuntimeState["number"] = 42
172+
strVal, ok := GetRuntimeStateValue[string](opts, "number")
173+
assert.False(t, ok)
174+
assert.Equal(t, "", strVal)
175+
})
176+
177+
t.Run("slice type", func(t *testing.T) {
178+
opts := &RunOptions{
179+
RuntimeState: map[string]any{
180+
"tags": []string{"tag1", "tag2", "tag3"},
181+
},
182+
}
183+
184+
tags, ok := GetRuntimeStateValue[[]string](opts, "tags")
185+
assert.True(t, ok)
186+
assert.Equal(t, []string{"tag1", "tag2", "tag3"}, tags)
187+
})
188+
189+
t.Run("map type", func(t *testing.T) {
190+
opts := &RunOptions{
191+
RuntimeState: map[string]any{
192+
"metadata": map[string]string{
193+
"key1": "value1",
194+
"key2": "value2",
195+
},
196+
},
197+
}
198+
199+
metadata, ok := GetRuntimeStateValue[map[string]string](opts, "metadata")
200+
assert.True(t, ok)
201+
assert.Equal(t, "value1", metadata["key1"])
202+
assert.Equal(t, "value2", metadata["key2"])
203+
})
204+
205+
t.Run("complex struct type", func(t *testing.T) {
206+
type UserContext struct {
207+
UserID string
208+
RoomID int
209+
Metadata map[string]string
210+
}
211+
212+
ctx := UserContext{
213+
UserID: "user-123",
214+
RoomID: 456,
215+
Metadata: map[string]string{
216+
"key1": "value1",
217+
},
218+
}
219+
opts := &RunOptions{
220+
RuntimeState: map[string]any{
221+
"user_context": ctx,
222+
},
223+
}
224+
225+
retrieved, ok := GetRuntimeStateValue[UserContext](opts, "user_context")
226+
require.True(t, ok)
227+
assert.Equal(t, ctx.UserID, retrieved.UserID)
228+
assert.Equal(t, ctx.RoomID, retrieved.RoomID)
229+
assert.Equal(t, ctx.Metadata, retrieved.Metadata)
230+
})
231+
}
232+
103233
func TestWithInvocationTransferInfo(t *testing.T) {
104234
transferInfo := &TransferInfo{
105235
TargetAgentName: "target-agent",

0 commit comments

Comments
 (0)