Skip to content

Commit 2f23b13

Browse files
authored
Merge branch 'main' into simplify-docagent
2 parents d04457f + b07a0f2 commit 2f23b13

File tree

13 files changed

+1041
-154
lines changed

13 files changed

+1041
-154
lines changed

autogen/a2a/agent_executor.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@
33
# SPDX-License-Identifier: Apache-2.0
44

55
from datetime import datetime, timezone
6+
from uuid import uuid4
67

78
from a2a.server.agent_execution import AgentExecutor, RequestContext
89
from a2a.server.events import EventQueue
9-
from a2a.types import TaskArtifactUpdateEvent, TaskState, TaskStatus, TaskStatusUpdateEvent
10-
from a2a.utils import new_task
10+
from a2a.types import Task, TaskArtifactUpdateEvent, TaskState, TaskStatus, TaskStatusUpdateEvent
1111
from a2a.utils.message import new_agent_text_message
1212

1313
from autogen import ConversableAgent
@@ -33,8 +33,17 @@ async def execute(self, context: RequestContext, event_queue: EventQueue) -> Non
3333

3434
task = context.current_task
3535
if not task:
36-
task = new_task(context.message)
37-
task.status.timestamp = datetime.now(timezone.utc).isoformat()
36+
request = context.message
37+
# build task object manually to allow empty messages
38+
task = Task(
39+
status=TaskStatus(
40+
state=TaskState.submitted,
41+
timestamp=datetime.now(timezone.utc).isoformat(),
42+
),
43+
id=request.task_id or str(uuid4()),
44+
context_id=request.context_id or str(uuid4()),
45+
history=[request],
46+
)
3847
# publish the task status submitted event
3948
await event_queue.enqueue_event(task)
4049

autogen/a2a/client.py

Lines changed: 69 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from a2a.client import ClientFactory as A2AClientFactory
1414
from a2a.types import AgentCard, Message, Task, TaskIdParams, TaskQueryParams, TaskState
1515
from a2a.utils.constants import AGENT_CARD_WELL_KNOWN_PATH, EXTENDED_AGENT_CARD_PATH, PREV_AGENT_CARD_WELL_KNOWN_PATH
16+
from typing_extensions import Self
1617

1718
from autogen import ConversableAgent
1819
from autogen.agentchat.group import ContextVariables
@@ -55,9 +56,14 @@ def __init__(
5556
max_reconnects: int = 3,
5657
polling_interval: float = 0.5,
5758
) -> None:
58-
self.url = url
59+
self.url = url # make it public for backward compatibility
5960

6061
self._httpx_client_factory = client or EmptyClientFactory()
62+
self._card_resolver = A2ACardResolver(
63+
httpx_client=self._httpx_client_factory(),
64+
base_url=url,
65+
)
66+
6167
self._max_reconnects = max_reconnects
6268
self._polling_interval = polling_interval
6369

