From 47021df6a6a8797a5ecd373937fec8a4adb3f842 Mon Sep 17 00:00:00 2001 From: Dmytro Struk <13853051+dmytrostruk@users.noreply.github.com> Date: Fri, 6 Feb 2026 11:46:27 -0800 Subject: [PATCH 1/2] Renamed next middleware parameter to call_next --- python/packages/core/AGENTS.md | 4 +- .../core/agent_framework/_middleware.py | 74 ++--- .../core/test_as_tool_kwargs_propagation.py | 42 ++- .../core/tests/core/test_middleware.py | 228 ++++++++------- .../core/test_middleware_context_result.py | 58 ++-- .../tests/core/test_middleware_with_agent.py | 260 ++++++++++-------- .../tests/core/test_middleware_with_chat.py | 60 ++-- .../ollama/tests/test_ollama_chat_client.py | 4 +- .../_handoff.py | 4 +- .../agent_framework_purview/_middleware.py | 8 +- python/samples/concepts/tools/README.md | 64 ++--- .../azure_ai/azure_ai_with_agent_as_tool.py | 4 +- .../openai/openai_responses_client_basic.py | 4 +- ...nai_responses_client_with_agent_as_tool.py | 4 +- .../devui/weather_agent_azure/agent.py | 8 +- .../agent_and_run_level_middleware.py | 28 +- .../middleware/chat_middleware.py | 8 +- .../middleware/class_based_middleware.py | 10 +- .../middleware/decorator_middleware.py | 8 +- .../exception_handling_with_middleware.py | 4 +- .../middleware/function_based_middleware.py | 12 +- .../middleware/middleware_termination.py | 10 +- .../override_result_with_middleware.py | 12 +- .../middleware/runtime_context_delegation.py | 16 +- .../middleware/shared_state_middleware.py | 8 +- .../middleware/thread_behavior_middleware.py | 14 +- 26 files changed, 528 insertions(+), 428 deletions(-) diff --git a/python/packages/core/AGENTS.md b/python/packages/core/AGENTS.md index 57e86c3710..4140050856 100644 --- a/python/packages/core/AGENTS.md +++ b/python/packages/core/AGENTS.md @@ -117,9 +117,9 @@ agent = OpenAIChatClient().as_agent( from agent_framework import ChatAgent, AgentMiddleware, AgentContext class LoggingMiddleware(AgentMiddleware): - async def process(self, context: AgentContext, next) -> AgentResponse: + async def process(self, context: AgentContext, call_next) -> AgentResponse: print(f"Input: {context.messages}") - response = await next(context) + response = await call_next(context) print(f"Output: {response}") return response diff --git a/python/packages/core/agent_framework/_middleware.py b/python/packages/core/agent_framework/_middleware.py index eff57cfdcb..9e83e925f7 100644 --- a/python/packages/core/agent_framework/_middleware.py +++ b/python/packages/core/agent_framework/_middleware.py @@ -122,7 +122,7 @@ class AgentContext: options: The options for the agent invocation as a dict. stream: Whether this is a streaming invocation. metadata: Metadata dictionary for sharing data between agent middleware. - result: Agent execution result. Can be observed after calling ``next()`` + result: Agent execution result. Can be observed after calling ``call_next()`` to see the actual execution result or can be set to override the execution result. For non-streaming: should be AgentResponse. For streaming: should be ResponseStream[AgentResponseUpdate, AgentResponse]. @@ -135,7 +135,7 @@ class AgentContext: class LoggingMiddleware(AgentMiddleware): - async def process(self, context: AgentContext, next): + async def process(self, context: AgentContext, call_next): print(f"Agent: {context.agent.name}") print(f"Messages: {len(context.messages)}") print(f"Thread: {context.thread}") @@ -145,7 +145,7 @@ async def process(self, context: AgentContext, next): context.metadata["start_time"] = time.time() # Continue execution - await next(context) + await call_next(context) # Access result after execution print(f"Result: {context.result}") @@ -208,7 +208,7 @@ class FunctionInvocationContext: function: The function being invoked. arguments: The validated arguments for the function. metadata: Metadata dictionary for sharing data between function middleware. - result: Function execution result. Can be observed after calling ``next()`` + result: Function execution result. Can be observed after calling ``call_next()`` to see the actual execution result or can be set to override the execution result. kwargs: Additional keyword arguments passed to the chat method that invoked this function. @@ -220,7 +220,7 @@ class FunctionInvocationContext: class ValidationMiddleware(FunctionMiddleware): - async def process(self, context: FunctionInvocationContext, next): + async def process(self, context: FunctionInvocationContext, call_next): print(f"Function: {context.function.name}") print(f"Arguments: {context.arguments}") @@ -229,7 +229,7 @@ async def process(self, context: FunctionInvocationContext, next): raise MiddlewareTermination("Validation failed") # Continue execution - await next(context) + await call_next(context) """ def __init__( @@ -268,7 +268,7 @@ class ChatContext: options: The options for the chat request as a dict. stream: Whether this is a streaming invocation. metadata: Metadata dictionary for sharing data between chat middleware. - result: Chat execution result. Can be observed after calling ``next()`` + result: Chat execution result. Can be observed after calling ``call_next()`` to see the actual execution result or can be set to override the execution result. For non-streaming: should be ChatResponse. For streaming: should be ResponseStream[ChatResponseUpdate, ChatResponse]. @@ -284,7 +284,7 @@ class ChatContext: class TokenCounterMiddleware(ChatMiddleware): - async def process(self, context: ChatContext, next): + async def process(self, context: ChatContext, call_next): print(f"Chat client: {context.chat_client.__class__.__name__}") print(f"Messages: {len(context.messages)}") print(f"Model: {context.options.get('model_id')}") @@ -293,7 +293,7 @@ async def process(self, context: ChatContext, next): context.metadata["input_tokens"] = self.count_tokens(context.messages) # Continue execution - await next(context) + await call_next(context) # Access result and count output tokens if context.result: @@ -363,9 +363,9 @@ class RetryMiddleware(AgentMiddleware): def __init__(self, max_retries: int = 3): self.max_retries = max_retries - async def process(self, context: AgentContext, next): + async def process(self, context: AgentContext, call_next): for attempt in range(self.max_retries): - await next(context) + await call_next(context) if context.result and not context.result.is_error: break print(f"Retry {attempt + 1}/{self.max_retries}") @@ -379,7 +379,7 @@ async def process(self, context: AgentContext, next): async def process( self, context: AgentContext, - next: Callable[[AgentContext], Awaitable[None]], + call_next: Callable[[AgentContext], Awaitable[None]], ) -> None: """Process an agent invocation. @@ -387,16 +387,16 @@ async def process( context: Agent invocation context containing agent, messages, and metadata. Use context.stream to determine if this is a streaming call. MiddlewareTypes can set context.result to override execution, or observe - the actual execution result after calling next(). + the actual execution result after calling call_next(). For non-streaming: AgentResponse For streaming: AsyncIterable[AgentResponseUpdate] - next: Function to call the next middleware or final agent execution. + call_next: Function to call the next middleware or final agent execution. Does not return anything - all data flows through the context. Note: MiddlewareTypes should not return anything. All data manipulation should happen within the context object. Set context.result to override execution, - or observe context.result after calling next() for actual results. + or observe context.result after calling call_next() for actual results. """ ... @@ -422,7 +422,7 @@ class CachingMiddleware(FunctionMiddleware): def __init__(self): self.cache = {} - async def process(self, context: FunctionInvocationContext, next): + async def process(self, context: FunctionInvocationContext, call_next): cache_key = f"{context.function.name}:{context.arguments}" # Check cache @@ -431,7 +431,7 @@ async def process(self, context: FunctionInvocationContext, next): raise MiddlewareTermination() # Execute function - await next(context) + await call_next(context) # Cache result if context.result: @@ -446,21 +446,21 @@ async def process(self, context: FunctionInvocationContext, next): async def process( self, context: FunctionInvocationContext, - next: Callable[[FunctionInvocationContext], Awaitable[None]], + call_next: Callable[[FunctionInvocationContext], Awaitable[None]], ) -> None: """Process a function invocation. Args: context: Function invocation context containing function, arguments, and metadata. MiddlewareTypes can set context.result to override execution, or observe - the actual execution result after calling next(). - next: Function to call the next middleware or final function execution. + the actual execution result after calling call_next(). + call_next: Function to call the next middleware or final function execution. Does not return anything - all data flows through the context. Note: MiddlewareTypes should not return anything. All data manipulation should happen within the context object. Set context.result to override execution, - or observe context.result after calling next() for actual results. + or observe context.result after calling call_next() for actual results. """ ... @@ -486,14 +486,14 @@ class SystemPromptMiddleware(ChatMiddleware): def __init__(self, system_prompt: str): self.system_prompt = system_prompt - async def process(self, context: ChatContext, next): + async def process(self, context: ChatContext, call_next): # Add system prompt to messages from agent_framework import ChatMessage context.messages.insert(0, ChatMessage(role="system", text=self.system_prompt)) # Continue execution - await next(context) + await call_next(context) # Use with an agent @@ -508,7 +508,7 @@ async def process(self, context: ChatContext, next): async def process( self, context: ChatContext, - next: Callable[[ChatContext], Awaitable[None]], + call_next: Callable[[ChatContext], Awaitable[None]], ) -> None: """Process a chat client request. @@ -516,16 +516,16 @@ async def process( context: Chat invocation context containing chat client, messages, options, and metadata. Use context.stream to determine if this is a streaming call. MiddlewareTypes can set context.result to override execution, or observe - the actual execution result after calling next(). + the actual execution result after calling call_next(). For non-streaming: ChatResponse For streaming: ResponseStream[ChatResponseUpdate, ChatResponse] - next: Function to call the next middleware or final chat execution. + call_next: Function to call the next middleware or final chat execution. Does not return anything - all data flows through the context. Note: MiddlewareTypes should not return anything. All data manipulation should happen within the context object. Set context.result to override execution, - or observe context.result after calling next() for actual results. + or observe context.result after calling call_next() for actual results. """ ... @@ -576,9 +576,9 @@ def agent_middleware(func: AgentMiddlewareCallable) -> AgentMiddlewareCallable: @agent_middleware - async def logging_middleware(context: AgentContext, next): + async def logging_middleware(context: AgentContext, call_next): print(f"Before: {context.agent.name}") - await next(context) + await call_next(context) print(f"After: {context.result}") @@ -609,9 +609,9 @@ def function_middleware(func: FunctionMiddlewareCallable) -> FunctionMiddlewareC @function_middleware - async def logging_middleware(context: FunctionInvocationContext, next): + async def logging_middleware(context: FunctionInvocationContext, call_next): print(f"Calling: {context.function.name}") - await next(context) + await call_next(context) print(f"Result: {context.result}") @@ -642,9 +642,9 @@ def chat_middleware(func: ChatMiddlewareCallable) -> ChatMiddlewareCallable: @chat_middleware - async def logging_middleware(context: ChatContext, next): + async def logging_middleware(context: ChatContext, call_next): print(f"Messages: {len(context.messages)}") - await next(context) + await call_next(context) print(f"Response: {context.result}") @@ -669,8 +669,8 @@ class MiddlewareWrapper(Generic[TContext]): def __init__(self, func: Callable[[TContext, Callable[[TContext], Awaitable[None]]], Awaitable[None]]) -> None: self.func = func - async def process(self, context: TContext, next: Callable[[TContext], Awaitable[None]]) -> None: - await self.func(context, next) + async def process(self, context: TContext, call_next: Callable[[TContext], Awaitable[None]]) -> None: + await self.func(context, call_next) class BaseMiddlewarePipeline(ABC): @@ -1226,7 +1226,7 @@ def _determine_middleware_type(middleware: Any) -> MiddlewareType: sig = inspect.signature(middleware) params = list(sig.parameters.values()) - # Must have at least 2 parameters (context and next) + # Must have at least 2 parameters (context and call_next) if len(params) >= 2: first_param = params[0] if hasattr(first_param.annotation, "__name__"): @@ -1240,7 +1240,7 @@ def _determine_middleware_type(middleware: Any) -> MiddlewareType: else: # Not enough parameters - can't be valid middleware raise MiddlewareException( - f"MiddlewareTypes function must have at least 2 parameters (context, next), " + f"MiddlewareTypes function must have at least 2 parameters (context, call_next), " f"but {middleware.__name__} has {len(params)}" ) except Exception as e: diff --git a/python/packages/core/tests/core/test_as_tool_kwargs_propagation.py b/python/packages/core/tests/core/test_as_tool_kwargs_propagation.py index 8a2c4ceb5b..4672b10e77 100644 --- a/python/packages/core/tests/core/test_as_tool_kwargs_propagation.py +++ b/python/packages/core/tests/core/test_as_tool_kwargs_propagation.py @@ -19,10 +19,12 @@ async def test_as_tool_forwards_runtime_kwargs(self, chat_client: MockChatClient captured_kwargs: dict[str, Any] = {} @agent_middleware - async def capture_middleware(context: AgentContext, next: Callable[[AgentContext], Awaitable[None]]) -> None: + async def capture_middleware( + context: AgentContext, call_next: Callable[[AgentContext], Awaitable[None]] + ) -> None: # Capture kwargs passed to the sub-agent captured_kwargs.update(context.kwargs) - await next(context) + await call_next(context) # Setup mock response chat_client.responses = [ @@ -60,9 +62,11 @@ async def test_as_tool_excludes_arg_name_from_forwarded_kwargs(self, chat_client captured_kwargs: dict[str, Any] = {} @agent_middleware - async def capture_middleware(context: AgentContext, next: Callable[[AgentContext], Awaitable[None]]) -> None: + async def capture_middleware( + context: AgentContext, call_next: Callable[[AgentContext], Awaitable[None]] + ) -> None: captured_kwargs.update(context.kwargs) - await next(context) + await call_next(context) # Setup mock response chat_client.responses = [ @@ -95,10 +99,12 @@ async def test_as_tool_nested_delegation_propagates_kwargs(self, chat_client: Mo captured_kwargs_list: list[dict[str, Any]] = [] @agent_middleware - async def capture_middleware(context: AgentContext, next: Callable[[AgentContext], Awaitable[None]]) -> None: + async def capture_middleware( + context: AgentContext, call_next: Callable[[AgentContext], Awaitable[None]] + ) -> None: # Capture kwargs at each level captured_kwargs_list.append(dict(context.kwargs)) - await next(context) + await call_next(context) # Setup mock responses to trigger nested tool invocation: B calls tool C, then completes. chat_client.responses = [ @@ -156,9 +162,11 @@ async def test_as_tool_streaming_mode_forwards_kwargs(self, chat_client: MockCha captured_kwargs: dict[str, Any] = {} @agent_middleware - async def capture_middleware(context: AgentContext, next: Callable[[AgentContext], Awaitable[None]]) -> None: + async def capture_middleware( + context: AgentContext, call_next: Callable[[AgentContext], Awaitable[None]] + ) -> None: captured_kwargs.update(context.kwargs) - await next(context) + await call_next(context) # Setup mock streaming responses from agent_framework import ChatResponseUpdate @@ -216,9 +224,11 @@ async def test_as_tool_kwargs_with_chat_options(self, chat_client: MockChatClien captured_kwargs: dict[str, Any] = {} @agent_middleware - async def capture_middleware(context: AgentContext, next: Callable[[AgentContext], Awaitable[None]]) -> None: + async def capture_middleware( + context: AgentContext, call_next: Callable[[AgentContext], Awaitable[None]] + ) -> None: captured_kwargs.update(context.kwargs) - await next(context) + await call_next(context) # Setup mock response chat_client.responses = [ @@ -256,14 +266,16 @@ async def test_as_tool_kwargs_isolated_per_invocation(self, chat_client: MockCha call_count = 0 @agent_middleware - async def capture_middleware(context: AgentContext, next: Callable[[AgentContext], Awaitable[None]]) -> None: + async def capture_middleware( + context: AgentContext, call_next: Callable[[AgentContext], Awaitable[None]] + ) -> None: nonlocal call_count call_count += 1 if call_count == 1: first_call_kwargs.update(context.kwargs) elif call_count == 2: second_call_kwargs.update(context.kwargs) - await next(context) + await call_next(context) # Setup mock responses for both calls chat_client.responses = [ @@ -306,9 +318,11 @@ async def test_as_tool_excludes_conversation_id_from_forwarded_kwargs(self, chat captured_kwargs: dict[str, Any] = {} @agent_middleware - async def capture_middleware(context: AgentContext, next: Callable[[AgentContext], Awaitable[None]]) -> None: + async def capture_middleware( + context: AgentContext, call_next: Callable[[AgentContext], Awaitable[None]] + ) -> None: captured_kwargs.update(context.kwargs) - await next(context) + await call_next(context) # Setup mock response chat_client.responses = [ diff --git a/python/packages/core/tests/core/test_middleware.py b/python/packages/core/tests/core/test_middleware.py index 7adde399ba..ae84541df4 100644 --- a/python/packages/core/tests/core/test_middleware.py +++ b/python/packages/core/tests/core/test_middleware.py @@ -135,12 +135,12 @@ class TestAgentMiddlewarePipeline: """Test cases for AgentMiddlewarePipeline.""" class PreNextTerminateMiddleware(AgentMiddleware): - async def process(self, context: AgentContext, next: Callable[[AgentContext], Awaitable[None]]) -> None: + async def process(self, context: AgentContext, call_next: Callable[[AgentContext], Awaitable[None]]) -> None: raise MiddlewareTermination class PostNextTerminateMiddleware(AgentMiddleware): - async def process(self, context: AgentContext, next: Any) -> None: - await next(context) + async def process(self, context: AgentContext, call_next: Any) -> None: + await call_next(context) raise MiddlewareTermination def test_init_empty(self) -> None: @@ -157,8 +157,8 @@ def test_init_with_class_middleware(self) -> None: def test_init_with_function_middleware(self) -> None: """Test AgentMiddlewarePipeline initialization with function-based middleware.""" - async def test_middleware(context: AgentContext, next: Callable[[AgentContext], Awaitable[None]]) -> None: - await next(context) + async def test_middleware(context: AgentContext, call_next: Callable[[AgentContext], Awaitable[None]]) -> None: + await call_next(context) pipeline = AgentMiddlewarePipeline(test_middleware) assert pipeline.has_middlewares @@ -185,9 +185,11 @@ class OrderTrackingMiddleware(AgentMiddleware): def __init__(self, name: str): self.name = name - async def process(self, context: AgentContext, next: Callable[[AgentContext], Awaitable[None]]) -> None: + async def process( + self, context: AgentContext, call_next: Callable[[AgentContext], Awaitable[None]] + ) -> None: execution_order.append(f"{self.name}_before") - await next(context) + await call_next(context) execution_order.append(f"{self.name}_after") middleware = OrderTrackingMiddleware("test") @@ -236,9 +238,11 @@ class StreamOrderTrackingMiddleware(AgentMiddleware): def __init__(self, name: str): self.name = name - async def process(self, context: AgentContext, next: Callable[[AgentContext], Awaitable[None]]) -> None: + async def process( + self, context: AgentContext, call_next: Callable[[AgentContext], Awaitable[None]] + ) -> None: execution_order.append(f"{self.name}_before") - await next(context) + await call_next(context) execution_order.append(f"{self.name}_after") middleware = StreamOrderTrackingMiddleware("test") @@ -363,10 +367,12 @@ async def test_execute_with_thread_in_context(self, mock_agent: SupportsAgentRun captured_thread = None class ThreadCapturingMiddleware(AgentMiddleware): - async def process(self, context: AgentContext, next: Callable[[AgentContext], Awaitable[None]]) -> None: + async def process( + self, context: AgentContext, call_next: Callable[[AgentContext], Awaitable[None]] + ) -> None: nonlocal captured_thread captured_thread = context.thread - await next(context) + await call_next(context) middleware = ThreadCapturingMiddleware() pipeline = AgentMiddlewarePipeline(middleware) @@ -388,10 +394,12 @@ async def test_execute_with_no_thread_in_context(self, mock_agent: SupportsAgent captured_thread = "not_none" # Use string to distinguish from None class ThreadCapturingMiddleware(AgentMiddleware): - async def process(self, context: AgentContext, next: Callable[[AgentContext], Awaitable[None]]) -> None: + async def process( + self, context: AgentContext, call_next: Callable[[AgentContext], Awaitable[None]] + ) -> None: nonlocal captured_thread captured_thread = context.thread - await next(context) + await call_next(context) middleware = ThreadCapturingMiddleware() pipeline = AgentMiddlewarePipeline(middleware) @@ -412,12 +420,12 @@ class TestFunctionMiddlewarePipeline: """Test cases for FunctionMiddlewarePipeline.""" class PreNextTerminateFunctionMiddleware(FunctionMiddleware): - async def process(self, context: FunctionInvocationContext, next: Any) -> None: + async def process(self, context: FunctionInvocationContext, call_next: Any) -> None: raise MiddlewareTermination class PostNextTerminateFunctionMiddleware(FunctionMiddleware): - async def process(self, context: FunctionInvocationContext, next: Any) -> None: - await next(context) + async def process(self, context: FunctionInvocationContext, call_next: Any) -> None: + await call_next(context) raise MiddlewareTermination async def test_execute_with_pre_next_termination(self, mock_function: FunctionTool[Any, Any]) -> None: @@ -475,9 +483,9 @@ def test_init_with_function_middleware(self) -> None: """Test FunctionMiddlewarePipeline initialization with function-based middleware.""" async def test_middleware( - context: FunctionInvocationContext, next: Callable[[FunctionInvocationContext], Awaitable[None]] + context: FunctionInvocationContext, call_next: Callable[[FunctionInvocationContext], Awaitable[None]] ) -> None: - await next(context) + await call_next(context) pipeline = FunctionMiddlewarePipeline(test_middleware) assert pipeline.has_middlewares @@ -507,10 +515,10 @@ def __init__(self, name: str): async def process( self, context: FunctionInvocationContext, - next: Callable[[FunctionInvocationContext], Awaitable[None]], + call_next: Callable[[FunctionInvocationContext], Awaitable[None]], ) -> None: execution_order.append(f"{self.name}_before") - await next(context) + await call_next(context) execution_order.append(f"{self.name}_after") middleware = OrderTrackingFunctionMiddleware("test") @@ -533,12 +541,12 @@ class TestChatMiddlewarePipeline: """Test cases for ChatMiddlewarePipeline.""" class PreNextTerminateChatMiddleware(ChatMiddleware): - async def process(self, context: ChatContext, next: Callable[[ChatContext], Awaitable[None]]) -> None: + async def process(self, context: ChatContext, call_next: Callable[[ChatContext], Awaitable[None]]) -> None: raise MiddlewareTermination class PostNextTerminateChatMiddleware(ChatMiddleware): - async def process(self, context: ChatContext, next: Callable[[ChatContext], Awaitable[None]]) -> None: - await next(context) + async def process(self, context: ChatContext, call_next: Callable[[ChatContext], Awaitable[None]]) -> None: + await call_next(context) raise MiddlewareTermination def test_init_empty(self) -> None: @@ -555,8 +563,8 @@ def test_init_with_class_middleware(self) -> None: def test_init_with_function_middleware(self) -> None: """Test ChatMiddlewarePipeline initialization with function-based middleware.""" - async def test_middleware(context: ChatContext, next: Callable[[ChatContext], Awaitable[None]]) -> None: - await next(context) + async def test_middleware(context: ChatContext, call_next: Callable[[ChatContext], Awaitable[None]]) -> None: + await call_next(context) pipeline = ChatMiddlewarePipeline(test_middleware) assert pipeline.has_middlewares @@ -584,9 +592,9 @@ class OrderTrackingChatMiddleware(ChatMiddleware): def __init__(self, name: str): self.name = name - async def process(self, context: ChatContext, next: Callable[[ChatContext], Awaitable[None]]) -> None: + async def process(self, context: ChatContext, call_next: Callable[[ChatContext], Awaitable[None]]) -> None: execution_order.append(f"{self.name}_before") - await next(context) + await call_next(context) execution_order.append(f"{self.name}_after") middleware = OrderTrackingChatMiddleware("test") @@ -636,9 +644,9 @@ class StreamOrderTrackingChatMiddleware(ChatMiddleware): def __init__(self, name: str): self.name = name - async def process(self, context: ChatContext, next: Callable[[ChatContext], Awaitable[None]]) -> None: + async def process(self, context: ChatContext, call_next: Callable[[ChatContext], Awaitable[None]]) -> None: execution_order.append(f"{self.name}_before") - await next(context) + await call_next(context) execution_order.append(f"{self.name}_after") middleware = StreamOrderTrackingChatMiddleware("test") @@ -766,10 +774,12 @@ async def test_agent_middleware_execution(self, mock_agent: SupportsAgentRun) -> metadata_updates: list[str] = [] class MetadataAgentMiddleware(AgentMiddleware): - async def process(self, context: AgentContext, next: Callable[[AgentContext], Awaitable[None]]) -> None: + async def process( + self, context: AgentContext, call_next: Callable[[AgentContext], Awaitable[None]] + ) -> None: context.metadata["before"] = True metadata_updates.append("before") - await next(context) + await call_next(context) context.metadata["after"] = True metadata_updates.append("after") @@ -797,11 +807,11 @@ class MetadataFunctionMiddleware(FunctionMiddleware): async def process( self, context: FunctionInvocationContext, - next: Callable[[FunctionInvocationContext], Awaitable[None]], + call_next: Callable[[FunctionInvocationContext], Awaitable[None]], ) -> None: context.metadata["before"] = True metadata_updates.append("before") - await next(context) + await call_next(context) context.metadata["after"] = True metadata_updates.append("after") @@ -829,10 +839,12 @@ async def test_agent_function_middleware(self, mock_agent: SupportsAgentRun) -> """Test function-based agent middleware.""" execution_order: list[str] = [] - async def test_agent_middleware(context: AgentContext, next: Callable[[AgentContext], Awaitable[None]]) -> None: + async def test_agent_middleware( + context: AgentContext, call_next: Callable[[AgentContext], Awaitable[None]] + ) -> None: execution_order.append("function_before") context.metadata["function_middleware"] = True - await next(context) + await call_next(context) execution_order.append("function_after") pipeline = AgentMiddlewarePipeline(test_agent_middleware) @@ -854,11 +866,11 @@ async def test_function_function_middleware(self, mock_function: FunctionTool[An execution_order: list[str] = [] async def test_function_middleware( - context: FunctionInvocationContext, next: Callable[[FunctionInvocationContext], Awaitable[None]] + context: FunctionInvocationContext, call_next: Callable[[FunctionInvocationContext], Awaitable[None]] ) -> None: execution_order.append("function_before") context.metadata["function_middleware"] = True - await next(context) + await call_next(context) execution_order.append("function_after") pipeline = FunctionMiddlewarePipeline(test_function_middleware) @@ -884,14 +896,18 @@ async def test_mixed_agent_middleware(self, mock_agent: SupportsAgentRun) -> Non execution_order: list[str] = [] class ClassMiddleware(AgentMiddleware): - async def process(self, context: AgentContext, next: Callable[[AgentContext], Awaitable[None]]) -> None: + async def process( + self, context: AgentContext, call_next: Callable[[AgentContext], Awaitable[None]] + ) -> None: execution_order.append("class_before") - await next(context) + await call_next(context) execution_order.append("class_after") - async def function_middleware(context: AgentContext, next: Callable[[AgentContext], Awaitable[None]]) -> None: + async def function_middleware( + context: AgentContext, call_next: Callable[[AgentContext], Awaitable[None]] + ) -> None: execution_order.append("function_before") - await next(context) + await call_next(context) execution_order.append("function_after") pipeline = AgentMiddlewarePipeline(ClassMiddleware(), function_middleware) @@ -915,17 +931,17 @@ class ClassMiddleware(FunctionMiddleware): async def process( self, context: FunctionInvocationContext, - next: Callable[[FunctionInvocationContext], Awaitable[None]], + call_next: Callable[[FunctionInvocationContext], Awaitable[None]], ) -> None: execution_order.append("class_before") - await next(context) + await call_next(context) execution_order.append("class_after") async def function_middleware( - context: FunctionInvocationContext, next: Callable[[FunctionInvocationContext], Awaitable[None]] + context: FunctionInvocationContext, call_next: Callable[[FunctionInvocationContext], Awaitable[None]] ) -> None: execution_order.append("function_before") - await next(context) + await call_next(context) execution_order.append("function_after") pipeline = FunctionMiddlewarePipeline(ClassMiddleware(), function_middleware) @@ -946,16 +962,16 @@ async def test_mixed_chat_middleware(self, mock_chat_client: Any) -> None: execution_order: list[str] = [] class ClassChatMiddleware(ChatMiddleware): - async def process(self, context: ChatContext, next: Callable[[ChatContext], Awaitable[None]]) -> None: + async def process(self, context: ChatContext, call_next: Callable[[ChatContext], Awaitable[None]]) -> None: execution_order.append("class_before") - await next(context) + await call_next(context) execution_order.append("class_after") async def function_chat_middleware( - context: ChatContext, next: Callable[[ChatContext], Awaitable[None]] + context: ChatContext, call_next: Callable[[ChatContext], Awaitable[None]] ) -> None: execution_order.append("function_before") - await next(context) + await call_next(context) execution_order.append("function_after") pipeline = ChatMiddlewarePipeline(ClassChatMiddleware(), function_chat_middleware) @@ -981,21 +997,27 @@ async def test_agent_middleware_execution_order(self, mock_agent: SupportsAgentR execution_order: list[str] = [] class FirstMiddleware(AgentMiddleware): - async def process(self, context: AgentContext, next: Callable[[AgentContext], Awaitable[None]]) -> None: + async def process( + self, context: AgentContext, call_next: Callable[[AgentContext], Awaitable[None]] + ) -> None: execution_order.append("first_before") - await next(context) + await call_next(context) execution_order.append("first_after") class SecondMiddleware(AgentMiddleware): - async def process(self, context: AgentContext, next: Callable[[AgentContext], Awaitable[None]]) -> None: + async def process( + self, context: AgentContext, call_next: Callable[[AgentContext], Awaitable[None]] + ) -> None: execution_order.append("second_before") - await next(context) + await call_next(context) execution_order.append("second_after") class ThirdMiddleware(AgentMiddleware): - async def process(self, context: AgentContext, next: Callable[[AgentContext], Awaitable[None]]) -> None: + async def process( + self, context: AgentContext, call_next: Callable[[AgentContext], Awaitable[None]] + ) -> None: execution_order.append("third_before") - await next(context) + await call_next(context) execution_order.append("third_after") middleware = [FirstMiddleware(), SecondMiddleware(), ThirdMiddleware()] @@ -1029,20 +1051,20 @@ class FirstMiddleware(FunctionMiddleware): async def process( self, context: FunctionInvocationContext, - next: Callable[[FunctionInvocationContext], Awaitable[None]], + call_next: Callable[[FunctionInvocationContext], Awaitable[None]], ) -> None: execution_order.append("first_before") - await next(context) + await call_next(context) execution_order.append("first_after") class SecondMiddleware(FunctionMiddleware): async def process( self, context: FunctionInvocationContext, - next: Callable[[FunctionInvocationContext], Awaitable[None]], + call_next: Callable[[FunctionInvocationContext], Awaitable[None]], ) -> None: execution_order.append("second_before") - await next(context) + await call_next(context) execution_order.append("second_after") middleware = [FirstMiddleware(), SecondMiddleware()] @@ -1065,21 +1087,21 @@ async def test_chat_middleware_execution_order(self, mock_chat_client: Any) -> N execution_order: list[str] = [] class FirstChatMiddleware(ChatMiddleware): - async def process(self, context: ChatContext, next: Callable[[ChatContext], Awaitable[None]]) -> None: + async def process(self, context: ChatContext, call_next: Callable[[ChatContext], Awaitable[None]]) -> None: execution_order.append("first_before") - await next(context) + await call_next(context) execution_order.append("first_after") class SecondChatMiddleware(ChatMiddleware): - async def process(self, context: ChatContext, next: Callable[[ChatContext], Awaitable[None]]) -> None: + async def process(self, context: ChatContext, call_next: Callable[[ChatContext], Awaitable[None]]) -> None: execution_order.append("second_before") - await next(context) + await call_next(context) execution_order.append("second_after") class ThirdChatMiddleware(ChatMiddleware): - async def process(self, context: ChatContext, next: Callable[[ChatContext], Awaitable[None]]) -> None: + async def process(self, context: ChatContext, call_next: Callable[[ChatContext], Awaitable[None]]) -> None: execution_order.append("third_before") - await next(context) + await call_next(context) execution_order.append("third_after") middleware = [FirstChatMiddleware(), SecondChatMiddleware(), ThirdChatMiddleware()] @@ -1114,7 +1136,9 @@ async def test_agent_context_validation(self, mock_agent: SupportsAgentRun) -> N """Test that agent context contains expected data.""" class ContextValidationMiddleware(AgentMiddleware): - async def process(self, context: AgentContext, next: Callable[[AgentContext], Awaitable[None]]) -> None: + async def process( + self, context: AgentContext, call_next: Callable[[AgentContext], Awaitable[None]] + ) -> None: # Verify context has all expected attributes assert hasattr(context, "agent") assert hasattr(context, "messages") @@ -1132,7 +1156,7 @@ async def process(self, context: AgentContext, next: Callable[[AgentContext], Aw # Add custom metadata context.metadata["validated"] = True - await next(context) + await call_next(context) middleware = ContextValidationMiddleware() pipeline = AgentMiddlewarePipeline(middleware) @@ -1154,7 +1178,7 @@ class ContextValidationMiddleware(FunctionMiddleware): async def process( self, context: FunctionInvocationContext, - next: Callable[[FunctionInvocationContext], Awaitable[None]], + call_next: Callable[[FunctionInvocationContext], Awaitable[None]], ) -> None: # Verify context has all expected attributes assert hasattr(context, "function") @@ -1170,7 +1194,7 @@ async def process( # Add custom metadata context.metadata["validated"] = True - await next(context) + await call_next(context) middleware = ContextValidationMiddleware() pipeline = FunctionMiddlewarePipeline(middleware) @@ -1189,7 +1213,7 @@ async def test_chat_context_validation(self, mock_chat_client: Any) -> None: """Test that chat context contains expected data.""" class ChatContextValidationMiddleware(ChatMiddleware): - async def process(self, context: ChatContext, next: Callable[[ChatContext], Awaitable[None]]) -> None: + async def process(self, context: ChatContext, call_next: Callable[[ChatContext], Awaitable[None]]) -> None: # Verify context has all expected attributes assert hasattr(context, "chat_client") assert hasattr(context, "messages") @@ -1211,7 +1235,7 @@ async def process(self, context: ChatContext, next: Callable[[ChatContext], Awai # Add custom metadata context.metadata["validated"] = True - await next(context) + await call_next(context) middleware = ChatContextValidationMiddleware() pipeline = ChatMiddlewarePipeline(middleware) @@ -1236,9 +1260,11 @@ async def test_streaming_flag_validation(self, mock_agent: SupportsAgentRun) -> streaming_flags: list[bool] = [] class StreamingFlagMiddleware(AgentMiddleware): - async def process(self, context: AgentContext, next: Callable[[AgentContext], Awaitable[None]]) -> None: + async def process( + self, context: AgentContext, call_next: Callable[[AgentContext], Awaitable[None]] + ) -> None: streaming_flags.append(context.stream) - await next(context) + await call_next(context) middleware = StreamingFlagMiddleware() pipeline = AgentMiddlewarePipeline(middleware) @@ -1276,9 +1302,11 @@ async def test_streaming_middleware_behavior(self, mock_agent: SupportsAgentRun) chunks_processed: list[str] = [] class StreamProcessingMiddleware(AgentMiddleware): - async def process(self, context: AgentContext, next: Callable[[AgentContext], Awaitable[None]]) -> None: + async def process( + self, context: AgentContext, call_next: Callable[[AgentContext], Awaitable[None]] + ) -> None: chunks_processed.append("before_stream") - await next(context) + await call_next(context) chunks_processed.append("after_stream") middleware = StreamProcessingMiddleware() @@ -1317,9 +1345,9 @@ async def test_chat_streaming_flag_validation(self, mock_chat_client: Any) -> No streaming_flags: list[bool] = [] class ChatStreamingFlagMiddleware(ChatMiddleware): - async def process(self, context: ChatContext, next: Callable[[ChatContext], Awaitable[None]]) -> None: + async def process(self, context: ChatContext, call_next: Callable[[ChatContext], Awaitable[None]]) -> None: streaming_flags.append(context.stream) - await next(context) + await call_next(context) middleware = ChatStreamingFlagMiddleware() pipeline = ChatMiddlewarePipeline(middleware) @@ -1358,9 +1386,9 @@ async def test_chat_streaming_middleware_behavior(self, mock_chat_client: Any) - chunks_processed: list[str] = [] class ChatStreamProcessingMiddleware(ChatMiddleware): - async def process(self, context: ChatContext, next: Callable[[ChatContext], Awaitable[None]]) -> None: + async def process(self, context: ChatContext, call_next: Callable[[ChatContext], Awaitable[None]]) -> None: chunks_processed.append("before_stream") - await next(context) + await call_next(context) chunks_processed.append("after_stream") middleware = ChatStreamProcessingMiddleware() @@ -1408,24 +1436,24 @@ class FunctionTestArgs(BaseModel): class TestAgentMiddleware(AgentMiddleware): """Test implementation of AgentMiddleware.""" - async def process(self, context: AgentContext, next: Callable[[AgentContext], Awaitable[None]]) -> None: - await next(context) + async def process(self, context: AgentContext, call_next: Callable[[AgentContext], Awaitable[None]]) -> None: + await call_next(context) class TestFunctionMiddleware(FunctionMiddleware): """Test implementation of FunctionMiddleware.""" async def process( - self, context: FunctionInvocationContext, next: Callable[[FunctionInvocationContext], Awaitable[None]] + self, context: FunctionInvocationContext, call_next: Callable[[FunctionInvocationContext], Awaitable[None]] ) -> None: - await next(context) + await call_next(context) class TestChatMiddleware(ChatMiddleware): """Test implementation of ChatMiddleware.""" - async def process(self, context: ChatContext, next: Callable[[ChatContext], Awaitable[None]]) -> None: - await next(context) + async def process(self, context: ChatContext, call_next: Callable[[ChatContext], Awaitable[None]]) -> None: + await call_next(context) class MockFunctionArgs(BaseModel): @@ -1441,7 +1469,9 @@ async def test_agent_middleware_no_next_no_execution(self, mock_agent: SupportsA """Test that when agent middleware doesn't call next(), no execution happens.""" class NoNextMiddleware(AgentMiddleware): - async def process(self, context: AgentContext, next: Callable[[AgentContext], Awaitable[None]]) -> None: + async def process( + self, context: AgentContext, call_next: Callable[[AgentContext], Awaitable[None]] + ) -> None: # Don't call next() - this should prevent any execution pass @@ -1468,7 +1498,9 @@ async def test_agent_middleware_no_next_no_streaming_execution(self, mock_agent: """Test that when agent middleware doesn't call next(), no streaming execution happens.""" class NoNextStreamingMiddleware(AgentMiddleware): - async def process(self, context: AgentContext, next: Callable[[AgentContext], Awaitable[None]]) -> None: + async def process( + self, context: AgentContext, call_next: Callable[[AgentContext], Awaitable[None]] + ) -> None: # Don't call next() - this should prevent any execution pass @@ -1505,7 +1537,7 @@ class NoNextFunctionMiddleware(FunctionMiddleware): async def process( self, context: FunctionInvocationContext, - next: Callable[[FunctionInvocationContext], Awaitable[None]], + call_next: Callable[[FunctionInvocationContext], Awaitable[None]], ) -> None: # Don't call next() - this should prevent any execution pass @@ -1534,14 +1566,18 @@ async def test_multiple_middlewares_early_stop(self, mock_agent: SupportsAgentRu execution_order: list[str] = [] class FirstMiddleware(AgentMiddleware): - async def process(self, context: AgentContext, next: Callable[[AgentContext], Awaitable[None]]) -> None: + async def process( + self, context: AgentContext, call_next: Callable[[AgentContext], Awaitable[None]] + ) -> None: execution_order.append("first") # Don't call next() - this should stop the pipeline class SecondMiddleware(AgentMiddleware): - async def process(self, context: AgentContext, next: Callable[[AgentContext], Awaitable[None]]) -> None: + async def process( + self, context: AgentContext, call_next: Callable[[AgentContext], Awaitable[None]] + ) -> None: execution_order.append("second") - await next(context) + await call_next(context) pipeline = AgentMiddlewarePipeline(FirstMiddleware(), SecondMiddleware()) messages = [ChatMessage(role="user", text="test")] @@ -1565,7 +1601,7 @@ async def test_chat_middleware_no_next_no_execution(self, mock_chat_client: Any) """Test that when chat middleware doesn't call next(), no execution happens.""" class NoNextChatMiddleware(ChatMiddleware): - async def process(self, context: ChatContext, next: Callable[[ChatContext], Awaitable[None]]) -> None: + async def process(self, context: ChatContext, call_next: Callable[[ChatContext], Awaitable[None]]) -> None: # Don't call next() - this should prevent any execution pass @@ -1593,7 +1629,7 @@ async def test_chat_middleware_no_next_no_streaming_execution(self, mock_chat_cl """Test that when chat middleware doesn't call next(), no streaming execution happens.""" class NoNextStreamingChatMiddleware(ChatMiddleware): - async def process(self, context: ChatContext, next: Callable[[ChatContext], Awaitable[None]]) -> None: + async def process(self, context: ChatContext, call_next: Callable[[ChatContext], Awaitable[None]]) -> None: # Don't call next() - this should prevent any execution pass @@ -1634,14 +1670,14 @@ async def test_multiple_chat_middlewares_early_stop(self, mock_chat_client: Any) execution_order: list[str] = [] class FirstChatMiddleware(ChatMiddleware): - async def process(self, context: ChatContext, next: Callable[[ChatContext], Awaitable[None]]) -> None: + async def process(self, context: ChatContext, call_next: Callable[[ChatContext], Awaitable[None]]) -> None: execution_order.append("first") # Don't call next() - this should stop the pipeline class SecondChatMiddleware(ChatMiddleware): - async def process(self, context: ChatContext, next: Callable[[ChatContext], Awaitable[None]]) -> None: + async def process(self, context: ChatContext, call_next: Callable[[ChatContext], Awaitable[None]]) -> None: execution_order.append("second") - await next(context) + await call_next(context) pipeline = ChatMiddlewarePipeline(FirstChatMiddleware(), SecondChatMiddleware()) messages = [ChatMessage(role="user", text="test")] diff --git a/python/packages/core/tests/core/test_middleware_context_result.py b/python/packages/core/tests/core/test_middleware_context_result.py index b4fc945577..abdea790df 100644 --- a/python/packages/core/tests/core/test_middleware_context_result.py +++ b/python/packages/core/tests/core/test_middleware_context_result.py @@ -43,9 +43,11 @@ async def test_agent_middleware_response_override_non_streaming(self, mock_agent override_response = AgentResponse(messages=[ChatMessage(role="assistant", text="overridden response")]) class ResponseOverrideMiddleware(AgentMiddleware): - async def process(self, context: AgentContext, next: Callable[[AgentContext], Awaitable[None]]) -> None: + async def process( + self, context: AgentContext, call_next: Callable[[AgentContext], Awaitable[None]] + ) -> None: # Execute the pipeline first, then override the response - await next(context) + await call_next(context) context.result = override_response middleware = ResponseOverrideMiddleware() @@ -77,9 +79,11 @@ async def override_stream() -> AsyncIterable[AgentResponseUpdate]: yield AgentResponseUpdate(contents=[Content.from_text(text=" stream")]) class StreamResponseOverrideMiddleware(AgentMiddleware): - async def process(self, context: AgentContext, next: Callable[[AgentContext], Awaitable[None]]) -> None: + async def process( + self, context: AgentContext, call_next: Callable[[AgentContext], Awaitable[None]] + ) -> None: # Execute the pipeline first, then override the response stream - await next(context) + await call_next(context) context.result = ResponseStream(override_stream()) middleware = StreamResponseOverrideMiddleware() @@ -111,10 +115,10 @@ class ResultOverrideMiddleware(FunctionMiddleware): async def process( self, context: FunctionInvocationContext, - next: Callable[[FunctionInvocationContext], Awaitable[None]], + call_next: Callable[[FunctionInvocationContext], Awaitable[None]], ) -> None: # Execute the pipeline first, then override the result - await next(context) + await call_next(context) context.result = override_result middleware = ResultOverrideMiddleware() @@ -141,9 +145,11 @@ async def test_chat_agent_middleware_response_override(self) -> None: mock_chat_client = MockChatClient() class ChatAgentResponseOverrideMiddleware(AgentMiddleware): - async def process(self, context: AgentContext, next: Callable[[AgentContext], Awaitable[None]]) -> None: + async def process( + self, context: AgentContext, call_next: Callable[[AgentContext], Awaitable[None]] + ) -> None: # Always call next() first to allow execution - await next(context) + await call_next(context) # Then conditionally override based on content if any("special" in msg.text for msg in context.messages if msg.text): context.result = AgentResponse( @@ -178,13 +184,15 @@ async def custom_stream() -> AsyncIterable[AgentResponseUpdate]: yield AgentResponseUpdate(contents=[Content.from_text(text=" response!")]) class ChatAgentStreamOverrideMiddleware(AgentMiddleware): - async def process(self, context: AgentContext, next: Callable[[AgentContext], Awaitable[None]]) -> None: + async def process( + self, context: AgentContext, call_next: Callable[[AgentContext], Awaitable[None]] + ) -> None: # Check if we want to override BEFORE calling next to avoid creating unused streams if any("custom stream" in msg.text for msg in context.messages if msg.text): context.result = ResponseStream(custom_stream()) return # Don't call next() - we're overriding the entire result # Normal case - let the agent handle it - await next(context) + await call_next(context) # Create ChatAgent with override middleware middleware = ChatAgentStreamOverrideMiddleware() @@ -215,10 +223,12 @@ async def test_agent_middleware_conditional_no_next(self, mock_agent: SupportsAg """Test that when agent middleware conditionally doesn't call next(), no execution happens.""" class ConditionalNoNextMiddleware(AgentMiddleware): - async def process(self, context: AgentContext, next: Callable[[AgentContext], Awaitable[None]]) -> None: + async def process( + self, context: AgentContext, call_next: Callable[[AgentContext], Awaitable[None]] + ) -> None: # Only call next() if message contains "execute" if any("execute" in msg.text for msg in context.messages if msg.text): - await next(context) + await call_next(context) # Otherwise, don't call next() - no execution should happen middleware = ConditionalNoNextMiddleware() @@ -259,13 +269,13 @@ class ConditionalNoNextFunctionMiddleware(FunctionMiddleware): async def process( self, context: FunctionInvocationContext, - next: Callable[[FunctionInvocationContext], Awaitable[None]], + call_next: Callable[[FunctionInvocationContext], Awaitable[None]], ) -> None: # Only call next() if argument name contains "execute" args = context.arguments assert isinstance(args, FunctionTestArgs) if "execute" in args.name: - await next(context) + await call_next(context) # Otherwise, don't call next() - no execution should happen middleware = ConditionalNoNextFunctionMiddleware() @@ -308,12 +318,14 @@ async def test_agent_middleware_response_observability(self, mock_agent: Support observed_responses: list[AgentResponse] = [] class ObservabilityMiddleware(AgentMiddleware): - async def process(self, context: AgentContext, next: Callable[[AgentContext], Awaitable[None]]) -> None: + async def process( + self, context: AgentContext, call_next: Callable[[AgentContext], Awaitable[None]] + ) -> None: # Context should be empty before next() assert context.result is None # Call next to execute - await next(context) + await call_next(context) # Context should now contain the response for observability assert context.result is not None @@ -343,13 +355,13 @@ class ObservabilityMiddleware(FunctionMiddleware): async def process( self, context: FunctionInvocationContext, - next: Callable[[FunctionInvocationContext], Awaitable[None]], + call_next: Callable[[FunctionInvocationContext], Awaitable[None]], ) -> None: # Context should be empty before next() assert context.result is None # Call next to execute - await next(context) + await call_next(context) # Context should now contain the result for observability assert context.result is not None @@ -374,9 +386,11 @@ async def test_agent_middleware_post_execution_override(self, mock_agent: Suppor """Test that middleware can override response after observing execution.""" class PostExecutionOverrideMiddleware(AgentMiddleware): - async def process(self, context: AgentContext, next: Callable[[AgentContext], Awaitable[None]]) -> None: + async def process( + self, context: AgentContext, call_next: Callable[[AgentContext], Awaitable[None]] + ) -> None: # Call next to execute first - await next(context) + await call_next(context) # Now observe and conditionally override assert context.result is not None @@ -409,10 +423,10 @@ class PostExecutionOverrideMiddleware(FunctionMiddleware): async def process( self, context: FunctionInvocationContext, - next: Callable[[FunctionInvocationContext], Awaitable[None]], + call_next: Callable[[FunctionInvocationContext], Awaitable[None]], ) -> None: # Call next to execute first - await next(context) + await call_next(context) # Now observe and conditionally override assert context.result is not None diff --git a/python/packages/core/tests/core/test_middleware_with_agent.py b/python/packages/core/tests/core/test_middleware_with_agent.py index 9c516259ca..10cc8b3011 100644 --- a/python/packages/core/tests/core/test_middleware_with_agent.py +++ b/python/packages/core/tests/core/test_middleware_with_agent.py @@ -44,9 +44,11 @@ class TrackingAgentMiddleware(AgentMiddleware): def __init__(self, name: str): self.name = name - async def process(self, context: AgentContext, next: Callable[[AgentContext], Awaitable[None]]) -> None: + async def process( + self, context: AgentContext, call_next: Callable[[AgentContext], Awaitable[None]] + ) -> None: execution_order.append(f"{self.name}_before") - await next(context) + await call_next(context) execution_order.append(f"{self.name}_after") # Create ChatAgent with middleware @@ -74,9 +76,9 @@ class TrackingFunctionMiddleware(FunctionMiddleware): async def process( self, context: FunctionInvocationContext, - next: Callable[[FunctionInvocationContext], Awaitable[None]], + call_next: Callable[[FunctionInvocationContext], Awaitable[None]], ) -> None: - await next(context) + await call_next(context) middleware = TrackingFunctionMiddleware() ChatAgent(chat_client=chat_client, middleware=[middleware]) @@ -94,10 +96,10 @@ def __init__(self, name: str): async def process( self, context: FunctionInvocationContext, - next: Callable[[FunctionInvocationContext], Awaitable[None]], + call_next: Callable[[FunctionInvocationContext], Awaitable[None]], ) -> None: execution_order.append(f"{self.name}_before") - await next(context) + await call_next(context) execution_order.append(f"{self.name}_after") middleware = TrackingFunctionMiddleware("function_middleware") @@ -120,11 +122,13 @@ async def test_agent_middleware_with_pre_termination(self, chat_client: "MockCha execution_order: list[str] = [] class PreTerminationMiddleware(AgentMiddleware): - async def process(self, context: AgentContext, next: Callable[[AgentContext], Awaitable[None]]) -> None: + async def process( + self, context: AgentContext, call_next: Callable[[AgentContext], Awaitable[None]] + ) -> None: execution_order.append("middleware_before") raise MiddlewareTermination # Code after raise is unreachable - await next(context) + await call_next(context) execution_order.append("middleware_after") # Create ChatAgent with terminating middleware @@ -149,9 +153,11 @@ async def test_agent_middleware_with_post_termination(self, chat_client: "MockCh execution_order: list[str] = [] class PostTerminationMiddleware(AgentMiddleware): - async def process(self, context: AgentContext, next: Callable[[AgentContext], Awaitable[None]]) -> None: + async def process( + self, context: AgentContext, call_next: Callable[[AgentContext], Awaitable[None]] + ) -> None: execution_order.append("middleware_before") - await next(context) + await call_next(context) execution_order.append("middleware_after") context.terminate = True @@ -187,12 +193,12 @@ class PreTerminationFunctionMiddleware(FunctionMiddleware): async def process( self, context: FunctionInvocationContext, - next: Callable[[FunctionInvocationContext], Awaitable[None]], + call_next: Callable[[FunctionInvocationContext], Awaitable[None]], ) -> None: execution_order.append("middleware_before") context.terminate = True # We call next() but since terminate=True, subsequent middleware and handler should not execute - await next(context) + await call_next(context) execution_order.append("middleware_after") ChatAgent(chat_client=chat_client, middleware=[PreTerminationFunctionMiddleware()], tools=[]) @@ -205,10 +211,10 @@ class PostTerminationFunctionMiddleware(FunctionMiddleware): async def process( self, context: FunctionInvocationContext, - next: Callable[[FunctionInvocationContext], Awaitable[None]], + call_next: Callable[[FunctionInvocationContext], Awaitable[None]], ) -> None: execution_order.append("middleware_before") - await next(context) + await call_next(context) execution_order.append("middleware_after") context.terminate = True @@ -219,10 +225,10 @@ async def test_function_based_agent_middleware_with_chat_agent(self, chat_client execution_order: list[str] = [] async def tracking_agent_middleware( - context: AgentContext, next: Callable[[AgentContext], Awaitable[None]] + context: AgentContext, call_next: Callable[[AgentContext], Awaitable[None]] ) -> None: execution_order.append("agent_function_before") - await next(context) + await call_next(context) execution_order.append("agent_function_after") # Create ChatAgent with function middleware @@ -246,9 +252,9 @@ async def test_function_based_function_middleware_with_chat_agent(self, chat_cli """Test function-based function middleware with ChatAgent.""" async def tracking_function_middleware( - context: FunctionInvocationContext, next: Callable[[FunctionInvocationContext], Awaitable[None]] + context: FunctionInvocationContext, call_next: Callable[[FunctionInvocationContext], Awaitable[None]] ) -> None: - await next(context) + await call_next(context) ChatAgent(chat_client=chat_client, middleware=[tracking_function_middleware]) @@ -259,10 +265,10 @@ async def test_function_based_function_middleware_with_supported_client( execution_order: list[str] = [] async def tracking_function_middleware( - context: FunctionInvocationContext, next: Callable[[FunctionInvocationContext], Awaitable[None]] + context: FunctionInvocationContext, call_next: Callable[[FunctionInvocationContext], Awaitable[None]] ) -> None: execution_order.append("function_function_before") - await next(context) + await call_next(context) execution_order.append("function_function_after") agent = ChatAgent(chat_client=chat_client_base, middleware=[tracking_function_middleware]) @@ -284,10 +290,12 @@ async def test_agent_middleware_with_streaming(self, chat_client: "MockChatClien streaming_flags: list[bool] = [] class StreamingTrackingMiddleware(AgentMiddleware): - async def process(self, context: AgentContext, next: Callable[[AgentContext], Awaitable[None]]) -> None: + async def process( + self, context: AgentContext, call_next: Callable[[AgentContext], Awaitable[None]] + ) -> None: execution_order.append("middleware_before") streaming_flags.append(context.stream) - await next(context) + await call_next(context) execution_order.append("middleware_after") # Create ChatAgent with middleware @@ -326,9 +334,11 @@ async def test_non_streaming_vs_streaming_flag_validation(self, chat_client: "Mo streaming_flags: list[bool] = [] class FlagTrackingMiddleware(AgentMiddleware): - async def process(self, context: AgentContext, next: Callable[[AgentContext], Awaitable[None]]) -> None: + async def process( + self, context: AgentContext, call_next: Callable[[AgentContext], Awaitable[None]] + ) -> None: streaming_flags.append(context.stream) - await next(context) + await call_next(context) # Create ChatAgent with middleware middleware = FlagTrackingMiddleware() @@ -358,9 +368,11 @@ class OrderedMiddleware(AgentMiddleware): def __init__(self, name: str): self.name = name - async def process(self, context: AgentContext, next: Callable[[AgentContext], Awaitable[None]]) -> None: + async def process( + self, context: AgentContext, call_next: Callable[[AgentContext], Awaitable[None]] + ) -> None: execution_order.append(f"{self.name}_before") - await next(context) + await call_next(context) execution_order.append(f"{self.name}_after") # Create multiple middleware @@ -388,33 +400,35 @@ async def test_mixed_middleware_types_with_chat_agent(self, chat_client_base: "M execution_order: list[str] = [] class ClassAgentMiddleware(AgentMiddleware): - async def process(self, context: AgentContext, next: Callable[[AgentContext], Awaitable[None]]) -> None: + async def process( + self, context: AgentContext, call_next: Callable[[AgentContext], Awaitable[None]] + ) -> None: execution_order.append("class_agent_before") - await next(context) + await call_next(context) execution_order.append("class_agent_after") async def function_agent_middleware( - context: AgentContext, next: Callable[[AgentContext], Awaitable[None]] + context: AgentContext, call_next: Callable[[AgentContext], Awaitable[None]] ) -> None: execution_order.append("function_agent_before") - await next(context) + await call_next(context) execution_order.append("function_agent_after") class ClassFunctionMiddleware(FunctionMiddleware): async def process( self, context: FunctionInvocationContext, - next: Callable[[FunctionInvocationContext], Awaitable[None]], + call_next: Callable[[FunctionInvocationContext], Awaitable[None]], ) -> None: execution_order.append("class_function_before") - await next(context) + await call_next(context) execution_order.append("class_function_after") async def function_function_middleware( - context: FunctionInvocationContext, next: Callable[[FunctionInvocationContext], Awaitable[None]] + context: FunctionInvocationContext, call_next: Callable[[FunctionInvocationContext], Awaitable[None]] ) -> None: execution_order.append("function_function_before") - await next(context) + await call_next(context) execution_order.append("function_function_after") agent = ChatAgent( @@ -433,23 +447,25 @@ async def test_mixed_middleware_types_with_supported_client(self, chat_client_ba execution_order: list[str] = [] class ClassAgentMiddleware(AgentMiddleware): - async def process(self, context: AgentContext, next: Callable[[AgentContext], Awaitable[None]]) -> None: + async def process( + self, context: AgentContext, call_next: Callable[[AgentContext], Awaitable[None]] + ) -> None: execution_order.append("class_agent_before") - await next(context) + await call_next(context) execution_order.append("class_agent_after") async def function_agent_middleware( - context: AgentContext, next: Callable[[AgentContext], Awaitable[None]] + context: AgentContext, call_next: Callable[[AgentContext], Awaitable[None]] ) -> None: execution_order.append("function_agent_before") - await next(context) + await call_next(context) execution_order.append("function_agent_after") async def function_function_middleware( - context: FunctionInvocationContext, next: Callable[[FunctionInvocationContext], Awaitable[None]] + context: FunctionInvocationContext, call_next: Callable[[FunctionInvocationContext], Awaitable[None]] ) -> None: execution_order.append("function_function_before") - await next(context) + await call_next(context) execution_order.append("function_function_after") agent = ChatAgent( @@ -505,10 +521,10 @@ def __init__(self, name: str): async def process( self, context: FunctionInvocationContext, - next: Callable[[FunctionInvocationContext], Awaitable[None]], + call_next: Callable[[FunctionInvocationContext], Awaitable[None]], ) -> None: execution_order.append(f"{self.name}_before") - await next(context) + await call_next(context) execution_order.append(f"{self.name}_after") # Set up mock to return a function call first, then a regular response @@ -567,10 +583,10 @@ async def test_function_based_function_middleware_with_tool_calls( execution_order: list[str] = [] async def tracking_function_middleware( - context: FunctionInvocationContext, next: Callable[[FunctionInvocationContext], Awaitable[None]] + context: FunctionInvocationContext, call_next: Callable[[FunctionInvocationContext], Awaitable[None]] ) -> None: execution_order.append("function_middleware_before") - await next(context) + await call_next(context) execution_order.append("function_middleware_after") # Set up mock to return a function call first, then a regular response @@ -631,20 +647,20 @@ class TrackingAgentMiddleware(AgentMiddleware): async def process( self, context: AgentContext, - next: Callable[[AgentContext], Awaitable[None]], + call_next: Callable[[AgentContext], Awaitable[None]], ) -> None: execution_order.append("agent_middleware_before") - await next(context) + await call_next(context) execution_order.append("agent_middleware_after") class TrackingFunctionMiddleware(FunctionMiddleware): async def process( self, context: FunctionInvocationContext, - next: Callable[[FunctionInvocationContext], Awaitable[None]], + call_next: Callable[[FunctionInvocationContext], Awaitable[None]], ) -> None: execution_order.append("function_middleware_before") - await next(context) + await call_next(context) execution_order.append("function_middleware_after") # Set up mock to return a function call first, then a regular response @@ -712,7 +728,7 @@ async def test_function_middleware_can_access_and_override_custom_kwargs( @function_middleware async def kwargs_middleware( - context: FunctionInvocationContext, next: Callable[[FunctionInvocationContext], Awaitable[None]] + context: FunctionInvocationContext, call_next: Callable[[FunctionInvocationContext], Awaitable[None]] ) -> None: nonlocal middleware_called middleware_called = True @@ -732,7 +748,7 @@ async def kwargs_middleware( modified_kwargs["new_param"] = context.kwargs.get("new_param") modified_kwargs["custom_param"] = context.kwargs.get("custom_param") - await next(context) + await call_next(context) chat_client_base.run_responses = [ ChatResponse( @@ -785,9 +801,9 @@ def __init__(self, name: str, execution_log: list[str]): self.name = name self.execution_log = execution_log - async def process(self, context: AgentContext, next: Callable[[AgentContext], Awaitable[None]]) -> None: + async def process(self, context: AgentContext, call_next: Callable[[AgentContext], Awaitable[None]]) -> None: self.execution_log.append(f"{self.name}_start") - await next(context) + await call_next(context) self.execution_log.append(f"{self.name}_end") async def test_middleware_dynamic_rebuild_non_streaming(self, chat_client: "MockChatClient") -> None: @@ -908,9 +924,9 @@ def __init__(self, name: str, execution_log: list[str]): self.name = name self.execution_log = execution_log - async def process(self, context: AgentContext, next: Callable[[AgentContext], Awaitable[None]]) -> None: + async def process(self, context: AgentContext, call_next: Callable[[AgentContext], Awaitable[None]]) -> None: self.execution_log.append(f"{self.name}_start") - await next(context) + await call_next(context) self.execution_log.append(f"{self.name}_end") async def test_run_level_middleware_isolation(self, chat_client: "MockChatClient") -> None: @@ -960,25 +976,29 @@ class MetadataAgentMiddleware(AgentMiddleware): def __init__(self, name: str): self.name = name - async def process(self, context: AgentContext, next: Callable[[AgentContext], Awaitable[None]]) -> None: + async def process( + self, context: AgentContext, call_next: Callable[[AgentContext], Awaitable[None]] + ) -> None: execution_log.append(f"{self.name}_start") # Set metadata to pass information to run middleware context.metadata[f"{self.name}_key"] = f"{self.name}_value" - await next(context) + await call_next(context) execution_log.append(f"{self.name}_end") class MetadataRunMiddleware(AgentMiddleware): def __init__(self, name: str): self.name = name - async def process(self, context: AgentContext, next: Callable[[AgentContext], Awaitable[None]]) -> None: + async def process( + self, context: AgentContext, call_next: Callable[[AgentContext], Awaitable[None]] + ) -> None: execution_log.append(f"{self.name}_start") # Read metadata set by agent middleware for key, value in context.metadata.items(): metadata_log.append(f"{self.name}_reads_{key}:{value}") # Set run-level metadata context.metadata[f"{self.name}_key"] = f"{self.name}_value" - await next(context) + await call_next(context) execution_log.append(f"{self.name}_end") # Create agent with agent-level middleware @@ -1029,10 +1049,12 @@ class StreamingTrackingMiddleware(AgentMiddleware): def __init__(self, name: str): self.name = name - async def process(self, context: AgentContext, next: Callable[[AgentContext], Awaitable[None]]) -> None: + async def process( + self, context: AgentContext, call_next: Callable[[AgentContext], Awaitable[None]] + ) -> None: execution_log.append(f"{self.name}_start") streaming_flags.append(context.stream) - await next(context) + await call_next(context) execution_log.append(f"{self.name}_end") # Create agent without agent-level middleware @@ -1071,44 +1093,48 @@ async def test_agent_and_run_level_both_agent_and_function_middleware( # Agent-level middleware class AgentLevelAgentMiddleware(AgentMiddleware): - async def process(self, context: AgentContext, next: Callable[[AgentContext], Awaitable[None]]) -> None: + async def process( + self, context: AgentContext, call_next: Callable[[AgentContext], Awaitable[None]] + ) -> None: execution_log.append("agent_level_agent_start") context.metadata["agent_level_agent"] = "processed" - await next(context) + await call_next(context) execution_log.append("agent_level_agent_end") class AgentLevelFunctionMiddleware(FunctionMiddleware): async def process( self, context: FunctionInvocationContext, - next: Callable[[FunctionInvocationContext], Awaitable[None]], + call_next: Callable[[FunctionInvocationContext], Awaitable[None]], ) -> None: execution_log.append("agent_level_function_start") context.metadata["agent_level_function"] = "processed" - await next(context) + await call_next(context) execution_log.append("agent_level_function_end") # Run-level middleware class RunLevelAgentMiddleware(AgentMiddleware): - async def process(self, context: AgentContext, next: Callable[[AgentContext], Awaitable[None]]) -> None: + async def process( + self, context: AgentContext, call_next: Callable[[AgentContext], Awaitable[None]] + ) -> None: execution_log.append("run_level_agent_start") # Verify agent-level middleware metadata is available assert "agent_level_agent" in context.metadata context.metadata["run_level_agent"] = "processed" - await next(context) + await call_next(context) execution_log.append("run_level_agent_end") class RunLevelFunctionMiddleware(FunctionMiddleware): async def process( self, context: FunctionInvocationContext, - next: Callable[[FunctionInvocationContext], Awaitable[None]], + call_next: Callable[[FunctionInvocationContext], Awaitable[None]], ) -> None: execution_log.append("run_level_function_start") # Verify agent-level function middleware metadata is available assert "agent_level_function" in context.metadata context.metadata["run_level_function"] = "processed" - await next(context) + await call_next(context) execution_log.append("run_level_function_end") # Create tool function for testing function middleware @@ -1192,17 +1218,17 @@ async def test_decorator_and_type_match(self, chat_client_base: "MockBaseChatCli @agent_middleware async def matching_agent_middleware( - context: AgentContext, next: Callable[[AgentContext], Awaitable[None]] + context: AgentContext, call_next: Callable[[AgentContext], Awaitable[None]] ) -> None: execution_order.append("decorator_type_match_agent") - await next(context) + await call_next(context) @function_middleware async def matching_function_middleware( - context: FunctionInvocationContext, next: Callable[[FunctionInvocationContext], Awaitable[None]] + context: FunctionInvocationContext, call_next: Callable[[FunctionInvocationContext], Awaitable[None]] ) -> None: execution_order.append("decorator_type_match_function") - await next(context) + await call_next(context) # Create tool function for testing function middleware def custom_tool(message: str) -> str: @@ -1254,9 +1280,9 @@ async def test_decorator_and_type_mismatch(self, chat_client: MockChatClient) -> @agent_middleware # type: ignore[arg-type] async def mismatched_middleware( context: FunctionInvocationContext, # Wrong type for @agent_middleware - next: Any, + call_next: Any, ) -> None: - await next(context) + await call_next(context) agent = ChatAgent(chat_client=chat_client, middleware=[mismatched_middleware]) await agent.run([ChatMessage(role="user", text="test")]) @@ -1266,14 +1292,14 @@ async def test_only_decorator_specified(self, chat_client_base: "MockBaseChatCli execution_order: list[str] = [] @agent_middleware - async def decorator_only_agent(context: Any, next: Any) -> None: # No type annotation + async def decorator_only_agent(context: Any, call_next: Any) -> None: # No type annotation execution_order.append("decorator_only_agent") - await next(context) + await call_next(context) @function_middleware - async def decorator_only_function(context: Any, next: Any) -> None: # No type annotation + async def decorator_only_function(context: Any, call_next: Any) -> None: # No type annotation execution_order.append("decorator_only_function") - await next(context) + await call_next(context) # Create tool function for testing function middleware def custom_tool(message: str) -> str: @@ -1320,16 +1346,16 @@ async def test_only_type_specified(self, chat_client_base: "MockBaseChatClient") execution_order: list[str] = [] # No decorator - async def type_only_agent(context: AgentContext, next: Callable[[AgentContext], Awaitable[None]]) -> None: + async def type_only_agent(context: AgentContext, call_next: Callable[[AgentContext], Awaitable[None]]) -> None: execution_order.append("type_only_agent") - await next(context) + await call_next(context) # No decorator async def type_only_function( - context: FunctionInvocationContext, next: Callable[[FunctionInvocationContext], Awaitable[None]] + context: FunctionInvocationContext, call_next: Callable[[FunctionInvocationContext], Awaitable[None]] ) -> None: execution_order.append("type_only_function") - await next(context) + await call_next(context) # Create tool function for testing function middleware def custom_tool(message: str) -> str: @@ -1372,8 +1398,8 @@ def custom_tool(message: str) -> str: async def test_neither_decorator_nor_type(self, chat_client: Any) -> None: """Neither decorator nor parameter type specified - should throw exception.""" - async def no_info_middleware(context: Any, next: Any) -> None: # No decorator, no type - await next(context) + async def no_info_middleware(context: Any, call_next: Any) -> None: # No decorator, no type + await call_next(context) # Should raise MiddlewareException with pytest.raises(MiddlewareException, match="Cannot determine middleware type"): @@ -1398,11 +1424,11 @@ async def test_decorator_markers_preserved(self) -> None: """Test that decorator markers are properly set on functions.""" @agent_middleware - async def test_agent_middleware(context: Any, next: Any) -> None: + async def test_agent_middleware(context: Any, call_next: Any) -> None: pass @function_middleware - async def test_function_middleware(context: Any, next: Any) -> None: + async def test_function_middleware(context: Any, call_next: Any) -> None: pass # Check that decorator markers were set @@ -1421,7 +1447,9 @@ async def test_agent_context_thread_behavior_across_multiple_runs(self, chat_cli thread_states: list[dict[str, Any]] = [] class ThreadTrackingMiddleware(AgentMiddleware): - async def process(self, context: AgentContext, next: Callable[[AgentContext], Awaitable[None]]) -> None: + async def process( + self, context: AgentContext, call_next: Callable[[AgentContext], Awaitable[None]] + ) -> None: # Capture state before next() call thread_messages = [] if context.thread and context.thread.message_store: @@ -1436,7 +1464,7 @@ async def process(self, context: AgentContext, next: Callable[[AgentContext], Aw } thread_states.append(before_state) - await next(context) + await call_next(context) # Capture state after next() call thread_messages_after = [] @@ -1532,9 +1560,9 @@ async def test_class_based_chat_middleware_with_chat_agent(self) -> None: execution_order: list[str] = [] class TrackingChatMiddleware(ChatMiddleware): - async def process(self, context: ChatContext, next: Callable[[ChatContext], Awaitable[None]]) -> None: + async def process(self, context: ChatContext, call_next: Callable[[ChatContext], Awaitable[None]]) -> None: execution_order.append("chat_middleware_before") - await next(context) + await call_next(context) execution_order.append("chat_middleware_after") # Create ChatAgent with chat middleware @@ -1561,10 +1589,10 @@ async def test_function_based_chat_middleware_with_chat_agent(self) -> None: execution_order: list[str] = [] async def tracking_chat_middleware( - context: ChatContext, next: Callable[[ChatContext], Awaitable[None]] + context: ChatContext, call_next: Callable[[ChatContext], Awaitable[None]] ) -> None: execution_order.append("chat_middleware_before") - await next(context) + await call_next(context) execution_order.append("chat_middleware_after") # Create ChatAgent with function-based chat middleware @@ -1590,7 +1618,7 @@ async def test_chat_middleware_can_modify_messages(self) -> None: @chat_middleware async def message_modifier_middleware( - context: ChatContext, next: Callable[[ChatContext], Awaitable[None]] + context: ChatContext, call_next: Callable[[ChatContext], Awaitable[None]] ) -> None: # Modify the first message by adding a prefix if context.messages: @@ -1600,7 +1628,7 @@ async def message_modifier_middleware( original_text = msg.text or "" context.messages[idx] = ChatMessage(role=msg.role, text=f"MODIFIED: {original_text}") break - await next(context) + await call_next(context) # Create ChatAgent with message-modifying middleware chat_client = MockBaseChatClient() @@ -1619,7 +1647,7 @@ async def test_chat_middleware_can_override_response(self) -> None: @chat_middleware async def response_override_middleware( - context: ChatContext, next: Callable[[ChatContext], Awaitable[None]] + context: ChatContext, call_next: Callable[[ChatContext], Awaitable[None]] ) -> None: # Override the response without calling next() context.result = ChatResponse( @@ -1647,15 +1675,15 @@ async def test_multiple_chat_middleware_execution_order(self) -> None: execution_order: list[str] = [] @chat_middleware - async def first_middleware(context: ChatContext, next: Callable[[ChatContext], Awaitable[None]]) -> None: + async def first_middleware(context: ChatContext, call_next: Callable[[ChatContext], Awaitable[None]]) -> None: execution_order.append("first_before") - await next(context) + await call_next(context) execution_order.append("first_after") @chat_middleware - async def second_middleware(context: ChatContext, next: Callable[[ChatContext], Awaitable[None]]) -> None: + async def second_middleware(context: ChatContext, call_next: Callable[[ChatContext], Awaitable[None]]) -> None: execution_order.append("second_before") - await next(context) + await call_next(context) execution_order.append("second_after") # Create ChatAgent with multiple chat middleware @@ -1681,10 +1709,10 @@ async def test_chat_middleware_with_streaming(self) -> None: streaming_flags: list[bool] = [] class StreamingTrackingChatMiddleware(ChatMiddleware): - async def process(self, context: ChatContext, next: Callable[[ChatContext], Awaitable[None]]) -> None: + async def process(self, context: ChatContext, call_next: Callable[[ChatContext], Awaitable[None]]) -> None: execution_order.append("streaming_chat_before") streaming_flags.append(context.stream) - await next(context) + await call_next(context) execution_order.append("streaming_chat_after") # Create ChatAgent with chat middleware @@ -1721,13 +1749,13 @@ async def test_chat_middleware_termination_before_execution(self) -> None: execution_order: list[str] = [] class PreTerminationChatMiddleware(ChatMiddleware): - async def process(self, context: ChatContext, next: Callable[[ChatContext], Awaitable[None]]) -> None: + async def process(self, context: ChatContext, call_next: Callable[[ChatContext], Awaitable[None]]) -> None: execution_order.append("middleware_before") # Set a custom response since we're terminating context.result = ChatResponse(messages=[ChatMessage(role="assistant", text="Terminated by middleware")]) raise MiddlewareTermination # We call next() but since terminate=True, execution should stop - await next(context) + await call_next(context) execution_order.append("middleware_after") # Create ChatAgent with terminating middleware @@ -1749,9 +1777,9 @@ async def test_chat_middleware_termination_after_execution(self) -> None: execution_order: list[str] = [] class PostTerminationChatMiddleware(ChatMiddleware): - async def process(self, context: ChatContext, next: Callable[[ChatContext], Awaitable[None]]) -> None: + async def process(self, context: ChatContext, call_next: Callable[[ChatContext], Awaitable[None]]) -> None: execution_order.append("middleware_before") - await next(context) + await call_next(context) execution_order.append("middleware_after") context.terminate = True @@ -1776,21 +1804,21 @@ async def test_combined_middleware(self) -> None: """Test ChatAgent with combined middleware types.""" execution_order: list[str] = [] - async def agent_middleware(context: AgentContext, next: Callable[[AgentContext], Awaitable[None]]) -> None: + async def agent_middleware(context: AgentContext, call_next: Callable[[AgentContext], Awaitable[None]]) -> None: execution_order.append("agent_middleware_before") - await next(context) + await call_next(context) execution_order.append("agent_middleware_after") - async def chat_middleware(context: ChatContext, next: Callable[[ChatContext], Awaitable[None]]) -> None: + async def chat_middleware(context: ChatContext, call_next: Callable[[ChatContext], Awaitable[None]]) -> None: execution_order.append("chat_middleware_before") - await next(context) + await call_next(context) execution_order.append("chat_middleware_after") async def function_middleware( - context: FunctionInvocationContext, next: Callable[[FunctionInvocationContext], Awaitable[None]] + context: FunctionInvocationContext, call_next: Callable[[FunctionInvocationContext], Awaitable[None]] ) -> None: execution_order.append("function_middleware_before") - await next(context) + await call_next(context) execution_order.append("function_middleware_after") # Create ChatAgent with function middleware and tools @@ -1814,7 +1842,9 @@ async def test_agent_middleware_can_access_and_override_custom_kwargs(self) -> N modified_kwargs: dict[str, Any] = {} @agent_middleware - async def kwargs_middleware(context: AgentContext, next: Callable[[AgentContext], Awaitable[None]]) -> None: + async def kwargs_middleware( + context: AgentContext, call_next: Callable[[AgentContext], Awaitable[None]] + ) -> None: # Capture the original kwargs captured_kwargs.update(context.kwargs) @@ -1826,7 +1856,7 @@ async def kwargs_middleware(context: AgentContext, next: Callable[[AgentContext] # Store modified kwargs for verification modified_kwargs.update(context.kwargs) - await next(context) + await call_next(context) # Create ChatAgent with agent middleware chat_client = MockBaseChatClient() @@ -1865,10 +1895,10 @@ async def kwargs_middleware(context: AgentContext, next: Callable[[AgentContext] # class TrackingMiddleware(AgentMiddleware): # async def process( -# self, context: AgentContext, next: Callable[[AgentContext], Awaitable[None]] +# self, context: AgentContext, call_next: Callable[[AgentContext], Awaitable[None]] # ) -> None: # execution_order.append("before") -# await next(context) +# await call_next(context) # execution_order.append("after") # @use_agent_middleware diff --git a/python/packages/core/tests/core/test_middleware_with_chat.py b/python/packages/core/tests/core/test_middleware_with_chat.py index 1042ef9ae2..15621f759f 100644 --- a/python/packages/core/tests/core/test_middleware_with_chat.py +++ b/python/packages/core/tests/core/test_middleware_with_chat.py @@ -32,10 +32,10 @@ class LoggingChatMiddleware(ChatMiddleware): async def process( self, context: ChatContext, - next: Callable[[ChatContext], Awaitable[None]], + call_next: Callable[[ChatContext], Awaitable[None]], ) -> None: execution_order.append("chat_middleware_before") - await next(context) + await call_next(context) execution_order.append("chat_middleware_after") # Add middleware to chat client @@ -58,9 +58,11 @@ async def test_function_based_chat_middleware(self, chat_client_base: "MockBaseC execution_order: list[str] = [] @chat_middleware - async def logging_chat_middleware(context: ChatContext, next: Callable[[ChatContext], Awaitable[None]]) -> None: + async def logging_chat_middleware( + context: ChatContext, call_next: Callable[[ChatContext], Awaitable[None]] + ) -> None: execution_order.append("function_middleware_before") - await next(context) + await call_next(context) execution_order.append("function_middleware_after") # Add middleware to chat client @@ -83,13 +85,13 @@ async def test_chat_middleware_can_modify_messages(self, chat_client_base: "Mock @chat_middleware async def message_modifier_middleware( - context: ChatContext, next: Callable[[ChatContext], Awaitable[None]] + context: ChatContext, call_next: Callable[[ChatContext], Awaitable[None]] ) -> None: # Modify the first message by adding a prefix if context.messages and len(context.messages) > 0: original_text = context.messages[0].text or "" context.messages[0] = ChatMessage(role=context.messages[0].role, text=f"MODIFIED: {original_text}") - await next(context) + await call_next(context) # Add middleware to chat client chat_client_base.chat_middleware = [message_modifier_middleware] @@ -109,7 +111,7 @@ async def test_chat_middleware_can_override_response(self, chat_client_base: "Mo @chat_middleware async def response_override_middleware( - context: ChatContext, next: Callable[[ChatContext], Awaitable[None]] + context: ChatContext, call_next: Callable[[ChatContext], Awaitable[None]] ) -> None: # Override the response without calling next() context.result = ChatResponse( @@ -136,15 +138,15 @@ async def test_multiple_chat_middleware_execution_order(self, chat_client_base: execution_order: list[str] = [] @chat_middleware - async def first_middleware(context: ChatContext, next: Callable[[ChatContext], Awaitable[None]]) -> None: + async def first_middleware(context: ChatContext, call_next: Callable[[ChatContext], Awaitable[None]]) -> None: execution_order.append("first_before") - await next(context) + await call_next(context) execution_order.append("first_after") @chat_middleware - async def second_middleware(context: ChatContext, next: Callable[[ChatContext], Awaitable[None]]) -> None: + async def second_middleware(context: ChatContext, call_next: Callable[[ChatContext], Awaitable[None]]) -> None: execution_order.append("second_before") - await next(context) + await call_next(context) execution_order.append("second_after") # Add middleware to chat client (order should be preserved) @@ -172,10 +174,10 @@ async def test_chat_agent_with_chat_middleware(self) -> None: @chat_middleware async def agent_level_chat_middleware( - context: ChatContext, next: Callable[[ChatContext], Awaitable[None]] + context: ChatContext, call_next: Callable[[ChatContext], Awaitable[None]] ) -> None: execution_order.append("agent_chat_middleware_before") - await next(context) + await call_next(context) execution_order.append("agent_chat_middleware_after") chat_client = MockBaseChatClient() @@ -203,15 +205,15 @@ async def test_chat_agent_with_multiple_chat_middleware(self, chat_client_base: execution_order: list[str] = [] @chat_middleware - async def first_middleware(context: ChatContext, next: Callable[[ChatContext], Awaitable[None]]) -> None: + async def first_middleware(context: ChatContext, call_next: Callable[[ChatContext], Awaitable[None]]) -> None: execution_order.append("first_before") - await next(context) + await call_next(context) execution_order.append("first_after") @chat_middleware - async def second_middleware(context: ChatContext, next: Callable[[ChatContext], Awaitable[None]]) -> None: + async def second_middleware(context: ChatContext, call_next: Callable[[ChatContext], Awaitable[None]]) -> None: execution_order.append("second_before") - await next(context) + await call_next(context) execution_order.append("second_after") # Create ChatAgent with multiple chat middleware @@ -238,7 +240,9 @@ async def test_chat_middleware_with_streaming(self, chat_client_base: "MockBaseC execution_order: list[str] = [] @chat_middleware - async def streaming_middleware(context: ChatContext, next: Callable[[ChatContext], Awaitable[None]]) -> None: + async def streaming_middleware( + context: ChatContext, call_next: Callable[[ChatContext], Awaitable[None]] + ) -> None: execution_order.append("streaming_before") # Verify it's a streaming context assert context.stream is True @@ -250,7 +254,7 @@ def upper_case_update(update: ChatResponseUpdate) -> ChatResponseUpdate: return update context.stream_transform_hooks.append(upper_case_update) - await next(context) + await call_next(context) execution_order.append("streaming_after") # Add middleware to chat client @@ -274,9 +278,11 @@ async def test_run_level_middleware_isolation(self, chat_client_base: "MockBaseC execution_count = {"count": 0} @chat_middleware - async def counting_middleware(context: ChatContext, next: Callable[[ChatContext], Awaitable[None]]) -> None: + async def counting_middleware( + context: ChatContext, call_next: Callable[[ChatContext], Awaitable[None]] + ) -> None: execution_count["count"] += 1 - await next(context) + await call_next(context) # First call with run-level middleware messages = [ChatMessage(role="user", text="first message")] @@ -304,7 +310,7 @@ async def test_chat_client_middleware_can_access_and_override_custom_kwargs( modified_kwargs: dict[str, Any] = {} @chat_middleware - async def kwargs_middleware(context: ChatContext, next: Callable[[ChatContext], Awaitable[None]]) -> None: + async def kwargs_middleware(context: ChatContext, call_next: Callable[[ChatContext], Awaitable[None]]) -> None: # Capture the original kwargs captured_kwargs.update(context.kwargs) @@ -316,7 +322,7 @@ async def kwargs_middleware(context: ChatContext, next: Callable[[ChatContext], # Store modified kwargs for verification modified_kwargs.update(context.kwargs) - await next(context) + await call_next(context) # Add middleware to chat client chat_client_base.chat_middleware = [kwargs_middleware] @@ -349,11 +355,11 @@ async def test_function_middleware_registration_on_chat_client( @function_middleware async def test_function_middleware( - context: FunctionInvocationContext, next: Callable[[FunctionInvocationContext], Awaitable[None]] + context: FunctionInvocationContext, call_next: Callable[[FunctionInvocationContext], Awaitable[None]] ) -> None: nonlocal execution_order execution_order.append(f"function_middleware_before_{context.function.name}") - await next(context) + await call_next(context) execution_order.append(f"function_middleware_after_{context.function.name}") # Define a simple tool function @@ -415,10 +421,10 @@ async def test_run_level_function_middleware(self, chat_client_base: "MockBaseCh @function_middleware async def run_level_function_middleware( - context: FunctionInvocationContext, next: Callable[[FunctionInvocationContext], Awaitable[None]] + context: FunctionInvocationContext, call_next: Callable[[FunctionInvocationContext], Awaitable[None]] ) -> None: execution_order.append("run_level_function_middleware_before") - await next(context) + await call_next(context) execution_order.append("run_level_function_middleware_after") # Define a simple tool function diff --git a/python/packages/ollama/tests/test_ollama_chat_client.py b/python/packages/ollama/tests/test_ollama_chat_client.py index efe6d70890..9332972c66 100644 --- a/python/packages/ollama/tests/test_ollama_chat_client.py +++ b/python/packages/ollama/tests/test_ollama_chat_client.py @@ -163,8 +163,8 @@ def test_serialize(ollama_unit_test_env: dict[str, str]) -> None: def test_chat_middleware(ollama_unit_test_env: dict[str, str]) -> None: @chat_middleware - async def sample_middleware(context, next): - await next(context) + async def sample_middleware(context, call_next): + await call_next(context) ollama_chat_client = OllamaChatClient(middleware=[sample_middleware]) assert len(ollama_chat_client.middleware) == 1 diff --git a/python/packages/orchestrations/agent_framework_orchestrations/_handoff.py b/python/packages/orchestrations/agent_framework_orchestrations/_handoff.py index 3bbfccba8a..e69f5c6238 100644 --- a/python/packages/orchestrations/agent_framework_orchestrations/_handoff.py +++ b/python/packages/orchestrations/agent_framework_orchestrations/_handoff.py @@ -129,11 +129,11 @@ def __init__(self, handoffs: Sequence[HandoffConfiguration]) -> None: async def process( self, context: FunctionInvocationContext, - next: Callable[[FunctionInvocationContext], Awaitable[None]], + call_next: Callable[[FunctionInvocationContext], Awaitable[None]], ) -> None: """Intercept matching handoff tool calls and inject synthetic results.""" if context.function.name not in self._handoff_functions: - await next(context) + await call_next(context) return from agent_framework._middleware import MiddlewareTermination diff --git a/python/packages/purview/agent_framework_purview/_middleware.py b/python/packages/purview/agent_framework_purview/_middleware.py index dba7a3f649..42f8b37df6 100644 --- a/python/packages/purview/agent_framework_purview/_middleware.py +++ b/python/packages/purview/agent_framework_purview/_middleware.py @@ -48,7 +48,7 @@ def __init__( async def process( self, context: AgentContext, - next: Callable[[AgentContext], Awaitable[None]], + call_next: Callable[[AgentContext], Awaitable[None]], ) -> None: # type: ignore[override] resolved_user_id: str | None = None try: @@ -74,7 +74,7 @@ async def process( if not self._settings.ignore_exceptions: raise - await next(context) + await call_next(context) try: # Post (response) check only if we have a normal AgentResponse @@ -140,7 +140,7 @@ def __init__( async def process( self, context: ChatContext, - next: Callable[[ChatContext], Awaitable[None]], + call_next: Callable[[ChatContext], Awaitable[None]], ) -> None: # type: ignore[override] resolved_user_id: str | None = None try: @@ -164,7 +164,7 @@ async def process( if not self._settings.ignore_exceptions: raise - await next(context) + await call_next(context) try: # Post (response) evaluation only if non-streaming and we have messages result shape diff --git a/python/samples/concepts/tools/README.md b/python/samples/concepts/tools/README.md index 0652494635..6643a42126 100644 --- a/python/samples/concepts/tools/README.md +++ b/python/samples/concepts/tools/README.md @@ -37,7 +37,7 @@ sequenceDiagram AML->>AMP: execute(AgentContext) loop Agent Middleware Chain - AMP->>AMP: middleware[i].process(context, next) + AMP->>AMP: middleware[i].process(context, call_next) Note right of AMP: Can modify: messages, options, thread end @@ -60,7 +60,7 @@ sequenceDiagram CML->>CMP: execute(ChatContext) loop Chat Middleware Chain - CMP->>CMP: middleware[i].process(context, next) + CMP->>CMP: middleware[i].process(context, call_next) Note right of CMP: Can modify: messages, options end @@ -81,7 +81,7 @@ sequenceDiagram loop For each function_call FIL->>FMP: execute(FunctionInvocationContext) loop Function Middleware Chain - FMP->>FMP: middleware[i].process(context, next) + FMP->>FMP: middleware[i].process(context, call_next) Note right of FMP: Can modify: arguments end FMP->>Tool: invoke(arguments) @@ -137,7 +137,7 @@ sequenceDiagram | `options` | `Mapping[str, Any]` | Chat options dict | | `stream` | `bool` | Whether streaming is enabled | | `metadata` | `dict` | Shared data between middleware | -| `result` | `AgentResponse \| None` | Set after `next()` is called | +| `result` | `AgentResponse \| None` | Set after `call_next()` is called | | `kwargs` | `Mapping[str, Any]` | Additional run arguments | **Key Operations:** @@ -150,7 +150,7 @@ sequenceDiagram - `context.messages` - Add, remove, or modify input messages - `context.options` - Change model parameters, temperature, etc. - `context.thread` - Replace or modify the thread -- `context.result` - Override the final response (after `next()`) +- `context.result` - Override the final response (after `call_next()`) ### 2. Chat Middleware Layer (`ChatMiddlewareLayer`) @@ -165,7 +165,7 @@ sequenceDiagram | `options` | `Mapping[str, Any]` | Chat options | | `stream` | `bool` | Whether streaming | | `metadata` | `dict` | Shared data between middleware | -| `result` | `ChatResponse \| None` | Set after `next()` is called | +| `result` | `ChatResponse \| None` | Set after `call_next()` is called | | `kwargs` | `Mapping[str, Any]` | Additional arguments | **Key Operations:** @@ -176,7 +176,7 @@ sequenceDiagram **What Can Be Modified:** - `context.messages` - Inject system prompts, filter content - `context.options` - Change model, temperature, tool_choice -- `context.result` - Override the response (after `next()`) +- `context.result` - Override the response (after `call_next()`) ### 3. Function Invocation Layer (`FunctionInvocationLayer`) @@ -251,23 +251,23 @@ response = await client.get_response( | `function` | `FunctionTool` | The function being invoked | | `arguments` | `BaseModel` | Validated Pydantic arguments | | `metadata` | `dict` | Shared data between middleware | -| `result` | `Any` | Set after `next()` is called | +| `result` | `Any` | Set after `call_next()` is called | | `kwargs` | `Mapping[str, Any]` | Runtime kwargs | **What Can Be Modified:** - `context.arguments` - Modify validated arguments before execution -- `context.result` - Override the function result (after `next()`) +- `context.result` - Override the function result (after `call_next()`) - Raise `MiddlewareTermination` to skip execution and terminate the function invocation loop **Special Behavior:** When `MiddlewareTermination` is raised in function middleware, it signals that the function invocation loop should exit **without making another LLM call**. This is useful when middleware determines that no further processing is needed (e.g., a termination condition is met). ```python class TerminatingMiddleware(FunctionMiddleware): - async def process(self, context: FunctionInvocationContext, next): + async def process(self, context: FunctionInvocationContext, call_next): if self.should_terminate(context): context.result = "terminated by middleware" raise MiddlewareTermination # Exit function invocation loop - await next(context) + await call_next(context) ``` ## Arguments Added/Altered at Each Layer @@ -334,20 +334,20 @@ class TerminatingMiddleware(FunctionMiddleware): There are three ways to exit a middleware's `process()` method: -### 1. Return Normally (with or without calling `next`) +### 1. Return Normally (with or without calling `call_next`) Returns control to the upstream middleware, allowing its post-processing code to run. ```python class CachingMiddleware(FunctionMiddleware): - async def process(self, context: FunctionInvocationContext, next): - # Option A: Return early WITHOUT calling next (skip downstream) + async def process(self, context: FunctionInvocationContext, call_next): + # Option A: Return early WITHOUT calling call_next (skip downstream) if cached := self.cache.get(context.function.name): context.result = cached return # Upstream post-processing still runs - # Option B: Call next, then return normally - await next(context) + # Option B: Call call_next, then return normally + await call_next(context) self.cache[context.function.name] = context.result return # Normal completion ``` @@ -358,11 +358,11 @@ Immediately exits the entire middleware chain. Upstream middleware's post-proces ```python class BlockedFunctionMiddleware(FunctionMiddleware): - async def process(self, context: FunctionInvocationContext, next): + async def process(self, context: FunctionInvocationContext, call_next): if context.function.name in self.blocked_functions: context.result = "Function blocked by policy" raise MiddlewareTermination("Blocked") # Skips ALL post-processing - await next(context) + await call_next(context) ``` ### 3. Raise Any Other Exception @@ -371,10 +371,10 @@ Bubbles up to the caller. The middleware chain is aborted and the exception prop ```python class ValidationMiddleware(FunctionMiddleware): - async def process(self, context: FunctionInvocationContext, next): + async def process(self, context: FunctionInvocationContext, call_next): if not self.is_valid(context.arguments): raise ValueError("Invalid arguments") # Bubbles up to user - await next(context) + await call_next(context) ``` ## `return` vs `raise MiddlewareTermination` @@ -383,13 +383,13 @@ The key difference is what happens to **upstream middleware's post-processing**: ```python class MiddlewareA(AgentMiddleware): - async def process(self, context, next): + async def process(self, context, call_next): print("A: before") - await next(context) + await call_next(context) print("A: after") # Does this run? class MiddlewareB(AgentMiddleware): - async def process(self, context, next): + async def process(self, context, call_next): print("B: before") context.result = "early result" # Choose one: @@ -408,14 +408,14 @@ With middleware registered as `[MiddlewareA, MiddlewareB]`: **Use `raise MiddlewareTermination`** when you want to completely bypass all remaining processing (e.g., blocking a request, returning cached response without any modification). -## Calling `next()` or Not +## Calling `call_next()` or Not -The decision to call `next(context)` determines whether downstream middleware and the actual operation execute: +The decision to call `call_next(context)` determines whether downstream middleware and the actual operation execute: -### Without calling `next()` - Skip downstream +### Without calling `call_next()` - Skip downstream ```python -async def process(self, context, next): +async def process(self, context, call_next): context.result = "replacement result" return # Downstream middleware and actual execution are SKIPPED ``` @@ -425,12 +425,12 @@ async def process(self, context, next): - Upstream middleware post-processing: ✅ Still runs (unless `MiddlewareTermination` raised) - Result: Whatever you set in `context.result` -### With calling `next()` - Full execution +### With calling `call_next()` - Full execution ```python -async def process(self, context, next): +async def process(self, context, call_next): # Pre-processing - await next(context) # Execute downstream + actual operation + await call_next(context) # Execute downstream + actual operation # Post-processing (context.result now contains real result) return ``` @@ -442,7 +442,7 @@ async def process(self, context, next): ### Summary Table -| Exit Method | Call `next()`? | Downstream Executes? | Actual Op Executes? | Upstream Post-Processing? | +| Exit Method | Call `call_next()`? | Downstream Executes? | Actual Op Executes? | Upstream Post-Processing? | |-------------|----------------|---------------------|---------------------|--------------------------| | `return` (or implicit) | Yes | ✅ | ✅ | ✅ Yes | | `return` | No | ❌ | ❌ | ✅ Yes | @@ -450,7 +450,7 @@ async def process(self, context, next): | `raise MiddlewareTermination` | Yes | ✅ | ✅ | ❌ No | | `raise OtherException` | Either | Depends | Depends | ❌ No (exception propagates) | -> **Note:** The first row (`return` after calling `next()`) is the default behavior. Python functions implicitly return `None` at the end, so simply calling `await next(context)` without an explicit `return` statement achieves this pattern. +> **Note:** The first row (`return` after calling `call_next()`) is the default behavior. Python functions implicitly return `None` at the end, so simply calling `await call_next(context)` without an explicit `return` statement achieves this pattern. ## Streaming vs Non-Streaming diff --git a/python/samples/getting_started/agents/azure_ai/azure_ai_with_agent_as_tool.py b/python/samples/getting_started/agents/azure_ai/azure_ai_with_agent_as_tool.py index b336e02d9d..f03fc4beb1 100644 --- a/python/samples/getting_started/agents/azure_ai/azure_ai_with_agent_as_tool.py +++ b/python/samples/getting_started/agents/azure_ai/azure_ai_with_agent_as_tool.py @@ -20,13 +20,13 @@ async def logging_middleware( context: FunctionInvocationContext, - next: Callable[[FunctionInvocationContext], Awaitable[None]], + call_next: Callable[[FunctionInvocationContext], Awaitable[None]], ) -> None: """MiddlewareTypes that logs tool invocations to show the delegation flow.""" print(f"[Calling tool: {context.function.name}]") print(f"[Request: {context.arguments}]") - await next(context) + await call_next(context) print(f"[Response: {context.result}]") diff --git a/python/samples/getting_started/agents/openai/openai_responses_client_basic.py b/python/samples/getting_started/agents/openai/openai_responses_client_basic.py index 06ecb55473..35f0c67b0e 100644 --- a/python/samples/getting_started/agents/openai/openai_responses_client_basic.py +++ b/python/samples/getting_started/agents/openai/openai_responses_client_basic.py @@ -20,7 +20,7 @@ @chat_middleware async def security_and_override_middleware( context: ChatContext, - next: Callable[[ChatContext], Awaitable[None]], + call_next: Callable[[ChatContext], Awaitable[None]], ) -> None: """Function-based middleware that implements security filtering and response override.""" print("[SecurityMiddleware] Processing input...") @@ -52,7 +52,7 @@ async def security_and_override_middleware( return # Continue to next middleware or AI execution - await next(context) + await call_next(context) print("[SecurityMiddleware] Response generated.") print(type(context.result)) diff --git a/python/samples/getting_started/agents/openai/openai_responses_client_with_agent_as_tool.py b/python/samples/getting_started/agents/openai/openai_responses_client_with_agent_as_tool.py index d90202a9af..d37d5a9b4a 100644 --- a/python/samples/getting_started/agents/openai/openai_responses_client_with_agent_as_tool.py +++ b/python/samples/getting_started/agents/openai/openai_responses_client_with_agent_as_tool.py @@ -19,13 +19,13 @@ async def logging_middleware( context: FunctionInvocationContext, - next: Callable[[FunctionInvocationContext], Awaitable[None]], + call_next: Callable[[FunctionInvocationContext], Awaitable[None]], ) -> None: """MiddlewareTypes that logs tool invocations to show the delegation flow.""" print(f"[Calling tool: {context.function.name}]") print(f"[Request: {context.arguments}]") - await next(context) + await call_next(context) print(f"[Response: {context.result}]") diff --git a/python/samples/getting_started/devui/weather_agent_azure/agent.py b/python/samples/getting_started/devui/weather_agent_azure/agent.py index b4dd667bed..0405ca8192 100644 --- a/python/samples/getting_started/devui/weather_agent_azure/agent.py +++ b/python/samples/getting_started/devui/weather_agent_azure/agent.py @@ -37,7 +37,7 @@ def cleanup_resources(): @chat_middleware async def security_filter_middleware( context: ChatContext, - next: Callable[[ChatContext], Awaitable[None]], + call_next: Callable[[ChatContext], Awaitable[None]], ) -> None: """Chat middleware that blocks requests containing sensitive information.""" blocked_terms = ["password", "secret", "api_key", "token"] @@ -77,13 +77,13 @@ async def blocked_stream() -> AsyncIterable[ChatResponseUpdate]: context.terminate = True return - await next(context) + await call_next(context) @function_middleware async def atlantis_location_filter_middleware( context: FunctionInvocationContext, - next: Callable[[FunctionInvocationContext], Awaitable[None]], + call_next: Callable[[FunctionInvocationContext], Awaitable[None]], ) -> None: """Function middleware that blocks weather requests for Atlantis.""" # Check if location parameter is "atlantis" @@ -96,7 +96,7 @@ async def atlantis_location_filter_middleware( context.terminate = True return - await next(context) + await call_next(context) # NOTE: approval_mode="never_require" is for sample brevity. Use "always_require" in production; see samples/getting_started/tools/function_tool_with_approval.py and samples/getting_started/tools/function_tool_with_approval_and_threads.py. diff --git a/python/samples/getting_started/middleware/agent_and_run_level_middleware.py b/python/samples/getting_started/middleware/agent_and_run_level_middleware.py index c90dd1936b..b76e0ac520 100644 --- a/python/samples/getting_started/middleware/agent_and_run_level_middleware.py +++ b/python/samples/getting_started/middleware/agent_and_run_level_middleware.py @@ -49,7 +49,7 @@ def get_weather( class SecurityAgentMiddleware(AgentMiddleware): """Agent-level security middleware that validates all requests.""" - async def process(self, context: AgentContext, next: Callable[[AgentContext], Awaitable[None]]) -> None: + async def process(self, context: AgentContext, call_next: Callable[[AgentContext], Awaitable[None]]) -> None: print("[SecurityMiddleware] Checking security for all requests...") # Check for security violations in the last user message @@ -58,22 +58,22 @@ async def process(self, context: AgentContext, next: Callable[[AgentContext], Aw query = last_message.text.lower() if any(word in query for word in ["password", "secret", "credentials"]): print("[SecurityMiddleware] Security violation detected! Blocking request.") - return # Don't call next() to prevent execution + return # Don't call call_next() to prevent execution print("[SecurityMiddleware] Security check passed.") context.metadata["security_validated"] = True - await next(context) + await call_next(context) async def performance_monitor_middleware( context: AgentContext, - next: Callable[[AgentContext], Awaitable[None]], + call_next: Callable[[AgentContext], Awaitable[None]], ) -> None: """Agent-level performance monitoring for all runs.""" print("[PerformanceMonitor] Starting performance monitoring...") start_time = time.time() - await next(context) + await call_next(context) end_time = time.time() duration = end_time - start_time @@ -85,7 +85,7 @@ async def performance_monitor_middleware( class HighPriorityMiddleware(AgentMiddleware): """Run-level middleware for high priority requests.""" - async def process(self, context: AgentContext, next: Callable[[AgentContext], Awaitable[None]]) -> None: + async def process(self, context: AgentContext, call_next: Callable[[AgentContext], Awaitable[None]]) -> None: print("[HighPriority] Processing high priority request with expedited handling...") # Read metadata set by agent-level middleware @@ -96,13 +96,13 @@ async def process(self, context: AgentContext, next: Callable[[AgentContext], Aw context.metadata["priority"] = "high" context.metadata["expedited"] = True - await next(context) + await call_next(context) print("[HighPriority] High priority processing completed") async def debugging_middleware( context: AgentContext, - next: Callable[[AgentContext], Awaitable[None]], + call_next: Callable[[AgentContext], Awaitable[None]], ) -> None: """Run-level debugging middleware for troubleshooting specific runs.""" print("[Debug] Debug mode enabled for this run") @@ -115,7 +115,7 @@ async def debugging_middleware( context.metadata["debug_enabled"] = True - await next(context) + await call_next(context) print("[Debug] Debug information collected") @@ -126,7 +126,7 @@ class CachingMiddleware(AgentMiddleware): def __init__(self) -> None: self.cache: dict[str, AgentResponse] = {} - async def process(self, context: AgentContext, next: Callable[[AgentContext], Awaitable[None]]) -> None: + async def process(self, context: AgentContext, call_next: Callable[[AgentContext], Awaitable[None]]) -> None: # Create a simple cache key from the last message last_message = context.messages[-1] if context.messages else None cache_key: str = last_message.text if last_message and last_message.text else "no_message" @@ -134,12 +134,12 @@ async def process(self, context: AgentContext, next: Callable[[AgentContext], Aw if cache_key in self.cache: print(f"[Cache] Cache HIT for: '{cache_key[:30]}...'") context.result = self.cache[cache_key] # type: ignore - return # Don't call next(), return cached result + return # Don't call call_next(), return cached result print(f"[Cache] Cache MISS for: '{cache_key[:30]}...'") context.metadata["cache_key"] = cache_key - await next(context) + await call_next(context) # Cache the result if we have one if context.result: @@ -149,14 +149,14 @@ async def process(self, context: AgentContext, next: Callable[[AgentContext], Aw async def function_logging_middleware( context: FunctionInvocationContext, - next: Callable[[FunctionInvocationContext], Awaitable[None]], + call_next: Callable[[FunctionInvocationContext], Awaitable[None]], ) -> None: """Function middleware that logs all function calls.""" function_name = context.function.name args = context.arguments print(f"[FunctionLog] Calling function: {function_name} with args: {args}") - await next(context) + await call_next(context) print(f"[FunctionLog] Function {function_name} completed") diff --git a/python/samples/getting_started/middleware/chat_middleware.py b/python/samples/getting_started/middleware/chat_middleware.py index e7e807f27e..768a9a5f8c 100644 --- a/python/samples/getting_started/middleware/chat_middleware.py +++ b/python/samples/getting_started/middleware/chat_middleware.py @@ -56,7 +56,7 @@ def __init__(self, replacement: str | None = None): async def process( self, context: ChatContext, - next: Callable[[ChatContext], Awaitable[None]], + call_next: Callable[[ChatContext], Awaitable[None]], ) -> None: """Observe and modify input messages before they are sent to AI.""" print("[InputObserverMiddleware] Observing input messages:") @@ -90,7 +90,7 @@ async def process( context.messages[:] = modified_messages # Continue to next middleware or AI execution - await next(context) + await call_next(context) # Observe that processing is complete print("[InputObserverMiddleware] Processing completed") @@ -99,7 +99,7 @@ async def process( @chat_middleware async def security_and_override_middleware( context: ChatContext, - next: Callable[[ChatContext], Awaitable[None]], + call_next: Callable[[ChatContext], Awaitable[None]], ) -> None: """Function-based middleware that implements security filtering and response override.""" print("[SecurityMiddleware] Processing input...") @@ -131,7 +131,7 @@ async def security_and_override_middleware( return # Continue to next middleware or AI execution - await next(context) + await call_next(context) async def class_based_chat_middleware() -> None: diff --git a/python/samples/getting_started/middleware/class_based_middleware.py b/python/samples/getting_started/middleware/class_based_middleware.py index 727c0a2821..ab6bfd5ab4 100644 --- a/python/samples/getting_started/middleware/class_based_middleware.py +++ b/python/samples/getting_started/middleware/class_based_middleware.py @@ -50,7 +50,7 @@ class SecurityAgentMiddleware(AgentMiddleware): async def process( self, context: AgentContext, - next: Callable[[AgentContext], Awaitable[None]], + call_next: Callable[[AgentContext], Awaitable[None]], ) -> None: # Check for potential security violations in the query # Look at the last user message @@ -63,11 +63,11 @@ async def process( context.result = AgentResponse( messages=[ChatMessage("assistant", ["Detected sensitive information, the request is blocked."])] ) - # Simply don't call next() to prevent execution + # Simply don't call call_next() to prevent execution return print("[SecurityAgentMiddleware] Security check passed.") - await next(context) + await call_next(context) class LoggingFunctionMiddleware(FunctionMiddleware): @@ -76,14 +76,14 @@ class LoggingFunctionMiddleware(FunctionMiddleware): async def process( self, context: FunctionInvocationContext, - next: Callable[[FunctionInvocationContext], Awaitable[None]], + call_next: Callable[[FunctionInvocationContext], Awaitable[None]], ) -> None: function_name = context.function.name print(f"[LoggingFunctionMiddleware] About to call function: {function_name}.") start_time = time.time() - await next(context) + await call_next(context) end_time = time.time() duration = end_time - start_time diff --git a/python/samples/getting_started/middleware/decorator_middleware.py b/python/samples/getting_started/middleware/decorator_middleware.py index 2ea1196bc3..3f5e57e48e 100644 --- a/python/samples/getting_started/middleware/decorator_middleware.py +++ b/python/samples/getting_started/middleware/decorator_middleware.py @@ -50,18 +50,18 @@ def get_current_time() -> str: @agent_middleware # Decorator marks this as agent middleware - no type annotations needed -async def simple_agent_middleware(context, next): # type: ignore - parameters intentionally untyped to demonstrate decorator functionality +async def simple_agent_middleware(context, call_next): # type: ignore - parameters intentionally untyped to demonstrate decorator functionality """Agent middleware that runs before and after agent execution.""" print("[Agent MiddlewareTypes] Before agent execution") - await next(context) + await call_next(context) print("[Agent MiddlewareTypes] After agent execution") @function_middleware # Decorator marks this as function middleware - no type annotations needed -async def simple_function_middleware(context, next): # type: ignore - parameters intentionally untyped to demonstrate decorator functionality +async def simple_function_middleware(context, call_next): # type: ignore - parameters intentionally untyped to demonstrate decorator functionality """Function middleware that runs before and after function calls.""" print(f"[Function MiddlewareTypes] Before calling: {context.function.name}") # type: ignore - await next(context) + await call_next(context) print(f"[Function MiddlewareTypes] After calling: {context.function.name}") # type: ignore diff --git a/python/samples/getting_started/middleware/exception_handling_with_middleware.py b/python/samples/getting_started/middleware/exception_handling_with_middleware.py index bc752e3615..b929af4c94 100644 --- a/python/samples/getting_started/middleware/exception_handling_with_middleware.py +++ b/python/samples/getting_started/middleware/exception_handling_with_middleware.py @@ -35,13 +35,13 @@ def unstable_data_service( async def exception_handling_middleware( - context: FunctionInvocationContext, next: Callable[[FunctionInvocationContext], Awaitable[None]] + context: FunctionInvocationContext, call_next: Callable[[FunctionInvocationContext], Awaitable[None]] ) -> None: function_name = context.function.name try: print(f"[ExceptionHandlingMiddleware] Executing function: {function_name}") - await next(context) + await call_next(context) print(f"[ExceptionHandlingMiddleware] Function {function_name} completed successfully.") except TimeoutError as e: print(f"[ExceptionHandlingMiddleware] Caught TimeoutError: {e}") diff --git a/python/samples/getting_started/middleware/function_based_middleware.py b/python/samples/getting_started/middleware/function_based_middleware.py index 1616aa5fc3..d9b9062003 100644 --- a/python/samples/getting_started/middleware/function_based_middleware.py +++ b/python/samples/getting_started/middleware/function_based_middleware.py @@ -27,7 +27,7 @@ Function-based middleware is ideal for simple, stateless operations and provides a more lightweight approach compared to class-based middleware. Both agent and function middleware -can be implemented as async functions that accept context and next parameters. +can be implemented as async functions that accept context and call_next parameters. """ @@ -43,7 +43,7 @@ def get_weather( async def security_agent_middleware( context: AgentContext, - next: Callable[[AgentContext], Awaitable[None]], + call_next: Callable[[AgentContext], Awaitable[None]], ) -> None: """Agent middleware that checks for security violations.""" # Check for potential security violations in the query @@ -53,16 +53,16 @@ async def security_agent_middleware( query = last_message.text if "password" in query.lower() or "secret" in query.lower(): print("[SecurityAgentMiddleware] Security Warning: Detected sensitive information, blocking request.") - # Simply don't call next() to prevent execution + # Simply don't call call_next() to prevent execution return print("[SecurityAgentMiddleware] Security check passed.") - await next(context) + await call_next(context) async def logging_function_middleware( context: FunctionInvocationContext, - next: Callable[[FunctionInvocationContext], Awaitable[None]], + call_next: Callable[[FunctionInvocationContext], Awaitable[None]], ) -> None: """Function middleware that logs function calls.""" function_name = context.function.name @@ -70,7 +70,7 @@ async def logging_function_middleware( start_time = time.time() - await next(context) + await call_next(context) end_time = time.time() duration = end_time - start_time diff --git a/python/samples/getting_started/middleware/middleware_termination.py b/python/samples/getting_started/middleware/middleware_termination.py index 69fa5766d9..043a15ec2a 100644 --- a/python/samples/getting_started/middleware/middleware_termination.py +++ b/python/samples/getting_started/middleware/middleware_termination.py @@ -22,7 +22,7 @@ This sample demonstrates how middleware can terminate execution using the `context.terminate` flag. The example includes: -- PreTerminationMiddleware: Terminates execution before calling next() to prevent agent processing +- PreTerminationMiddleware: Terminates execution before calling call_next() to prevent agent processing - PostTerminationMiddleware: Allows processing to complete but terminates further execution This is useful for implementing security checks, rate limiting, or early exit conditions. @@ -48,7 +48,7 @@ def __init__(self, blocked_words: list[str]): async def process( self, context: AgentContext, - next: Callable[[AgentContext], Awaitable[None]], + call_next: Callable[[AgentContext], Awaitable[None]], ) -> None: # Check if the user message contains any blocked words last_message = context.messages[-1] if context.messages else None @@ -75,7 +75,7 @@ async def process( context.terminate = True break - await next(context) + await call_next(context) class PostTerminationMiddleware(AgentMiddleware): @@ -88,7 +88,7 @@ def __init__(self, max_responses: int = 1): async def process( self, context: AgentContext, - next: Callable[[AgentContext], Awaitable[None]], + call_next: Callable[[AgentContext], Awaitable[None]], ) -> None: print(f"[PostTerminationMiddleware] Processing request (response count: {self.response_count})") @@ -101,7 +101,7 @@ async def process( context.terminate = True # Allow the agent to process normally - await next(context) + await call_next(context) # Increment response count after processing self.response_count += 1 diff --git a/python/samples/getting_started/middleware/override_result_with_middleware.py b/python/samples/getting_started/middleware/override_result_with_middleware.py index 8aef8f8e3b..414fb1da9d 100644 --- a/python/samples/getting_started/middleware/override_result_with_middleware.py +++ b/python/samples/getting_started/middleware/override_result_with_middleware.py @@ -49,11 +49,11 @@ def get_weather( return f"The weather in {location} is {conditions[randint(0, 3)]} with a high of {randint(10, 30)}°C." -async def weather_override_middleware(context: ChatContext, next: Callable[[ChatContext], Awaitable[None]]) -> None: +async def weather_override_middleware(context: ChatContext, call_next: Callable[[ChatContext], Awaitable[None]]) -> None: """Chat middleware that overrides weather results for both streaming and non-streaming cases.""" # Let the original agent execution complete first - await next(context) + await call_next(context) # Check if there's a result to override (agent called weather function) if context.result is not None: @@ -84,9 +84,9 @@ def _update_hook(update: ChatResponseUpdate) -> ChatResponseUpdate: context.result = ChatResponse(messages=[ChatMessage(role=Role.ASSISTANT, text=custom_message)]) -async def validate_weather_middleware(context: ChatContext, next: Callable[[ChatContext], Awaitable[None]]) -> None: +async def validate_weather_middleware(context: ChatContext, call_next: Callable[[ChatContext], Awaitable[None]]) -> None: """Chat middleware that simulates result validation for both streaming and non-streaming cases.""" - await next(context) + await call_next(context) validation_note = "Validation: weather data verified." @@ -104,9 +104,9 @@ def _append_validation_note(response: ChatResponse) -> ChatResponse: context.result.messages.append(ChatMessage(role=Role.ASSISTANT, text=validation_note)) -async def agent_cleanup_middleware(context: AgentContext, next: Callable[[AgentContext], Awaitable[None]]) -> None: +async def agent_cleanup_middleware(context: AgentContext, call_next: Callable[[AgentContext], Awaitable[None]]) -> None: """Agent middleware that validates chat middleware effects and cleans the result.""" - await next(context) + await call_next(context) if context.result is None: return diff --git a/python/samples/getting_started/middleware/runtime_context_delegation.py b/python/samples/getting_started/middleware/runtime_context_delegation.py index d4669239a6..700b1da6f5 100644 --- a/python/samples/getting_started/middleware/runtime_context_delegation.py +++ b/python/samples/getting_started/middleware/runtime_context_delegation.py @@ -54,7 +54,7 @@ def __init__(self) -> None: async def inject_context_middleware( self, context: FunctionInvocationContext, - next: Callable[[FunctionInvocationContext], Awaitable[None]], + call_next: Callable[[FunctionInvocationContext], Awaitable[None]], ) -> None: """MiddlewareTypes that extracts runtime context from kwargs and stores in container. @@ -74,7 +74,7 @@ async def inject_context_middleware( print(f" - Session Metadata Keys: {list(self.session_metadata.keys())}") # Continue to tool execution - await next(context) + await call_next(context) # Create a container instance that will be shared via closure @@ -278,19 +278,19 @@ async def pattern_2_hierarchical_with_kwargs_propagation() -> None: @function_middleware async def email_kwargs_tracker( - context: FunctionInvocationContext, next: Callable[[FunctionInvocationContext], Awaitable[None]] + context: FunctionInvocationContext, call_next: Callable[[FunctionInvocationContext], Awaitable[None]] ) -> None: email_agent_kwargs.update(context.kwargs) print(f"[EmailAgent] Received runtime context: {list(context.kwargs.keys())}") - await next(context) + await call_next(context) @function_middleware async def sms_kwargs_tracker( - context: FunctionInvocationContext, next: Callable[[FunctionInvocationContext], Awaitable[None]] + context: FunctionInvocationContext, call_next: Callable[[FunctionInvocationContext], Awaitable[None]] ) -> None: sms_agent_kwargs.update(context.kwargs) print(f"[SMSAgent] Received runtime context: {list(context.kwargs.keys())}") - await next(context) + await call_next(context) client = OpenAIChatClient(model_id="gpt-4o-mini") @@ -359,7 +359,7 @@ def __init__(self) -> None: self.validated_tokens: list[str] = [] async def validate_and_track( - self, context: FunctionInvocationContext, next: Callable[[FunctionInvocationContext], Awaitable[None]] + self, context: FunctionInvocationContext, call_next: Callable[[FunctionInvocationContext], Awaitable[None]] ) -> None: """Validate API token and track usage.""" api_token = context.kwargs.get("api_token") @@ -375,7 +375,7 @@ async def validate_and_track( else: print("[AuthMiddleware] No API token provided") - await next(context) + await call_next(context) @tool(approval_mode="never_require") diff --git a/python/samples/getting_started/middleware/shared_state_middleware.py b/python/samples/getting_started/middleware/shared_state_middleware.py index f48ec3807d..a377d7dfd3 100644 --- a/python/samples/getting_started/middleware/shared_state_middleware.py +++ b/python/samples/getting_started/middleware/shared_state_middleware.py @@ -57,7 +57,7 @@ def __init__(self) -> None: async def call_counter_middleware( self, context: FunctionInvocationContext, - next: Callable[[FunctionInvocationContext], Awaitable[None]], + call_next: Callable[[FunctionInvocationContext], Awaitable[None]], ) -> None: """First middleware: increments call count in shared state.""" # Increment the shared call count @@ -66,18 +66,18 @@ async def call_counter_middleware( print(f"[CallCounter] This is function call #{self.call_count}") # Call the next middleware/function - await next(context) + await call_next(context) async def result_enhancer_middleware( self, context: FunctionInvocationContext, - next: Callable[[FunctionInvocationContext], Awaitable[None]], + call_next: Callable[[FunctionInvocationContext], Awaitable[None]], ) -> None: """Second middleware: uses shared call count to enhance function results.""" print(f"[ResultEnhancer] Current total calls so far: {self.call_count}") # Call the next middleware/function - await next(context) + await call_next(context) # After function execution, enhance the result using shared state if context.result: diff --git a/python/samples/getting_started/middleware/thread_behavior_middleware.py b/python/samples/getting_started/middleware/thread_behavior_middleware.py index 0665d23720..e3306eef7b 100644 --- a/python/samples/getting_started/middleware/thread_behavior_middleware.py +++ b/python/samples/getting_started/middleware/thread_behavior_middleware.py @@ -21,14 +21,14 @@ - How AgentContext.thread property behaves across multiple runs - How middleware can access conversation history through the thread -- The timing of when thread messages are populated (before vs after next() call) +- The timing of when thread messages are populated (before vs after call_next() call) - How to track thread state changes across runs Key behaviors demonstrated: -1. First run: context.messages is populated, context.thread is initially empty (before next()) -2. After next(): thread contains input message + response from agent +1. First run: context.messages is populated, context.thread is initially empty (before call_next()) +2. After call_next(): thread contains input message + response from agent 3. Second run: context.messages contains only current input, thread contains previous history -4. After next(): thread contains full conversation history (all previous + current messages) +4. After call_next(): thread contains full conversation history (all previous + current messages) """ @@ -46,7 +46,7 @@ def get_weather( async def thread_tracking_middleware( context: AgentContext, - next: Callable[[AgentContext], Awaitable[None]], + call_next: Callable[[AgentContext], Awaitable[None]], ) -> None: """MiddlewareTypes that tracks and logs thread behavior across runs.""" thread_messages = [] @@ -56,8 +56,8 @@ async def thread_tracking_middleware( print(f"[MiddlewareTypes pre-execution] Current input messages: {len(context.messages)}") print(f"[MiddlewareTypes pre-execution] Thread history messages: {len(thread_messages)}") - # Call next to execute the agent - await next(context) + # Call call_next to execute the agent + await call_next(context) # Check thread state after agent execution updated_thread_messages = [] From 6c5218dd7575ed463efb12ee51888cd73196d50f Mon Sep 17 00:00:00 2001 From: Dmytro Struk <13853051+dmytrostruk@users.noreply.github.com> Date: Fri, 6 Feb 2026 12:19:24 -0800 Subject: [PATCH 2/2] Resolved comments --- python/packages/core/AGENTS.md | 7 +++---- python/packages/core/agent_framework/_middleware.py | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/python/packages/core/AGENTS.md b/python/packages/core/AGENTS.md index 4140050856..2a308c245d 100644 --- a/python/packages/core/AGENTS.md +++ b/python/packages/core/AGENTS.md @@ -117,11 +117,10 @@ agent = OpenAIChatClient().as_agent( from agent_framework import ChatAgent, AgentMiddleware, AgentContext class LoggingMiddleware(AgentMiddleware): - async def process(self, context: AgentContext, call_next) -> AgentResponse: + async def process(self, context: AgentContext, call_next) -> None: print(f"Input: {context.messages}") - response = await call_next(context) - print(f"Output: {response}") - return response + await call_next(context) + print(f"Output: {context.result}") agent = ChatAgent(..., middleware=[LoggingMiddleware()]) ``` diff --git a/python/packages/core/agent_framework/_middleware.py b/python/packages/core/agent_framework/_middleware.py index 9e83e925f7..48f762cdaa 100644 --- a/python/packages/core/agent_framework/_middleware.py +++ b/python/packages/core/agent_framework/_middleware.py @@ -1240,7 +1240,7 @@ def _determine_middleware_type(middleware: Any) -> MiddlewareType: else: # Not enough parameters - can't be valid middleware raise MiddlewareException( - f"MiddlewareTypes function must have at least 2 parameters (context, call_next), " + f"Middleware function must have at least 2 parameters (context, call_next), " f"but {middleware.__name__} has {len(params)}" ) except Exception as e: