Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions python/packages/core/agent_framework/_middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -1445,7 +1445,7 @@ def _middleware_handler(


def _determine_middleware_type(middleware: Any) -> MiddlewareType:
"""Determine middleware type using decorator and/or parameter type annotation.
"""Determine the middleware type from function annotations or decorators.

Args:
middleware: The middleware function to analyze.
Expand All @@ -1456,6 +1456,8 @@ def _determine_middleware_type(middleware: Any) -> MiddlewareType:
Raises:
MiddlewareException: When middleware type cannot be determined or there's a mismatch.
"""
middleware_name = getattr(middleware, "__name__", type(middleware).__name__)

# Check for decorator marker
decorator_type: MiddlewareType | None = getattr(middleware, "_middleware_type", None)

Expand All @@ -1480,7 +1482,7 @@ def _determine_middleware_type(middleware: Any) -> MiddlewareType:
# Not enough parameters - can't be valid middleware
raise MiddlewareException(
f"Middleware function must have at least 2 parameters (context, call_next), "
f"but {middleware.__name__} has {len(params)}"
f"but {middleware_name} has {len(params)}"
)
except Exception as e:
if isinstance(e, MiddlewareException):
Expand All @@ -1493,7 +1495,7 @@ def _determine_middleware_type(middleware: Any) -> MiddlewareType:
if decorator_type != param_type:
raise MiddlewareException(
f"MiddlewareTypes type mismatch: decorator indicates '{decorator_type.value}' "
f"but parameter type indicates '{param_type.value}' for function {middleware.__name__}"
f"but parameter type indicates '{param_type.value}' for function {middleware_name}"
)
return decorator_type

Expand All @@ -1507,7 +1509,7 @@ def _determine_middleware_type(middleware: Any) -> MiddlewareType:

# Neither decorator nor parameter type specified - throw exception
raise MiddlewareException(
f"Cannot determine middleware type for function {middleware.__name__}. "
f"Cannot determine middleware type for function {middleware_name}. "
f"Please either use @agent_middleware/@function_middleware/@chat_middleware decorators "
f"or specify parameter types (AgentContext, FunctionInvocationContext, or ChatContext)."
)
Expand Down
51 changes: 51 additions & 0 deletions python/packages/core/tests/core/test_middleware_with_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -2302,3 +2302,54 @@ async def kwargs_middleware(context: AgentContext, call_next: Callable[[], Await
# response = await agent.run("test message")
# assert response is not None
# assert execution_order == ["before", "after"]


class TestCallableClassMiddlewareErrorHandling:
"""Tests for exception handling when using callable class instances as middleware."""

def test_callable_class_middleware_insufficient_params_raises_middleware_exception(self) -> None:
"""Test that callable class instance with insufficient params raises MiddlewareException."""

class InsufficientParamsMiddleware:
async def __call__(self, ctx: Any) -> None:
pass

client = MockBaseChatClient()
insufficient_middleware: list[Any] = [InsufficientParamsMiddleware()]
with pytest.raises(MiddlewareException) as exc_info:
Agent(client=client, middleware=insufficient_middleware)

assert "InsufficientParamsMiddleware" in str(exc_info.value)
assert "must have at least 2 parameters" in str(exc_info.value)

def test_callable_class_middleware_type_mismatch_raises_middleware_exception(self) -> None:
"""Test that callable class instance with decorator/annotation mismatch raises MiddlewareException."""

class MismatchedCallableMiddleware:
_middleware_type = MiddlewareType.AGENT

async def __call__(self, context: FunctionInvocationContext, call_next: Any) -> None:
await call_next()

client = MockBaseChatClient()
mismatched_middleware: list[Any] = [MismatchedCallableMiddleware()]
with pytest.raises(MiddlewareException) as exc_info:
Agent(client=client, middleware=mismatched_middleware)

assert "MismatchedCallableMiddleware" in str(exc_info.value)
assert "MiddlewareTypes type mismatch" in str(exc_info.value)

def test_callable_class_middleware_undetermined_type_raises_middleware_exception(self) -> None:
"""Test that a callable class instance without annotations or decorator raises MiddlewareException."""

class UndeterminedCallableMiddleware:
async def __call__(self, arg1: Any, arg2: Any) -> None:
pass

client = MockBaseChatClient()
undetermined_middleware: list[Any] = [UndeterminedCallableMiddleware()]
with pytest.raises(MiddlewareException) as exc_info:
Agent(client=client, middleware=undetermined_middleware)

assert "UndeterminedCallableMiddleware" in str(exc_info.value)
assert "Cannot determine middleware type" in str(exc_info.value)
Loading