generated from cloudwego/.github
-
Notifications
You must be signed in to change notification settings - Fork 718
refactor(adk): introduce AgentHandler interface for runtime agent customization #660
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
shentongmartin
wants to merge
53
commits into
main
Choose a base branch
from
refactor/agent_middleware
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
53 commits
Select commit
Hold shift + click to select a range
d284118
refactor(adk): migrate from agent middleware to handlers
shentongmartin 0b349e5
refactor(adk): rename handler interfaces for clarity and consistency
shentongmartin 6e03599
refactor(adk): move BeforeAgent to runtime and support dynamic tools …
shentongmartin 647bf43
fix(adk): return error instead of silently ignoring tool.Info failure…
shentongmartin 6797bba
docs(adk): restore documentation comments for exported types and func…
shentongmartin ddac2cd
refactor(adk): simplify AgentHandler interface
shentongmartin 5bfe310
refactor(adk): replace GetToolMiddleware with ToolCallHandler interface
shentongmartin 5910cb9
refactor(adk): rename AgentConfig to AgentRunContext and clean up
shentongmartin 4ff8b2d
refactor(adk): rename ToolCallHandler to ToolCallWrapper and fix Befo…
shentongmartin 0599f9e
refactor(adk): move RuntimeReturnDirectly from context to State
shentongmartin 6082fb3
refactor(adk): decouple AgentMiddleware from AgentHandler interface
shentongmartin 55689c9
fix(adk): use getRunFunc in Resume for correct graph topology
shentongmartin 8a50fd3
perf(adk): pre-compute middleware contributions at instantiation time
shentongmartin 3427b51
test(adk): add test for dynamic tool execution
shentongmartin 262481f
fix(adk): include middleware tools in buildRunFunc graph topology dec…
shentongmartin 72ad562
perf(adk): conditionally add return-directly branch based on tool con…
shentongmartin 4ceaeba
refactor(adk): move initialTools computation to prepareBuildContext
shentongmartin b5e6223
refactor(adk): remove redundant middleware pre-computed fields
shentongmartin 0d11723
refactor(adk): improve buildNoToolsRunFunc code style and fix message…
shentongmartin ca6ba1c
perf(adk): cache buildContext to avoid redundant computation
shentongmartin e6e5f5c
perf(adk): add fast path in getRunFunc when no handlers
shentongmartin 15dc865
refactor(adk): dissolve graphConfig type into buildContext
shentongmartin b89549a
refactor(adk): remove derived fields from buildContext
shentongmartin 93ae691
refactor(adk): inline hasReturnDirectlyTool and isRuntimeCompatible
shentongmartin 3e247d1
refactor(adk): make runFunc a template accepting instruction, returnD…
shentongmartin 7ddd1ab
refactor(adk): remove initialTools field from buildContext
shentongmartin f594f5b
refactor(adk): change returnDirectly maps from map[string]bool to map…
shentongmartin fe04542
refactor(adk): simplify reactConfig and encapsulate runtime values in…
shentongmartin 5650246
refactor(adk): return new buildContext from applyBeforeAgent instead …
shentongmartin d81da1f
refactor(adk): remove ToolMeta and put tools/returnDirectly directly …
shentongmartin c183ee4
refactor(adk): simplify buildContext and getRunFunc
shentongmartin 1a50c49
refactor(adk): move error/interrupt handling from callbacks to runFunc
shentongmartin 8838655
refactor(adk): move tool result sending from callbacks to middleware …
shentongmartin d94ffe2
refactor(adk): replace ToolsNode callbacks with StreamStatePostHandler
shentongmartin eddf345
refactor: replace callback-based event sending with ModelCallWrapper …
shentongmartin e9edf35
refactor(adk): add callback injection for models without IsCallbacksE…
shentongmartin 1c44f8d
refactor(adk): move chain instantiation outside runFunc
shentongmartin eb08a57
refactor(adk): fix ReturnDirectlyEvent persistence on interrupt
shentongmartin b6e7a71
refactor(adk): rename wrap_chatmodel.go to wrappers.go and consolidat…
shentongmartin 74123f0
refactor(adk): replace slices.Clone with cloneSlice for Go 1.18 compa…
shentongmartin 08b1220
refactor(adk): improve test coverage for chatmodel.go, wrappers.go, a…
shentongmartin 46191e9
refactor(adk): improve error messages and add middleware order docume…
shentongmartin ba420f4
refactor(adk): embed wrapper interfaces in AgentHandler for consistency
shentongmartin 65763e4
docs(adk): add comprehensive documentation for AgentHandler vs AgentM…
shentongmartin ed28f4e
refactor(adk): replace ModelCallWrapper interface with WrapModel meth…
shentongmartin 7c65de8
refactor(adk): replace ToolCallWrapper interface with WrapTool method…
shentongmartin c80c5ac
refactor(adk): change BaseAgentHandler to pointer receiver
shentongmartin a90d222
refactor(adk): rename AgentHandler to HandlerMiddleware
shentongmartin 9dc7807
refactor(adk): implement cached reflection for HandlerMiddleware
shentongmartin c663b19
refactor(adk): remove handler helper functions
shentongmartin 14f4248
refactor(adk): remove dead code in retryChatModel
shentongmartin 2bae96b
refactor(adk): optimize WrapTool to construction-time wrapping
shentongmartin ec65a64
refactor(adk): convert BeforeModelRewriteHistory/AfterModelRewriteHis…
shentongmartin File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,139 @@ | ||
| /* | ||
| * Copyright 2025 CloudWeGo Authors | ||
| * | ||
| * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| * you may not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
|
|
||
| package adk | ||
|
|
||
| import ( | ||
| "context" | ||
| "reflect" | ||
|
|
||
| "github.com/cloudwego/eino/components/model" | ||
| "github.com/cloudwego/eino/components/tool" | ||
| ) | ||
|
|
||
| // AgentContext contains runtime information passed to handlers before each agent run. | ||
| // Handlers can modify Instruction, Tools, and ReturnDirectly to customize agent behavior. | ||
| type AgentContext struct { | ||
| Instruction string | ||
| Tools []tool.BaseTool | ||
| ReturnDirectly map[string]struct{} | ||
| } | ||
|
|
||
| // HandlerMiddleware defines the interface for customizing agent behavior. | ||
| // | ||
| // Why HandlerMiddleware instead of AgentMiddleware? | ||
| // | ||
| // AgentMiddleware is a struct type, which has inherent limitations: | ||
| // - Struct types are closed: users cannot add new methods to extend functionality | ||
| // - The framework only recognizes AgentMiddleware's fixed fields, so even if users | ||
| // embed AgentMiddleware in a custom struct and add methods, the framework cannot | ||
| // call those methods (config.Middlewares is []AgentMiddleware, not a user type) | ||
| // - Callbacks in AgentMiddleware only return error, cannot return modified context | ||
| // | ||
| // HandlerMiddleware is an interface type, which is open for extension: | ||
| // - Users can implement custom handlers with arbitrary internal state and methods | ||
| // - All methods return (context.Context, ..., error), allowing context propagation | ||
| // - Configuration is centralized in struct fields rather than scattered in closures | ||
| // | ||
| // HandlerMiddleware vs AgentMiddleware: | ||
| // - Use AgentMiddleware for simple, static additions (extra instruction/tools) | ||
| // - Use HandlerMiddleware for dynamic behavior, context modification, or call wrapping | ||
| // - AgentMiddleware is kept for backward compatibility with existing users | ||
| // - Both can be used together; middlewares are applied first, then handlers | ||
| // | ||
| // Use *BaseHandlerMiddleware as an embedded struct to provide default no-op | ||
| // implementations for all methods. | ||
| type HandlerMiddleware interface { | ||
| // BeforeAgent is called before each agent run, allowing modification of | ||
| // the agent's instruction and tools configuration. | ||
| BeforeAgent(ctx context.Context, runCtx *AgentContext) (context.Context, *AgentContext, error) | ||
|
|
||
| // BeforeModelRewriteHistory is called before each model invocation. | ||
| // The returned messages are persisted to the agent's internal state and passed to the model. | ||
| // The returned context is propagated to the model call and subsequent handlers. | ||
| BeforeModelRewriteHistory(ctx context.Context, messages []Message) (context.Context, []Message, error) | ||
|
|
||
| // AfterModelRewriteHistory is called after each model invocation. | ||
| // The input messages include the model's response as the last message. | ||
| // The returned messages are persisted to the agent's internal state. | ||
| AfterModelRewriteHistory(ctx context.Context, messages []Message) (context.Context, []Message, error) | ||
|
|
||
| // WrapTool wraps a tool with custom behavior. | ||
| // Return the input tool unchanged if no wrapping is needed. | ||
| // Called at construction time (or after BeforeAgent if tools are modified dynamically). | ||
| WrapTool(ctx context.Context, t tool.BaseTool) (tool.BaseTool, error) | ||
|
|
||
| // WrapModel wraps a chat model with custom behavior. | ||
| // Return the input model unchanged if no wrapping is needed. | ||
| // Called once when the agent is built, not per-call. | ||
| WrapModel(ctx context.Context, m model.BaseChatModel) (model.BaseChatModel, error) | ||
| } | ||
|
|
||
| // BaseHandlerMiddleware provides default no-op implementations for HandlerMiddleware. | ||
| // Embed *BaseHandlerMiddleware in custom handlers to only override the methods you need. | ||
| type BaseHandlerMiddleware struct{} | ||
|
|
||
| func (b *BaseHandlerMiddleware) WrapTool(_ context.Context, t tool.BaseTool) (tool.BaseTool, error) { | ||
| return t, nil | ||
| } | ||
|
|
||
| func (b *BaseHandlerMiddleware) WrapModel(_ context.Context, m model.BaseChatModel) (model.BaseChatModel, error) { | ||
| return m, nil | ||
| } | ||
|
|
||
| func (b *BaseHandlerMiddleware) BeforeAgent(ctx context.Context, runCtx *AgentContext) (context.Context, *AgentContext, error) { | ||
| return ctx, runCtx, nil | ||
| } | ||
|
|
||
| func (b *BaseHandlerMiddleware) BeforeModelRewriteHistory(ctx context.Context, messages []Message) (context.Context, []Message, error) { | ||
| return ctx, messages, nil | ||
| } | ||
|
|
||
| func (b *BaseHandlerMiddleware) AfterModelRewriteHistory(ctx context.Context, messages []Message) (context.Context, []Message, error) { | ||
| return ctx, messages, nil | ||
| } | ||
|
|
||
| type handlerInfo struct { | ||
| handler HandlerMiddleware | ||
| hasBeforeAgent bool | ||
| hasBeforeModelRewriteHistory bool | ||
| hasAfterModelRewriteHistory bool | ||
| hasWrapTool bool | ||
| hasWrapModel bool | ||
| } | ||
|
|
||
| var baseHandlerMiddlewareType = reflect.TypeOf(&BaseHandlerMiddleware{}) | ||
|
|
||
| func isMethodOverridden(handler HandlerMiddleware, methodName string) bool { | ||
| handlerType := reflect.TypeOf(handler) | ||
| handlerMethod, ok1 := handlerType.MethodByName(methodName) | ||
| baseMethod, ok2 := baseHandlerMiddlewareType.MethodByName(methodName) | ||
| if !ok1 || !ok2 { | ||
| return true | ||
| } | ||
| return handlerMethod.Func.Pointer() != baseMethod.Func.Pointer() | ||
| } | ||
|
|
||
| func newHandlerInfo(h HandlerMiddleware) handlerInfo { | ||
| return handlerInfo{ | ||
| handler: h, | ||
| hasBeforeAgent: isMethodOverridden(h, "BeforeAgent"), | ||
| hasBeforeModelRewriteHistory: isMethodOverridden(h, "BeforeModelRewriteHistory"), | ||
| hasAfterModelRewriteHistory: isMethodOverridden(h, "AfterModelRewriteHistory"), | ||
| hasWrapTool: isMethodOverridden(h, "WrapTool"), | ||
| hasWrapModel: isMethodOverridden(h, "WrapModel"), | ||
| } | ||
| } | ||
Oops, something went wrong.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
agent 运行前切面要基于这个定义吗?但我看这里没有 agent input 相关信息
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
你加上就行,我只加了之前有的