feat(adk): auto memory middleware#987
Draft
N3kox wants to merge 55 commits into
Draft
Conversation
|
|
||
| func (a *ChatModelAgent) applyBeforeAgent(ctx context.Context, ec *execContext) (context.Context, *execContext, error) { | ||
| func (a *ChatModelAgent) applyBeforeAgent(ctx context.Context, ec *execContext, agentInput *AgentInput) (context.Context, *execContext, error) { | ||
| runCtx := &ChatModelAgentContext{ |
There was a problem hiding this comment.
🚨 Breaking API Changes Detected
Package: github.com/cloudwego/eino/adk
Incompatible changes:
- ChatModelAgentMiddleware.AfterAgent: added
Review Guidelines
Please ensure that:
- The changes are absolutely necessary
- They are properly documented
- Migration guides are provided if needed
6544882 to
7a50a8c
Compare
ce5ea9f to
b205f20
Compare
b205f20 to
b3675fb
Compare
b342ae0 to
883b949
Compare
Change-Id: I09b9754cf51f10fe662fb6cb33935ad92a4d1656
…mance
- Add HumanReadableSerializer that produces human-readable JSON output
- Add GobSerializer for comparison benchmarks
- Refactor serialization_test.go to use table-driven tests for both serializers
- Add comprehensive benchmarks comparing InternalSerializer, HumanReadableSerializer, and GobSerializer
Performance improvements over InternalSerializer:
- 30-68% faster marshal/unmarshal operations
- 17-49% less memory allocation
- 30-76% fewer allocations
- 33-59% smaller serialized output size
The HumanReadableSerializer uses standard JSON encoding with type annotations
only for interface{} fields, making the output both human-readable and
type-preserving for round-trip serialization.~
Change-Id: Ifeae12484fc73b74067a0ac3a496e73e0674b975
Change-Id: Iecbcfd8ab5905e61df9a5578e8d3c99387dace85
Change-Id: I5ac5d8b60be92a28d1e57929f03fa4201649fbd0
Change-Id: I318ff16a6bd81bb57b38791eeea17b3385b1fde4
Change-Id: Iefb047967d4cb4796edd363e7e6ba0a9c4f49148
Change-Id: I3de2ba37914fe779cd880b38ce067acf5fbde7c8
The runner previously wrote the interrupt checkpoint inline, before the session event persister had flushed. This risks a checkpoint that references events not yet durable in the SessionStore. Introduce deferredRunnerCheckpoint: on the interrupt/cancel path the checkpoint payload is captured but not written until finalize() confirms persister.closeAndWait succeeded. If event persistence failed, the checkpoint write is skipped entirely (fail-closed). Also adds regression tests for checkpoint ordering invariants and the InMemoryStore checkpoint round-trip. Change-Id: Ib29263add4a261513f1349a9173a913c74ee65ef
Resume previously assumed the agent_tool interrupt state was always the JSON envelope (agentToolInterruptState) introduced for SessionID-based event filtering. Pre-envelope checkpoints stored raw gob bridge bytes, so json.Unmarshal failed outright and resume errored. Try the JSON envelope first; on parse failure or empty BridgeCheckpoint, treat the raw bytes as the legacy bridge checkpoint and synthesize a fresh childSessionID. Pre-envelope checkpoints predate session persistence and have no parent-session filter to coordinate with, so the synthesized ID is harmless. Un-skip the v0.7.37, v0.8.2, v0.8.3, v0.8.4 compat fixtures so the resume path is now exercised for real on-disk legacy bytes. Change-Id: I5000424ba028e9fb9fe3843ea9673a3dd23a7860
Merge AfterCursor and PageToken into a single `After` field in LoadEventsOptions. Rename NextPageToken to `Next` in LoadEventsResult. Rename afterEventCursor to afterCursor in LoadLatestTurnEnd returns. One concept, one name: the caller seeds After from LoadLatestTurnEnd, then passes res.Next back as After on subsequent pages. Change-Id: Icd0960940b7f69938e2b4a5062d220c709bc0e66
The Options suffix implies a functional-options pattern; Request better reflects the struct-pointer parameter style and pairs with LoadEventsResult. Also removes dead variable in conformance test. Change-Id: Ia28d8a1f92f4307182508dfba56e2b3c454acfa4
…ializer Correct the tool-call middleware ordering in chatmodel.go doc comments (cancelMonitor wraps around user handlers, not inside). Add architectural doc comment to newTypedInvokableAgentToolRunner explaining why AgentTool lacks its own SessionStore. Remove the unused GobSerializer type alias from schema/serialization.go (already aliased internally). Update conformance test variable names to match LoadEventsRequest rename. Change-Id: I2251e190c9d343dbd1e57cbebfc236840be4a0fa
…igurable page size AppendEvents failures in the session persister now retry with exponential backoff (default 3 retries, 50ms initial delay, 2x multiplier, 25% jitter) before latching the error. This prevents transient store failures from causing irrecoverable session log corruption. Also extracts the hard-coded page-size=100 in reconstructFromEventLog and replayTailEvents into the configurable LoadPageSize field on SessionPersistenceConfig (default 100, preserving current behavior). New config fields on SessionPersistenceConfig: - MaxFlushRetries (default 3, set to -1 to disable) - FlushRetryInitialBackoff (default 50ms) - LoadPageSize (default 100) Change-Id: I407b6038fd61df74420a5ddd16ce8ec0f8860948
…preservation When a SessionStore is configured, the Runner now reuses the exact tool list from the previous turn's TurnEndState to feed the model, ensuring byte-exact prompt cache hits across turns. The ToolSearch middleware skips its initialization strip logic when it detects pre-seeded tool infos via the new ToolInfosPreSeededKey RunLocalValue. Users can opt out with WithRefreshToolInfos() when tools have genuinely changed between turns. Change-Id: I95585e105d41689f08116325478f21552482b79b
Change-Id: Id35944259eaee9254bc9ffa7288fe6bc43bc021b
…ion mode When sessions are enabled, TurnEndState.Messages carries the previous turn's system message. The Runner prepends this history to the new input, then defaultGenModelInput unconditionally prepends a fresh system message, causing duplication. Strip the leading system message from history before prepending the fresh instruction so that dynamic SessionValues are always re-evaluated without duplicating the system prompt. Change-Id: Id14acc6aa5f2941ea64c4de82393a36bcea2fb36
…to single file Merge three scattered test files (human_readable_test.go, human_readable_edge_test.go, schema/toolinfo_humanreadable_test.go) into one unified file in internal/serialization. Replace schema.ToolInfo usage with a local mock type to eliminate the circular dependency, and remove duplicate test cases that exercised identical code paths. Change-Id: I0b1fa9d001201382917abb8bf4b3bd220ffdf13d
…izer FileStore now writes Data directly (no base64) and rejects payloads containing raw \n or \r. This preserves human-readability for the default HumanReadableSerializer while clearly documenting that binary serializers (Gob, protobuf) are incompatible with FileStore. Change-Id: I608b444bbc7c83b7c7cb21fee1195f1e77f68aad
b562910 to
bc20cee
Compare
RunID was a redundant concept alongside TurnID. This removes it entirely from SessionEvent and the runner state, making TurnID the sole turn-correlation identifier. On Resume, the interrupted turn's TurnID is now recovered from the event log tail (events after the last committed TurnEnd), ensuring resumed events carry the same TurnID as the original interrupted run. Fresh Runs always generate new TurnIDs regardless of in-flight state. Also adds doc comments for TurnID semantics and 6 attack tests covering the new behavior. Change-Id: Iea8665219a2b18837f5b62440e901b1b35ba0d29
Remove SpanEvent.DurationMS, rename FirstChunkDurationMS to TTFTMS, remove LifecycleEvent.Reason, add SessionErrorTypeFatal constant with fatal error emission on stopReason=="failed", and change UserInterruptEvent to fire on cancelled (not graph-interrupt). Change-Id: I2b5d97c16062982ff418eb9e83d6a55546a59a39
Prevents serializing null when the pointer is nil, keeping JSONL output clean and consistent with the other optional event fields. Change-Id: Ic28d77108a2891e4e6c195c123a1ad73bd3f6c72
The struct now covers loading, serialization, and flush tuning — "Persistence" was too narrow. Field renamed accordingly: SessionPersistence -> Session in TypedRunnerConfig and TurnLoopConfig. Change-Id: I4d8089e1f0ffd34bae8a4dc34c8b5a562c41fc03
Introduce SpanKindTool with paired span.tool_call_start / span.tool_call_end
events that mirror the existing model span pair. ToolSpanMeta carries the
operational fields (call ID, tool name, evaluated permission, link IDs to
the parent model span and to the assistant / tool result messages) but never
the tool input or output content — those already live on the corresponding
messages, so persistence cost drops to O(1) per call instead of duplicating
the full input/output payloads in observation events.
Schema: add SpanKindTool, SessionEventSpanToolCallStart / End, and
ToolSpanMeta. Enforce Span.Model XOR Span.Tool in ClassifySessionEvent.
Delete AgentObservationEvent / AgentThinkingEvent / AgentToolUseEvent /
AgentToolResultEvent and their SessionEventAgent* constants; remove the
SessionEvent.AgentObservation field.
Emission: rewrite the four tool-call wrappers to emit start/end spans, and
remove the thinking observation. ID plumbing crosses the model→tool boundary
via toolBoundary{ModelSpanID, AssistantMessageEventID} stashed on the
per-run typedChatModelAgentExecCtx; tool wrappers read it to populate the
span's ParentSpanID and AssistantMessageEventID. The streamable wrappers
attach end-span emission to the consumer's stream copy via
schema.WithOnEOF / schema.WithErrWrapper (Copy(2)) instead of a goroutine
drainer, eliminating the race where the agent's event generator could close
before a background drainer's emission landed.
Tests cover end-to-end persistence (start/end pair, no observation kinds),
the streaming EOF path, and the new mutual-exclusion classification check.
Change-Id: I59089e65498ca5be6b0f50aa7011dfe4a906250b
Tool spans are now anchored to a single SpanID across interrupt boundaries, so a permission-gated tool call produces one logical start+end pair even when start fires on the original run and end fires on a later resume. The wrapper at chain position #1 (typedEventSenderToolWrapper) now keys in-flight span identity off tCtx.CallID, snapshots the parent model SpanID and assistant message EventID into typedState, and defers end-span emission when the inner endpoint returns an interrupt-shape error. On resume, the entry round-trips via gob and the matching end span reuses the original SpanID / StartEventID / parent IDs. The previous in-memory cross-turn carrier on typedChatModelAgentExecCtx is removed along with its mutex. ToolSpanMeta.EvaluatedPermission is removed: under the new lifecycle the start span fires before permission decides, so the field cannot meaningfully be populated. Gate state for interrupted calls will be conveyed via the forthcoming AgentInterrupt event; for non-interrupted calls it remains observable in the tool result message content. Change-Id: I1701d64e0cb3144134ea8f2fc7cfb62185ceb039
Add SessionEventPayload.Kind and LoadEventsRequest.Kinds so storage backends can filter events without deserializing Data. Both InMemoryStore and FileStore now persist and filter by Kind. reconstructSessionState passes modelContextSessionEventKinds to skip irrelevant events. FileStore adopts a 3-field tab-separated line format (EventID, Kind, Data). Change-Id: I637e7a3a382bb481dba1bae0d3bf720ad063d9c9
Add durable session event for business interrupts so that crash
recovery, UI reload, and external clients can retrieve the resume
handle (CheckPointID + InterruptContexts) from SessionStore.
- New types: AgentInterruptCause, AgentInterruptEvent
- New constant: SessionEventAgentInterrupt ("agent.interrupt")
- Runner emits the event post-loop with cause inference, ToolUseID,
and best-effort SpanEventID linkage
- ClassifySessionEvent handles the new payload
- State reconstruction explicitly excludes agent.interrupt
- First-turn interrupted TurnID recovery for resume continuity
- Comprehensive test coverage: serialization round-trip,
classification, reconstruction exclusion, tool linkage,
TurnLoop managed interrupt persistence
- SESSION_API_DOCUMENTATION.md added
Change-Id: I8471015e182079655f0cef566a87e697a50790cd
Change-Id: Iaaea7b25dbc3a22bf55e14c283da08f885e6e708
Change-Id: If9366c1ee599eec3ad30548629f10d1f56542f54
SessionConfig.PersistenceMode lets callers choose between the existing low-latency async persistence and a new sync mode that guarantees every persistable SessionEvent is appended before the corresponding AgentEvent is delivered. In sync mode streaming message outputs are fully materialized and delivered as non-streaming events after persistence. Refactors the persister retry loop into appendEventsWithRetry for shared use by both modes, and updates the runner event loop to gate delivery on successful append when sync is active. Change-Id: I0d5a9f14ce3773dd59f805d24fad16fcb3e423ea
Remove CheckPointID, SpanEventID, and InterruptContexts []*InterruptCtx from AgentInterruptEvent. Replace with Contexts []*AgentInterruptContext where each element carries its own Cause, InterruptID, Info, and ToolUseID. This eliminates redundant Address/Parent/IsRootCause data from persisted events and makes each context self-describing. Change-Id: I6855daac0fb23a162fa397f8811ece014d148234
Attach the persisted SessionEvent identity to all persistable message AgentEvents so downstream consumers (TurnLoop, onAgentEvents) can correlate live events with their store representation without inspecting Output.MessageOutput.Message.Role. Three paths handled: - Non-streaming: full SE backfilled after toSessionEventChecked. - Sync-streaming: SE set on persistEvent after materialization. - Async-streaming: shell SE (Kind+EventID+Timestamp, Message nil) attached to the live event; cleared before persistence to let toSessionEventChecked build the canonical SE from materialized output. Change-Id: I4d60b9fb247844192902dcaf722f8407a46e591d
Change-Id: I9aa1ee4532a568548e9304e514c0f6166a9f1a7f
The sync-mode stream persistence tests read store.events inside the iter.Next() loop without holding the mutex, racing with the persister goroutine's AppendEvents call. Snapshot the slice under lock before iterating. Change-Id: I56fd7618aa45dd2ae7e9b70609ee696eb0554bb5
Change-Id: I2a326eb9a837a7eed4cd34e18424764e3add6e0c
Rename public Runner and TurnLoop config fields from Session to SessionConfig for clearer API semantics. Also ensure TurnLoop does not pass a typed nil checkpoint store when only SessionStore is configured. Change-Id: I6fd2017a3a9788e4464fecc32eaccd24aae2410d
Change-Id: I6e45dfcadeb4ca725e5b5cf9257e2a309d594743
Change-Id: I4d42e16cd420690625342d5097df4dbcae07cb8b
Change-Id: Iabc283f15ab65dc8100ace045344a4a24574eea9
Change-Id: I2cc402789b17b9feb777853632ab5f19e260c527
Change-Id: I6a1df710e5f7e251d629d13a2c00cd49837e1828
bc20cee to
ce575f9
Compare
cd4a202 to
018ef59
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What type of PR is this?
Check the PR title.
(Optional) Translate the PR title into Chinese.
(Optional) More detailed description for this PR(en: English/zh: Chinese).
en:
zh(optional):
(Optional) Which issue(s) this PR fixes:
(optional) The PR that updates user documentation: