Skip to content

feat(adk): generify all remaining middlewares for AgenticMessage support#1003

Open
shentongmartin wants to merge 11 commits intoalpha/09from
feat/all_agentic_msg
Open

feat(adk): generify all remaining middlewares for AgenticMessage support#1003
shentongmartin wants to merge 11 commits intoalpha/09from
feat/all_agentic_msg

Conversation

@shentongmartin
Copy link
Copy Markdown
Contributor

@shentongmartin shentongmartin commented Apr 29, 2026

Generify All Remaining Middlewares for AgenticMessage

Problem

The ADK core, deep agent, and filesystem middleware already support AgenticMessage via TypedChatModelAgentMiddleware[M], but the 7 remaining middlewares under adk/middlewares/ are hardcoded to *schema.Message. This blocks any agent using *schema.AgenticMessage from leveraging these middlewares for tool call patching, context reduction, summarization, skill execution, etc.

Solution

Generify all 7 middlewares over M adk.MessageType using the established TypedXxx[M] + backward-compatible alias pattern. Every middleware now has a NewTyped[M] constructor alongside the existing New(), with internal struct renamed to typedXxxMiddleware[M].

The key challenge: *schema.Message and *schema.AgenticMessage have fundamentally different field structures (flat Role/Content/ToolCalls vs nested AgenticRoleType/ContentBlocks). Each middleware needed type-switch dispatch to handle both:

switch m := any(msg).(type) {
case *schema.Message:
    // flat fields: m.Role, m.Content, m.ToolCalls
case *schema.AgenticMessage:
    // block-based: m.Role (AgenticRoleType), m.ContentBlocks
}

For hard middlewares (reduction, summarization, skill), this required genericizing Config callback types, internal helper functions, and deep message field accessors — not just the struct/constructor.

Decisions

Full AgenticMessage support vs pass-through: Initially considered making hard middlewares pass-through for *schema.AgenticMessage (return messages unchanged). Rejected — the plan explicitly requires native support, and pass-through defeats the purpose of the middleware.

Generic internal methods vs separate implementations: For reduction/summarization, chose generic helper functions (isAssistantMsg[M], getToolCallsGeneric[M], copyMessagesGeneric[M], etc.) with type-switch dispatch over maintaining two complete parallel implementations. This keeps the main algorithm in one place while isolating type-specific field access.

model.BaseModel[M] for skill: AgentHubOptions.Model and ModelHub.Get() changed from model.ToolCallingChatModel to model.BaseModel[M]. This is required because ToolCallingChatModel is an alias for BaseModel[*schema.Message], which would leak *schema.Message into generic code.

Key Insight

*schema.AgenticMessage has no tool role — tool results are ContentBlockTypeFunctionToolResult blocks in user-role messages. Any middleware that checks for "tool messages" (reduction, patchtoolcalls) needs fundamentally different detection logic for agentic messages: iterate ContentBlocks looking for FunctionToolResult entries instead of checking msg.Role == schema.Tool.

Summary

Problem Solution
7 middlewares hardcoded to *schema.Message Generic typedXxxMiddleware[M] with NewTyped[M] + New() delegation
*schema.AgenticMessage has different field structure Type-switch dispatch with generic helper functions
Config callback types use concrete *schema.Message TypedConfig[M] with Config = TypedConfig[*schema.Message] alias
AgentHubOptions.Model leaks *schema.Message via ToolCallingChatModel Changed to model.BaseModel[M] in TypedAgentHubOptions[M]
No tool role in AgenticMessage Iterate ContentBlocks for FunctionToolResult instead of role check
Type declaration inside generic function (Go 1.18 compat) Moved struct to package level

泛化所有剩余中间件以支持 AgenticMessage

问题

ADK 核心、deep agent 和 filesystem 中间件已通过 TypedChatModelAgentMiddleware[M] 支持 AgenticMessage,但 adk/middlewares/ 下的 7 个中间件仍硬编码为 *schema.Message。这导致使用 *schema.AgenticMessage 的 agent 无法利用这些中间件进行 tool call 修补、上下文缩减、摘要生成、技能执行等。

方案

将所有 7 个中间件泛化为 M adk.MessageType,使用已有的 TypedXxx[M] + 向后兼容别名模式。每个中间件新增 NewTyped[M] 构造函数,保留已有的 New(),内部结构重命名为 typedXxxMiddleware[M]

核心挑战:*schema.Message*schema.AgenticMessage 的字段结构完全不同(扁平的 Role/Content/ToolCalls vs 嵌套的 AgenticRoleType/ContentBlocks)。每个中间件需要 type-switch 分发来处理两种类型:

switch m := any(msg).(type) {
case *schema.Message:
    // 扁平字段: m.Role, m.Content, m.ToolCalls
case *schema.AgenticMessage:
    // 基于 block: m.Role (AgenticRoleType), m.ContentBlocks
}

对于复杂中间件(reduction、summarization、skill),这要求泛化 Config 回调类型、内部辅助函数和深层消息字段访问器——不仅仅是 struct/constructor。

决策

完整 AgenticMessage 支持 vs 透传:最初考虑让复杂中间件对 *schema.AgenticMessage 做透传(原样返回消息)。被否决——计划明确要求原生支持,透传违背中间件的本意。

泛型内部方法 vs 独立实现:对于 reduction/summarization,选择使用泛型辅助函数(isAssistantMsg[M]getToolCallsGeneric[M]copyMessagesGeneric[M] 等)配合 type-switch 分发,而非维护两套完整的并行实现。这样主算法保持在一处,类型相关的字段访问被隔离。

skill 使用 model.BaseModel[M]AgentHubOptions.ModelModelHub.Get()model.ToolCallingChatModel 改为 model.BaseModel[M]。因为 ToolCallingChatModelBaseModel[*schema.Message] 的别名,会将 *schema.Message 泄漏到泛型代码中。

关键洞察

*schema.AgenticMessage 没有 tool 角色——工具结果是 user 角色消息中的 ContentBlockTypeFunctionToolResult block。任何检查"工具消息"的中间件(reduction、patchtoolcalls)都需要针对 agentic message 采用完全不同的检测逻辑:遍历 ContentBlocks 查找 FunctionToolResult 条目,而非检查 msg.Role == schema.Tool

总结

问题 方案
7 个中间件硬编码为 *schema.Message 泛型 typedXxxMiddleware[M] + NewTyped[M] + New() 委托
*schema.AgenticMessage 字段结构不同 Type-switch 分发 + 泛型辅助函数
Config 回调类型使用具体的 *schema.Message TypedConfig[M] + Config = TypedConfig[*schema.Message] 别名
AgentHubOptions.Model 通过 ToolCallingChatModel 泄漏 *schema.Message 改为 TypedAgentHubOptions[M] 中的 model.BaseModel[M]
AgenticMessage 没有 tool 角色 遍历 ContentBlocks 查找 FunctionToolResult 而非角色检查
泛型函数内的类型声明(Go 1.18 兼容性) 将 struct 移至包级别

@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 29, 2026

Codecov Report

❌ Patch coverage is 89.13525% with 98 lines in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (alpha/09@eb1de22). Learn more about missing BASE report.

Files with missing lines Patch % Lines
adk/middlewares/reduction/reduction.go 87.03% 24 Missing and 11 partials ⚠️
adk/middlewares/summarization/summarization.go 87.58% 29 Missing and 6 partials ⚠️
adk/middlewares/skill/skill.go 81.69% 12 Missing and 1 partial ⚠️
adk/middlewares/patchtoolcalls/patchtoolcalls.go 88.88% 6 Missing and 2 partials ⚠️
adk/middlewares/agentsmd/agentsmd.go 96.07% 2 Missing ⚠️
...k/middlewares/dynamictool/toolsearch/toolsearch.go 96.96% 2 Missing ⚠️
schema/agentic_message.go 97.43% 1 Missing and 1 partial ⚠️
adk/middlewares/summarization/customized_action.go 0.00% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##             alpha/09    #1003   +/-   ##
===========================================
  Coverage            ?   83.09%           