@@ -66,7 +72,7 @@ def __init__(
6672
self.__llm_config: dict[str, Any] = {}
6773

6874
self._client_config = client_config or ClientConfig()
69-
self.__agent_card: AgentCard | None = None
75+
self._agent_card: AgentCard | None = None
7076

7177
self.replace_reply_func(
7278
ConversableAgent.generate_oai_reply,
@@ -77,6 +83,48 @@ def __init__(
7783
A2aRemoteAgent.a_generate_remote_reply,
7884
)
7985

86+
@classmethod
87+
def from_card(
88+
cls,
89+
card: AgentCard,
90+
*,
91+
silent: bool | None = None,
92+
client: ClientFactory | None = None,
93+
client_config: ClientConfig | None = None,
94+
max_reconnects: int = 3,
95+
polling_interval: float = 0.5,
96+
) -> Self:
97+
"""Creates an A2aRemoteAgent instance from an existing AgentCard.
98+
99+
This method allows you to instantiate an A2aRemoteAgent directly using a pre-existing
100+
AgentCard, such as one retrieved from a discovery service or constructed manually.
101+
The resulting agent will use the data from the given card and avoid redundant card
102+
fetching. The agent's registryURL is set to "UNKNOWN" since it is assumed to be derived
103+
from the card.
104+
105+
Args:
106+
card: The agent card containing metadata and configuration for the remote agent.
107+
silent: whether to print the message sent. If None, will use the value of silent in each function.
108+
client: An optional HTTPX client instance factory.
109+
client_config: A2A Client configuration options.
110+
max_reconnects: Maximum number of reconnection attempts before giving up.
111+
polling_interval: Time in seconds between polling operations. Works for A2A Servers doesn't support streaming.
112+
113+
Returns:
114+
Self: An instance of the A2aRemoteAgent configured with the provided card.
115+
"""
116+
instance = cls(
117+
url="UNKNOWN",
118+
name=card.name,
119+
silent=silent,
120+
client=client,
121+
client_config=client_config,
122+
max_reconnects=max_reconnects,
123+
polling_interval=polling_interval,
124+
)
125+
instance._agent_card = card
126+
return instance
127+
80128
def generate_remote_reply(
81129
self,
82130
messages: list[dict[str, Any]] | None = None,
@@ -94,8 +142,8 @@ async def a_generate_remote_reply(
94142
if messages is None:
95143
messages = self._oai_messages[sender]
96144

97-
if not self.__agent_card:
98-
self.__agent_card = await self._get_agent_card()
145+
if not self._agent_card:
146+
self._agent_card = await self._get_agent_card()
99147

100148
initial_message = request_message_to_a2a(
101149
request_message=RequestMessage(
@@ -108,9 +156,9 @@ async def a_generate_remote_reply(
108156

109157
self._client_config.httpx_client = self._httpx_client_factory()
110158
async with self._client_config.httpx_client:
111-
agent_client = A2AClientFactory(self._client_config).create(self.__agent_card)
159+
agent_client = A2AClientFactory(self._client_config).create(self._agent_card)
112160

113-
if self.__agent_card.capabilities.streaming:
161+
if self._agent_card.capabilities.streaming:
114162
reply = await self._ask_streaming(agent_client, initial_message)
115163
return self._apply_reply(reply, sender)
116164

@@ -142,9 +190,9 @@ async def _ask_streaming(self, client: Client, message: Message) -> ResponseMess
142190
if task and connection_attemps < self._max_reconnects:
143191
pass
144192

145-
if not self.__agent_card:
193+
if not self._agent_card:
146194
raise A2aClientError("Failed to connect to the agent: agent card not found") from e
147-
raise A2aClientError(f"Failed to connect to the agent: {pformat(self.__agent_card.model_dump())}") from e
195+
raise A2aClientError(f"Failed to connect to the agent: {pformat(self._agent_card.model_dump())}") from e
148196

149197
task = cast(Task, task)
150198
while connection_attemps < self._max_reconnects:
@@ -159,11 +207,9 @@ async def _ask_streaming(self, client: Client, message: Message) -> ResponseMess
159207
if connection_attemps < self._max_reconnects:
160208
pass
161209

162-
if not self.__agent_card:
210+
if not self._agent_card:
163211
raise A2aClientError("Failed to connect to the agent: agent card not found") from e
164-
raise A2aClientError(
165-
f"Failed to connect to the agent: {pformat(self.__agent_card.model_dump())}"
166-
) from e
212+
raise A2aClientError(f"Failed to connect to the agent: {pformat(self._agent_card.model_dump())}") from e
167213

168214
return None
169215

@@ -175,9 +221,9 @@ async def _ask_polling(self, client: Client, message: Message) -> ResponseMessag
175221
return result
176222
break
177223
except httpx.ConnectError as e:
178-
if not self.__agent_card:
224+
if not self._agent_card:
179225
raise A2aClientError("Failed to connect to the agent: agent card not found") from e
180-
raise A2aClientError(f"Failed to connect to the agent: {pformat(self.__agent_card.model_dump())}") from e
226+
raise A2aClientError(f"Failed to connect to the agent: {pformat(self._agent_card.model_dump())}") from e
181227

182228
started_task, connection_attemps = cast(Task, started_task), 0
183229
while connection_attemps < self._max_reconnects:
@@ -190,10 +236,10 @@ async def _ask_polling(self, client: Client, message: Message) -> ResponseMessag
190236
if connection_attemps < self._max_reconnects:
191237
pass
192238

193-
if not self.__agent_card:
239+
if not self._agent_card:
194240
raise A2aClientError("Failed to connect to the agent: agent card not found") from e
195241
raise A2aClientError(
196-
f"Failed to connect to the agent: {pformat(self.__agent_card.model_dump())}"
242+
f"Failed to connect to the agent: {pformat(self._agent_card.model_dump())}"
197243
) from e
198244

199245
else:
@@ -231,27 +277,27 @@ async def _get_agent_card(
231277
self,
232278
auth_http_kwargs: dict[str, Any] | None = None,
233279
) -> AgentCard:
234-
resolver = A2ACardResolver(httpx_client=self._httpx_client_factory(), base_url=self.url)
235-
236280
card: AgentCard | None = None
237281

238282
try:
239-
logger.info(f"Attempting to fetch public agent card from: {self.url}{AGENT_CARD_WELL_KNOWN_PATH}")
283+
logger.info(
284+
f"Attempting to fetch public agent card from: {self._card_resolver.base_url}{AGENT_CARD_WELL_KNOWN_PATH}"
285+
)
240286

241287
try:
242-
card = await resolver.get_agent_card(relative_card_path=AGENT_CARD_WELL_KNOWN_PATH)
288+
card = await self._card_resolver.get_agent_card(relative_card_path=AGENT_CARD_WELL_KNOWN_PATH)
243289
except A2AClientHTTPError as e_public:
244290
if e_public.status_code == 404:
245291
logger.info(
246-
f"Attempting to fetch public agent card from: {self.url}{PREV_AGENT_CARD_WELL_KNOWN_PATH}"
292+
f"Attempting to fetch public agent card from: {self._card_resolver.base_url}{PREV_AGENT_CARD_WELL_KNOWN_PATH}"
247293
)
248-
card = await resolver.get_agent_card(relative_card_path=PREV_AGENT_CARD_WELL_KNOWN_PATH)
294+
card = await self._card_resolver.get_agent_card(relative_card_path=PREV_AGENT_CARD_WELL_KNOWN_PATH)
249295
else:
250296
raise e_public
251297

252298
if card.supports_authenticated_extended_card:
253299
try:
254-
card = await resolver.get_agent_card(
300+
card = await self._card_resolver.get_agent_card(
255301
relative_card_path=EXTENDED_AGENT_CARD_PATH,
256302
http_kwargs=auth_http_kwargs,
257303
)

autogen/a2a/utils.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@
1010

1111
from autogen.remote.protocol import RequestMessage, ResponseMessage
1212

13-
CLIENT_TOOLS_KEY = "ag2_client_tools"
14-
CONTEXT_KEY = "ag2_context_update"
13+
AG2_METADATA_KEY_PREFIX = "ag2_"
14+
CLIENT_TOOLS_KEY = f"{AG2_METADATA_KEY_PREFIX}client_tools"
15+
CONTEXT_KEY = f"{AG2_METADATA_KEY_PREFIX}context_update"
1516

1617

1718
def request_message_to_a2a(

autogen/agentchat/group/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
GroupManagerTarget,
2525
)
2626
"""
27+
from .targets.function_target import FunctionTarget, FunctionTargetResult
2728
from .targets.transition_target import (
2829
AgentNameTarget,
2930
AgentTarget,
@@ -44,6 +45,8 @@
4445
"ContextVariables",
4546
"ExpressionAvailableCondition",
4647
"ExpressionContextCondition",
48+
"FunctionTarget",
49+
"FunctionTargetResult",
4750
"GroupChatConfig",
4851
"GroupChatTarget",
4952
# "GroupManagerSelectionMessageContextStr",

0 commit comments

Comments
 (0)