Skip to content

Commit a77de0d

Browse files
fix(adk): dedupe empty model context snapshots (#1115)
1 parent 33648fd commit a77de0d

3 files changed

Lines changed: 65 additions & 7 deletions

File tree

adk/chatmodel.go

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -93,21 +93,48 @@ func copyModelContextEvent(event *ModelContextEvent) *ModelContextEvent {
9393
return nil
9494
}
9595
return &ModelContextEvent{
96-
ToolInfos: append([]*schema.ToolInfo{}, event.ToolInfos...),
97-
DeferredToolInfos: append([]*schema.ToolInfo{}, event.DeferredToolInfos...),
96+
ToolInfos: cloneToolInfos(event.ToolInfos),
97+
DeferredToolInfos: cloneToolInfos(event.DeferredToolInfos),
9898
}
9999
}
100100

101+
func cloneToolInfos(infos []*schema.ToolInfo) []*schema.ToolInfo {
102+
if infos == nil {
103+
return nil
104+
}
105+
return append([]*schema.ToolInfo{}, infos...)
106+
}
107+
108+
func modelContextEventEqual(a, b *ModelContextEvent) bool {
109+
if a == nil || b == nil {
110+
return a == b
111+
}
112+
return toolInfosEqual(a.ToolInfos, b.ToolInfos) &&
113+
toolInfosEqual(a.DeferredToolInfos, b.DeferredToolInfos)
114+
}
115+
116+
func toolInfosEqual(a, b []*schema.ToolInfo) bool {
117+
if len(a) != len(b) {
118+
return false
119+
}
120+
for i := range a {
121+
if !reflect.DeepEqual(a[i], b[i]) {
122+
return false
123+
}
124+
}
125+
return true
126+
}
127+
101128
func syncModelContextSessionEvent[M MessageType](ctx context.Context, state *TypedChatModelAgentState[M]) {
102129
execCtx := getTypedChatModelAgentExecCtx[M](ctx)
103130
if execCtx == nil || !execCtx.sessionEvents || state == nil {
104131
return
105132
}
106133
current := &ModelContextEvent{
107-
ToolInfos: append([]*schema.ToolInfo{}, state.ToolInfos...),
108-
DeferredToolInfos: append([]*schema.ToolInfo{}, state.DeferredToolInfos...),
134+
ToolInfos: cloneToolInfos(state.ToolInfos),
135+
DeferredToolInfos: cloneToolInfos(state.DeferredToolInfos),
109136
}
110-
changed := !execCtx.sawModelContext || !reflect.DeepEqual(execCtx.lastModelContext, current)
137+
changed := !execCtx.sawModelContext || !modelContextEventEqual(execCtx.lastModelContext, current)
111138
if changed {
112139
execCtx.send(ctx, &TypedAgentEvent[M]{
113140
SessionEventVariant: &SessionEventVariant[M]{

adk/session.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1667,8 +1667,8 @@ func replayDurableContextEvents[M MessageType](events []*SessionEvent[M]) (*reco
16671667
return nil, fmt.Errorf("reconstruct: %w", err)
16681668
}
16691669
if events[i] != nil && events[i].ModelContext != nil {
1670-
state.ToolInfos = append([]*schema.ToolInfo{}, events[i].ModelContext.ToolInfos...)
1671-
state.DeferredToolInfos = append([]*schema.ToolInfo{}, events[i].ModelContext.DeferredToolInfos...)
1670+
state.ToolInfos = cloneToolInfos(events[i].ModelContext.ToolInfos)
1671+
state.DeferredToolInfos = cloneToolInfos(events[i].ModelContext.DeferredToolInfos)
16721672
state.sawModelContext = true
16731673
}
16741674
}

adk/session_test.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -610,6 +610,37 @@ func TestRunnerSessionModePrependsCommittedMessagesOnce(t *testing.T) {
610610
assert.Equal(t, "value", secondAgent.values[0]["override"])
611611
}
612612

613+
func TestRunnerSessionModeSkipsDuplicateEmptyModelContext(t *testing.T) {
614+
ctx := context.Background()
615+
store := newSessionHelperStore()
616+
sessionID := "runner-model-context-session"
617+
model := &leadingSystemTestModel[*schema.Message]{response: schema.AssistantMessage("ok", nil)}
618+
agent, err := NewChatModelAgent(ctx, &ChatModelAgentConfig{
619+
Name: "runner-model-context-agent",
620+
Description: "runner model context agent",
621+
Instruction: "You are a helpful assistant.",
622+
Model: model,
623+
})
624+
require.NoError(t, err)
625+
626+
runner := NewRunner(ctx, RunnerConfig{
627+
Agent: agent,
628+
SessionID: sessionID,
629+
SessionStore: store,
630+
})
631+
drainSessionEvents(t, runner.Query(ctx, "first"))
632+
drainSessionEvents(t, runner.Query(ctx, "second"))
633+
634+
result, err := store.LoadEventsForSession(ctx, sessionID, &LoadSessionEventsRequest{
635+
Kinds: []SessionEventKind{SessionEventModelContext},
636+
})
637+
require.NoError(t, err)
638+
require.Len(t, result.Events, 1)
639+
require.NotNil(t, result.Events[0].ModelContext)
640+
assert.Empty(t, result.Events[0].ModelContext.ToolInfos)
641+
assert.Empty(t, result.Events[0].ModelContext.DeferredToolInfos)
642+
}
643+
613644
func TestAttack_SessionEventIDGeneratorCoversRunnerEvents(t *testing.T) {
614645
ctx := context.Background()
615646
store := newSessionHelperStore()

0 commit comments

Comments
 (0)