Python: Add middleware sample to show filtering messages between agents (#3556) - #6143
Conversation
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Adds a message-middleware demonstration layer to the group chat agent manager sample. A composable interceptor pattern is introduced (content filtering, tagging, sender block-listing) and applied via a user-space wrapper around workflow.run until native builder support exists.
Changes:
- Introduces
MessageInterceptortype andcompose_middlewareplus three example interceptors. - Wraps workflow execution with
run_with_message_middlewareto mutate intermediate/output events. - Updates docstring, task prompt, and removes the duplicate
termination_conditionconstructor argument in favor of.with_termination_condition(...).
| try: | ||
| return msg.replace(text=safe_text) if hasattr(msg, "replace") else Message( | ||
| role=msg.role, author_name=msg.author_name, text=safe_text | ||
| ) | ||
| except Exception: | ||
| return None |
| async for event in workflow.run(task, stream=stream): | ||
| if event.type == "intermediate" and isinstance(event.data, AgentResponseUpdate): | ||
| original_text = event.data.text | ||
| if original_text: |
| intercepted = middleware(temp_msg) | ||
| if intercepted is None: | ||
| continue | ||
| event.data.text = intercepted.text |
| processed = middleware(msg) | ||
| if processed is not None: | ||
| filtered_messages.append(processed) | ||
| event.data = filtered_messages |
| middleware_pipeline = compose_middleware( | ||
| create_content_filter(forbidden_terms=["confidential", "internal-ip", "password"]), | ||
| create_message_tagger(prefix="AUDIT"), | ||
| create_sender_blocklist(blocked_agents=set()) |
| 2. Transformation: Audit tagging, format enforcement, prompt sanitization | ||
| 3. Access Control: Multi-tenant routing, cost gates, agent permissioning | ||
| 4. Ordering Rule: Access Control → Content Filter → Transformation → Logging | ||
| 5. Failure Mode: Dropping messages (`None`) removes context → prefer redaction tokens | ||
| 6. Integration Note: This sample uses a wrapper pattern. Native hooks (e.g., |
| asyncio.run(main()) | ||
|
No newline at end of file |
There was a problem hiding this comment.
I do not think this closes #3556 as written. The issue asks for message interception between agents, but this sample wraps workflow.run() and post-processes emitted events after the group chat internals have already consumed the participant response. That means a dropped/redacted message can still be appended to the orchestrator conversation and broadcast to other participants before the wrapper sees it.
There are also current API mismatches: agent_framework.Event is not exported, Message uses contents=[...] rather than text=..., Message has no replace() method, WorkflowEvent is not a dataclass so dataclasses.replace(event, ...) will not work, and streamed updates are emitted as output events rather than intermediate events.
I think the best way to make this sample match #3556 is to show a custom BaseGroupChatOrchestrator that applies the interceptor before participant messages are appended to group-chat state or broadcast to other participants.
An executor/participant wrapper can transform an AgentExecutorResponse, but that feels less aligned with the issue: the request is specifically about message interception between agents in group chat. The orchestrator is already the central routing point for participant responses, speaker selection, and broadcasts, so it is the clearest place to demonstrate content filtering, message transformation, and access control without implying that caller-facing workflow.run() events are the actual inter-agent message channel.
p.s. I'd also prefer a better name for the sample too, please. And it needs to be added to the workflows samples README. Thanks.
|
Considering the concerns raised by Evan Mattson (@moonbox3) and the lack of activity, I'm closing this one. |
Motivation and Context
This PR adds a sample demonstrating how middleware can be used to intercept and process messages between agents in a group chat workflow.
Currently, there is no built-in middleware hook in
GroupChatBuilderfor message interception. This sample provides a user-space pattern to achieve this behavior without modifying the core framework.This addresses issue #3556 by illustrating how developers can implement:
Description
This change updates the
group_chat_agent_manager.pysample to include a middleware-style pattern for message interception.Key aspects of the implementation:
Introduces a simple
MessageInterceptorcallable interface:MessageMessageorNone(to drop messages)Demonstrates multiple middleware use cases:
Implements a wrapper (
run_with_message_middleware) that:intermediateevents)outputevents)Documents patterns and trade-offs:
Contribution Checklist