===========================================
  Files               ?      162           
  Lines               ?    22712           
  Branches            ?        0           
===========================================
  Hits                ?    18872           
  Misses              ?     2590           
  Partials            ?     1250           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@shentongmartin shentongmartin force-pushed the feat/all_agentic_msg branch 3 times, most recently from c3269ee to 06d874d Compare April 29, 2026 11:40
@shentongmartin shentongmartin force-pushed the feat/all_agentic_msg branch 2 times, most recently from a2349f8 to 91925ac Compare May 6, 2026 03:40
Comment thread adk/middlewares/reduction/reduction.go Outdated
}).(func(context.Context, []M, []*schema.ToolInfo) (int64, error))
case *schema.AgenticMessage:
return any(func(ctx context.Context, msgs []*schema.AgenticMessage, tools []*schema.ToolInfo) (int64, error) {
var tokens int64
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

抽成一个 default 函数?

}
// fromContent=true when there's exactly one text part (simple case),
// false when there are multiple parts (multi-part).
isSimple := len(parts) == 1 && parts[0].Type == schema.ToolPartTypeText
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个也写成函数?

}
// A user-role agentic message that only contains FunctionToolResult blocks
// is a tool result message, not a normal user message.
for _, block := range m.ContentBlocks {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

有点别扭,user message 如果同时存在 UserInput 和 FunctionToolResult 是不是不能当作是普通 user message 处理

}
case *schema.AgenticMessage:
// Find the first FunctionToolResult block and replace its Blocks.
for _, block := range m.ContentBlocks {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

写成一个方法?类似 toolResult.ToMessageInputParts()

// (e.g., message reduction, tool call patching), which is naturally supported by composing
// middlewares in an agent pipeline.
func SummarizeMessages(ctx context.Context, cfg *Config, messages []adk.Message) (*SummarizeOutput, error) {
func TypedSummarizeMessages[M adk.MessageType](ctx context.Context, cfg *TypedConfig[M], messages []M) (*TypedSummarizeOutput[M], error) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个已经 Deprecated 了

if block == nil {
continue
}
if block.UserInputText != nil {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

和extractTextContent的处理不一致,这个方法应该叫 getUserTextContent。原来 trim 逻辑也有点不太对,不应该破坏原来的 MultiPart 结构。第二点如果不好改,合入之后我来改也行。

Comment thread adk/middlewares/skill/skill.go Outdated
continue
}
if block.AssistantGenText != nil {
content += block.AssistantGenText.Text
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

是不是应该换行拼接?

Refactor 7 ADK middlewares to be generic over M adk.MessageType, enabling
support for both *schema.Message and *schema.AgenticMessage:

- plantask: trivial conversion (no message access)
- patchtoolcalls: dual implementation with type-switch dispatch
- agentsmd: generic insertBeforeFirstUser with type-switch
- toolsearch: generic extractToolSearchResult with ContentBlock iteration
- reduction: full genericization of Config callbacks, token counting,
  message copying, clear rewrite, and tool result extraction
- summarization: full genericization of all 6 callback types, internal
  methods, and new TypedSummarizeMessages[M] public API
- skill: generic AgentHub, ModelHub, skillTool with fork mode support,
  TypedAgentHubOptions[M] with model.BaseModel[M]

Each middleware follows the TypedXxx[M] + backward-compatible alias pattern.
All generic code paths include native AgenticMessage implementations (no
pass-through). Added AgenticState export to adk/react.go for
compose.ProcessState usage.

Added table-driven generic tests for all 7 middlewares, exercising both
*schema.Message and *schema.AgenticMessage paths: helper functions, clear
flow, truncation, end-to-end summarization, agent mode, fork mode, reminder
insertion, tool search result extraction, and tool call patching.

Change-Id: I4f97e61dbc451c42753b3e2847899d920b8d5e72
…compat

Go 1.18/1.19 does not support type declarations inside generic functions.
Move agentsMDTestCase struct to package level to fix CI build failure.

Change-Id: I2617d8cde8a663420fab46fa32a9e7379896a409
Cover Image/Audio/Video/File branches in toolResultFromMsgGeneric and
setToolResultContent for *schema.AgenticMessage, exercising all
FunctionToolResultBlock media variants and their round-trip through
ToolOutputPart conversion.

Change-Id: I57ca915e9f92da9c1d6fe40d5c6fb25826d0b54b
Replace convoluted []M↔[]*ConcreteType round-trip conversions with a
single generic iteration path. Extract isUserRole[M] and
makeUserMsgWithExtra[M] helpers, remove the two duplicate concrete
insertBeforeFirst* functions.

Change-Id: Iee3fd5f7714710faea1429f124395890d1854643
Replace []M↔[]*ConcreteType round-trip conversions and two duplicate
concrete ensureReminder* methods with a single generic loop using
isSystemRoleTS[M] and makeReminderMsg[M] helpers.

Change-Id: I05f5b2f16f9cd18e73957865fe626ef3bcc6c575
Delete AgenticState alias from react.go — the internal agenticState
is unexported by design and should not leak to middleware packages.
Mark fork mode unsupported for AgenticMessage with a clear error.

Remove IsNilMessage from adk's exported API; skill package defines
its own local isNilMessage helper instead.

Change-Id: Idc972e578846f5f15b3220a949c147e565f633b5
Fix emitEvent crash for AgenticMessage agents by using TypedSendEvent[M]
with generic TypedCustomizedAction[M]. Add missing afterToolCallsHook in
newAgenticReact. Guard nil messages in reduction copy functions. Merge 3
valuable attack tests into formal test suites and remove attack test files.

Change-Id: I295966fb752de88128571ed5149ff909ea745a06
…edMiddleware

Remove inferable type arguments across all middleware packages and tests.
Rename skill.NewTypedMiddleware to skill.NewTyped for consistency with
other middlewares. Simplify typedInsertBeforeFirstUser by eliminating
redundant inserted flag. Delete unused defaultShouldRetry in summarization.

Change-Id: I97d08c6a8e6e74b02cceac726dd9019e2065fd34
The token counter was reading tools from TypedModelContext.Tools which is
deprecated, constructed once at graph-build time, and never reflects
modifications by earlier handlers. Switch to state.ToolInfos which is the
persisted source of truth. Add test asserting the correct source is used.

Change-Id: I15a41ac2a0b990956bd1f814951a513023dbb55b
…ter rebase

Adapt to alpha/09's schema rename (39d5285): FunctionToolResultBlock →
FunctionToolResultContentBlock, .Blocks → .Content. Export
FunctionToolResultAgenticMessage from adk/wrappers.go for middleware use.

Change-Id: Idde280443dedf33351897de885b5c0dd550eb817
@shentongmartin shentongmartin force-pushed the feat/all_agentic_msg branch from 91925ac to 85bdb37 Compare May 11, 2026 06:56
- Extract defaultAgenticTokenCounter from inline lambda (reduction)
- Add FunctionToolResult.ToToolOutputParts/SetFromToolOutputParts methods
  to schema, replacing manual conversion loops in reduction
- Rename getTextContent → getMsgTextContent (summarization) for
  consistency with existing setMsgTextContent
- Use newline-separated join for multi-block text in skill fork mode

Change-Id: I0eebb193522d40f35aca75ac93a6730148c4e95c
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants