Skip to content

Conversation

@eavanvalkenburg
Copy link
Member

Motivation and Context

Description

Contribution Checklist

  • The code builds clean without any errors or warnings
  • The PR follows the Contribution Guidelines
  • All unit tests pass, and I have added new tests where possible
  • Is this a breaking change? If yes, add "[BREAKING]" prefix to the title of the PR.

Copilot AI review requested due to automatic review settings January 27, 2026 02:18
@markwallace-microsoft markwallace-microsoft added the documentation Improvements or additions to documentation label Jan 27, 2026
@eavanvalkenburg eavanvalkenburg changed the title Python: created ADR for generic input output on agents. Python: Generic Agents ADR Jan 27, 2026
Copy link
Contributor

Copilot AI left a 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 TInput and TOutput type parameters for AgentProtocol
  • Proposes renaming AgentResponse.messages to AgentResponse.items for generic applicability
  • Proposes unified run() method with stream parameter (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'
Copy link

Copilot AI Jan 27, 2026

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".

Suggested change
input: TInput | Sequence[TInput] | None = None, # renamed from 'messages'
value: TInput | Sequence[TInput] | None = None, # renamed from 'messages'

Copilot uses AI. Check for mistakes.
Comment on lines +225 to +246
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,
Copy link

Copilot AI Jan 27, 2026

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".

Copilot uses AI. Check for mistakes.
Comment on lines +283 to +304
) -> 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]]:
Copy link

Copilot AI Jan 27, 2026

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.

Copilot uses AI. Check for mistakes.
Comment on lines +151 to +155

@property
def text(self) -> str:
"""Concatenated text (only valid when TOutput is ChatMessage)."""
...
Copy link

Copilot AI Jan 27, 2026

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.

Suggested change
@property
def text(self) -> str:
"""Concatenated text (only valid when TOutput is ChatMessage)."""
...

Copilot uses AI. Check for mistakes.
status: Proposed
contact: eavanvalkenburg
date: 2026-01-26
deciders: eavanvalkenburg, markwallace-microsoft, sphenry, alliscode, taochenosu, moonbox3, dmytrostruk, giles17
Copy link

Copilot AI Jan 27, 2026

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.

Suggested change
deciders: eavanvalkenburg, markwallace-microsoft, sphenry, alliscode, taochenosu, moonbox3, dmytrostruk, giles17
deciders: eavanvalkenburg, markwallace-microsoft, sphenry, alliscode, taochenosu, moonbox3, dmytrostruk, giles17

Copilot uses AI. Check for mistakes.

## Decision Outcome

Update to use a Input and Output type generic for all agent types.
Copy link

Copilot AI Jan 27, 2026

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).

Suggested change
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.

Copilot uses AI. Check for mistakes.
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)
Copy link
Contributor

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants