-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Python: Generic Agents ADR #3448
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Python: Generic Agents ADR #3448
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR introduces an Architecture Decision Record (ADR) proposing to make the Python Agent Framework's agent abstractions generic over input and output types. The proposal aims to decouple agents from chat-specific types, enabling support for structured data, specialized protocols (A2A, Copilot Studio), and custom agent types while maintaining type safety.
Changes:
- Adds ADR proposing generic
TInputandTOutputtype parameters forAgentProtocol - Proposes renaming
AgentResponse.messagestoAgentResponse.itemsfor generic applicability - Proposes unified
run()method withstreamparameter (aligns with ADR 0013) - Introduces
ResponseStream[TUpdate, TFinal]type for typed streaming responses
| class AgentProtocol(Protocol, Generic[TInput, TOutput]): | ||
| def run( | ||
| self, | ||
| input: TInput | Sequence[TInput] | None = None, # renamed from 'messages' |
Copilot
AI
Jan 27, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using "input" as a parameter name shadows the built-in Python function input(). While this is technically valid in Python, it's generally discouraged as it can cause confusion and make the built-in unavailable in that scope. Consider using a more specific name like "message", "data", or "content" instead, or if keeping consistency with the generic nature is important, consider "value" or "item".
| input: TInput | Sequence[TInput] | None = None, # renamed from 'messages' | |
| value: TInput | Sequence[TInput] | None = None, # renamed from 'messages' |
| input: TInput | Sequence[TInput] | None = None, | ||
| *, | ||
| stream: Literal[False] = False, | ||
| thread: AgentThread | None = None, | ||
| **kwargs: Any, | ||
| ) -> Coroutine[Any, Any, AgentResponse[TOutput]]: | ||
| ... | ||
|
|
||
| @overload | ||
| def run( | ||
| self, | ||
| input: TInput | Sequence[TInput] | None = None, | ||
| *, | ||
| stream: Literal[True], | ||
| thread: AgentThread | None = None, | ||
| **kwargs: Any, | ||
| ) -> ResponseStream[AgentResponseUpdate[TOutput], AgentResponse[TOutput]]: | ||
| ... | ||
|
|
||
| def run( | ||
| self, | ||
| input: TInput | Sequence[TInput] | None = None, |
Copilot
AI
Jan 27, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using "input" as a parameter name shadows the built-in Python function input(). While this is technically valid in Python, it's generally discouraged as it can cause confusion and make the built-in unavailable in that scope. Consider using a more specific name like "message", "data", or "content" instead, or if keeping consistency with the generic nature is important, consider "value" or "item".
| ) -> Awaitable[AgentResponse[ChatMessage]]: | ||
| ... | ||
|
|
||
| @overload | ||
| def run( | ||
| self, | ||
| messages: ChatInput | Sequence[ChatInput] | None = None, | ||
| *, | ||
| stream: Literal[True], | ||
| thread: AgentThread | None = None, | ||
| **kwargs: Any, | ||
| ) -> ResponseStream[AgentResponseUpdate[ChatMessage], AgentResponse[ChatMessage]]: | ||
| ... | ||
|
|
||
| def run( | ||
| self, | ||
| messages: ChatInput | Sequence[ChatInput] | None = None, | ||
| *, | ||
| stream: bool = False, | ||
| thread: AgentThread | None = None, | ||
| **kwargs: Any, | ||
| ) -> Awaitable[AgentResponse[ChatMessage]] | ResponseStream[AgentResponseUpdate[ChatMessage], AgentResponse[ChatMessage]]: |
Copilot
AI
Jan 27, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Inconsistent return type annotation between AgentProtocol and ChatAgent. The AgentProtocol uses Coroutine[Any, Any, AgentResponse[TOutput]] (line 230), but ChatAgent uses Awaitable[AgentResponse[ChatMessage]] (lines 283, 304). For consistency and to match the protocol, ChatAgent should also use Coroutine[Any, Any, AgentResponse[ChatMessage]], or the protocol should be changed to use the more general Awaitable type.
|
|
||
| @property | ||
| def text(self) -> str: | ||
| """Concatenated text (only valid when TOutput is ChatMessage).""" | ||
| ... |
Copilot
AI
Jan 27, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The text property is documented as "only valid when TOutput is ChatMessage", but it's defined on the generic AgentResponse[TOutput] class. This creates an API design issue where the property exists at runtime for all types but is only meaningful for ChatMessage outputs. Consider either: (1) removing this property from the generic base and adding it only to a specialized ChatAgentResponse subclass, (2) documenting that it raises an error for non-ChatMessage types, or (3) making it return an empty string or None for incompatible types. The current design may lead to runtime errors or confusion.
| @property | |
| def text(self) -> str: | |
| """Concatenated text (only valid when TOutput is ChatMessage).""" | |
| ... |
| status: Proposed | ||
| contact: eavanvalkenburg | ||
| date: 2026-01-26 | ||
| deciders: eavanvalkenburg, markwallace-microsoft, sphenry, alliscode, taochenosu, moonbox3, dmytrostruk, giles17 |
Copilot
AI
Jan 27, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Extra space found after "markwallace-microsoft," in the deciders list. Should be a single space between items.
| deciders: eavanvalkenburg, markwallace-microsoft, sphenry, alliscode, taochenosu, moonbox3, dmytrostruk, giles17 | |
| deciders: eavanvalkenburg, markwallace-microsoft, sphenry, alliscode, taochenosu, moonbox3, dmytrostruk, giles17 |
|
|
||
| ## Decision Outcome | ||
|
|
||
| Update to use a Input and Output type generic for all agent types. |
Copilot
AI
Jan 27, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Grammatical error: "a Input" should be "an Input" (use "an" before vowel sounds).
| Update to use a Input and Output type generic for all agent types. | |
| Update to use an Input and Output type generic for all agent types. |
| Currently, the Agent Framework's `AgentProtocol` and agent implementations are tightly coupled to chat-based interactions. The `run()` method accepts `str | ChatMessage | Sequence[str | ChatMessage]` and returns `AgentResponse` containing a `messages: list[ChatMessage]` field. | ||
|
|
||
| This design limits extensibility for agents that work with different input/output types, such as: | ||
| - Structured data agents (JSON input/output) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ChatMessage is already extensible today via the AIContent list that it contains. AIContent can be text (including JSON), images, sound, or really anything else that an Agent/AI Service requires.
I think the value of this approach is that callers will always receive a specific structure even if they don't know the type of agent they are working with. Within that structure there may be known content types like TextContent, but also Agent/Service specific content types. They can choose to just use the former (if they don't know the agent type) or both if they do know the agent type.
Motivation and Context
Description
Contribution Checklist