Compare commits

...

4 Commits

Author SHA1 Message Date
Devin AI
3ea38280d7 ci: trigger test matrix
Co-Authored-By: João <joao@crewai.com>
2025-11-12 18:12:26 +00:00
Devin AI
7e154ebc16 test: Add multi-turn skill ID resolution test
This test verifies that the fix in _handle_agent_response_and_continue()
correctly rebuilds the AgentResponse model with both endpoints and skill IDs
for subsequent turns in multi-turn A2A conversations.

The test simulates a multi-turn scenario where:
1. First turn: LLM returns skill ID 'Research'
2. Second turn: LLM returns skill ID 'Writing' (different agent)
3. Third turn: LLM returns skill ID 'Research' again

All turns should accept skill IDs without validation errors.

Co-Authored-By: João <joao@crewai.com>
2025-11-12 18:08:05 +00:00
Devin AI
416c2665a7 fix: Rebuild AgentResponse model in multi-turn A2A flows to support skill IDs
In multi-turn A2A conversations, the AgentResponse model was only rebuilt
in _execute_task_with_a2a() but not in subsequent turns handled by
_handle_agent_response_and_continue(). This meant that if the LLM returned
a skill ID on a later turn, it would fail validation.

This commit rebuilds the model in _handle_agent_response_and_continue()
using extract_agent_identifiers_from_cards() to include both endpoints
and skill IDs, ensuring all turns support skill ID resolution.

Co-Authored-By: João <joao@crewai.com>
2025-11-12 18:02:58 +00:00
Devin AI
d141078e72 fix: Allow A2A agents to be identified by skill ID in addition to endpoint URL
This commit fixes issue #3897 where the LLM would return a skill.id
(e.g., 'Research') instead of the full endpoint URL, causing a
Pydantic validation error.

Changes:
- Added resolve_agent_identifier() function to map skill IDs to endpoints
- Added extract_agent_identifiers_from_cards() to collect both endpoints and skill IDs
- Modified _execute_task_with_a2a() to rebuild AgentResponse model after fetching AgentCards
- Updated _delegate_to_a2a() to use resolver for identifier resolution
- Updated _augment_prompt_with_a2a() to explicitly instruct LLM about both identifier types
- Added comprehensive unit tests for resolve_agent_identifier()
- Added integration tests replicating the exact issue from #3897

The fix allows the dynamic Pydantic model to accept both endpoint URLs
and skill IDs in the Literal constraint, then resolves skill IDs to
their canonical endpoints before delegation. This maintains backward
compatibility while fixing the validation error.

Fixes #3897

Co-Authored-By: João <joao@crewai.com>
2025-11-12 17:51:48 +00:00
4 changed files with 656 additions and 8 deletions

View File

@@ -753,3 +753,99 @@ def get_a2a_agents_and_response_model(
"""
a2a_agents, agent_ids = extract_a2a_agent_ids_from_config(a2a_config=a2a_config)
return a2a_agents, create_agent_response_model(agent_ids)
def extract_agent_identifiers_from_cards(
a2a_agents: list[A2AConfig],
agent_cards: dict[str, AgentCard],
) -> tuple[str, ...]:
"""Extract all valid agent identifiers (endpoints and skill IDs) from agent cards.
Args:
a2a_agents: List of A2A agent configurations
agent_cards: Dictionary mapping endpoints to AgentCards
Returns:
Tuple of all valid identifiers (endpoints + skill IDs)
"""
identifiers = set()
for config in a2a_agents:
identifiers.add(config.endpoint)
for card in agent_cards.values():
if card.skills:
for skill in card.skills:
identifiers.add(skill.id)
return tuple(sorted(identifiers))
def resolve_agent_identifier(
identifier: str,
a2a_agents: list[A2AConfig],
agent_cards: dict[str, AgentCard],
) -> str:
"""Resolve an agent identifier (endpoint or skill ID) to a canonical endpoint.
This function allows both endpoint URLs and skill IDs to be used as agent identifiers.
If the identifier is already an endpoint, it's returned as-is. If it's a skill ID,
it's resolved to the endpoint of the agent card that contains that skill.
Args:
identifier: Either an endpoint URL or a skill ID
a2a_agents: List of A2A agent configurations
agent_cards: Dictionary mapping endpoints to AgentCards
Returns:
The canonical endpoint URL
Raises:
ValueError: If the identifier is unknown or ambiguous (matches multiple agents)
Examples:
>>> # Endpoint passthrough
>>> resolve_agent_identifier(
... "http://localhost:10001/.well-known/agent-card.json",
... a2a_agents,
... agent_cards
... )
'http://localhost:10001/.well-known/agent-card.json'
>>> # Skill ID resolution
>>> resolve_agent_identifier("Research", a2a_agents, agent_cards)
'http://localhost:10001/.well-known/agent-card.json'
"""
endpoints = {config.endpoint for config in a2a_agents}
if identifier in endpoints:
return identifier
matching_endpoints: list[str] = []
for endpoint, card in agent_cards.items():
if card.skills:
for skill in card.skills:
if skill.id == identifier:
matching_endpoints.append(endpoint)
break
if len(matching_endpoints) == 0:
available_endpoints = ", ".join(sorted(endpoints))
available_skill_ids = []
for card in agent_cards.values():
if card.skills:
available_skill_ids.extend([skill.id for skill in card.skills])
available_skills = ", ".join(sorted(set(available_skill_ids))) if available_skill_ids else "none"
raise ValueError(
f"Unknown A2A agent identifier '{identifier}'. "
f"Available endpoints: {available_endpoints}. "
f"Available skill IDs: {available_skills}."
)
if len(matching_endpoints) > 1:
endpoints_list = ", ".join(sorted(matching_endpoints))
raise ValueError(
f"Ambiguous skill ID '{identifier}' found in multiple agents: {endpoints_list}. "
f"Please use the specific endpoint URL to disambiguate."
)
return matching_endpoints[0]

View File

@@ -23,9 +23,12 @@ from crewai.a2a.templates import (
)
from crewai.a2a.types import AgentResponseProtocol
from crewai.a2a.utils import (
create_agent_response_model,
execute_a2a_delegation,
extract_agent_identifiers_from_cards,
fetch_agent_card,
get_a2a_agents_and_response_model,
resolve_agent_identifier,
)
from crewai.events.event_bus import crewai_event_bus
from crewai.events.types.a2a_events import (
@@ -190,6 +193,9 @@ def _execute_task_with_a2a(
finally:
task.description = original_description
agent_identifiers = extract_agent_identifiers_from_cards(a2a_agents, agent_cards)
agent_response_model = create_agent_response_model(agent_identifiers)
task.description = _augment_prompt_with_a2a(
a2a_agents=a2a_agents,
task_description=original_description,
@@ -301,6 +307,13 @@ def _augment_prompt_with_a2a(
IMPORTANT: You have the ability to delegate this task to remote A2A agents.
{agents_text}
AGENT IDENTIFICATION: When setting a2a_ids, you may use either:
1. The agent's endpoint URL (e.g., "http://localhost:10001/.well-known/agent-card.json")
2. The exact skill.id from the agent's skills list (e.g., "Research")
Prefer using endpoint URLs when possible to avoid ambiguity. If a skill.id appears on multiple agents, you MUST use the endpoint URL to specify which agent you want.
{history_text}{turn_info}
@@ -373,6 +386,9 @@ def _handle_agent_response_and_continue(
if "agent_card" in a2a_result and agent_id not in agent_cards_dict:
agent_cards_dict[agent_id] = a2a_result["agent_card"]
agent_identifiers = extract_agent_identifiers_from_cards(a2a_agents, agent_cards_dict)
agent_response_model = create_agent_response_model(agent_identifiers)
task.description = _augment_prompt_with_a2a(
a2a_agents=a2a_agents,
task_description=original_task_description,
@@ -445,16 +461,20 @@ def _delegate_to_a2a(
ImportError: If a2a-sdk is not installed
"""
a2a_agents, agent_response_model = get_a2a_agents_and_response_model(self.a2a)
agent_ids = tuple(config.endpoint for config in a2a_agents)
current_request = str(agent_response.message)
agent_id = agent_response.a2a_ids[0]
agent_identifier = agent_response.a2a_ids[0]
if agent_id not in agent_ids:
raise ValueError(
f"Unknown A2A agent ID(s): {agent_response.a2a_ids} not in {agent_ids}"
agent_cards_dict = agent_cards or {}
try:
agent_endpoint = resolve_agent_identifier(
agent_identifier, a2a_agents, agent_cards_dict
)
except ValueError as e:
raise ValueError(
f"Failed to resolve A2A agent identifier '{agent_identifier}': {e}"
) from e
agent_config = next(filter(lambda x: x.endpoint == agent_id, a2a_agents))
agent_config = next(filter(lambda x: x.endpoint == agent_endpoint, a2a_agents))
task_config = task.config or {}
context_id = task_config.get("context_id")
task_id_config = task_config.get("task_id")
@@ -488,7 +508,7 @@ def _delegate_to_a2a(
metadata=metadata,
extensions=extensions,
conversation_history=conversation_history,
agent_id=agent_id,
agent_id=agent_endpoint,
agent_role=Role.user,
agent_branch=agent_branch,
response_model=agent_config.response_model,
@@ -501,7 +521,7 @@ def _delegate_to_a2a(
final_result, next_request = _handle_agent_response_and_continue(
self=self,
a2a_result=a2a_result,
agent_id=agent_id,
agent_id=agent_endpoint,
agent_cards=agent_cards,
a2a_agents=a2a_agents,
original_task_description=original_task_description,

View File

@@ -0,0 +1,245 @@
"""Test resolve_agent_identifier function for A2A skill ID resolution."""
import pytest
from a2a.types import AgentCapabilities, AgentCard, AgentSkill
from crewai.a2a.config import A2AConfig
from crewai.a2a.utils import resolve_agent_identifier
@pytest.fixture
def sample_agent_configs():
"""Create sample A2A agent configurations."""
return [
A2AConfig(endpoint="http://localhost:10001/.well-known/agent-card.json"),
A2AConfig(endpoint="http://localhost:10002/.well-known/agent-card.json"),
]
@pytest.fixture
def sample_agent_cards():
"""Create sample AgentCards with skills."""
card1 = AgentCard(
name="Research Agent",
description="An expert research agent",
url="http://localhost:10001",
version="1.0.0",
capabilities=AgentCapabilities(),
default_input_modes=["text/plain"],
default_output_modes=["text/plain"],
skills=[
AgentSkill(
id="Research",
name="Research",
description="Conduct comprehensive research",
tags=["research", "analysis"],
examples=["Research quantum computing"],
)
],
)
card2 = AgentCard(
name="Writing Agent",
description="An expert writing agent",
url="http://localhost:10002",
version="1.0.0",
capabilities=AgentCapabilities(),
default_input_modes=["text/plain"],
default_output_modes=["text/plain"],
skills=[
AgentSkill(
id="Writing",
name="Writing",
description="Write high-quality content",
tags=["writing", "content"],
examples=["Write a blog post"],
)
],
)
return {
"http://localhost:10001/.well-known/agent-card.json": card1,
"http://localhost:10002/.well-known/agent-card.json": card2,
}
def test_resolve_endpoint_passthrough(sample_agent_configs, sample_agent_cards):
"""Test that endpoint URLs are returned as-is."""
endpoint = "http://localhost:10001/.well-known/agent-card.json"
result = resolve_agent_identifier(endpoint, sample_agent_configs, sample_agent_cards)
assert result == endpoint
def test_resolve_unique_skill_id(sample_agent_configs, sample_agent_cards):
"""Test that a unique skill ID resolves to the correct endpoint."""
result = resolve_agent_identifier("Research", sample_agent_configs, sample_agent_cards)
assert result == "http://localhost:10001/.well-known/agent-card.json"
result = resolve_agent_identifier("Writing", sample_agent_configs, sample_agent_cards)
assert result == "http://localhost:10002/.well-known/agent-card.json"
def test_resolve_unknown_identifier(sample_agent_configs, sample_agent_cards):
"""Test that unknown identifiers raise a descriptive error."""
with pytest.raises(ValueError) as exc_info:
resolve_agent_identifier("UnknownSkill", sample_agent_configs, sample_agent_cards)
error_msg = str(exc_info.value)
assert "Unknown A2A agent identifier 'UnknownSkill'" in error_msg
assert "Available endpoints:" in error_msg
assert "Available skill IDs:" in error_msg
assert "Research" in error_msg
assert "Writing" in error_msg
def test_resolve_ambiguous_skill_id():
"""Test that ambiguous skill IDs raise a descriptive error."""
configs = [
A2AConfig(endpoint="http://localhost:10001/.well-known/agent-card.json"),
A2AConfig(endpoint="http://localhost:10002/.well-known/agent-card.json"),
]
card1 = AgentCard(
name="Research Agent 1",
description="First research agent",
url="http://localhost:10001",
version="1.0.0",
capabilities=AgentCapabilities(),
default_input_modes=["text/plain"],
default_output_modes=["text/plain"],
skills=[
AgentSkill(
id="Research",
name="Research",
description="Conduct research",
tags=["research"],
examples=["Research topic"],
)
],
)
card2 = AgentCard(
name="Research Agent 2",
description="Second research agent",
url="http://localhost:10002",
version="1.0.0",
capabilities=AgentCapabilities(),
default_input_modes=["text/plain"],
default_output_modes=["text/plain"],
skills=[
AgentSkill(
id="Research",
name="Research",
description="Conduct research",
tags=["research"],
examples=["Research topic"],
)
],
)
cards = {
"http://localhost:10001/.well-known/agent-card.json": card1,
"http://localhost:10002/.well-known/agent-card.json": card2,
}
with pytest.raises(ValueError) as exc_info:
resolve_agent_identifier("Research", configs, cards)
error_msg = str(exc_info.value)
assert "Ambiguous skill ID 'Research'" in error_msg
assert "found in multiple agents" in error_msg
assert "http://localhost:10001/.well-known/agent-card.json" in error_msg
assert "http://localhost:10002/.well-known/agent-card.json" in error_msg
assert "Please use the specific endpoint URL to disambiguate" in error_msg
def test_resolve_with_no_skills():
"""Test resolution when agent cards have no skills."""
configs = [
A2AConfig(endpoint="http://localhost:10001/.well-known/agent-card.json"),
]
card = AgentCard(
name="Agent Without Skills",
description="An agent without skills",
url="http://localhost:10001",
version="1.0.0",
capabilities=AgentCapabilities(),
default_input_modes=["text/plain"],
default_output_modes=["text/plain"],
skills=[],
)
cards = {
"http://localhost:10001/.well-known/agent-card.json": card,
}
result = resolve_agent_identifier(
"http://localhost:10001/.well-known/agent-card.json", configs, cards
)
assert result == "http://localhost:10001/.well-known/agent-card.json"
with pytest.raises(ValueError) as exc_info:
resolve_agent_identifier("SomeSkill", configs, cards)
error_msg = str(exc_info.value)
assert "Unknown A2A agent identifier 'SomeSkill'" in error_msg
assert "Available skill IDs: none" in error_msg
def test_resolve_with_multiple_skills_same_card(sample_agent_configs):
"""Test resolution when a card has multiple skills."""
card = AgentCard(
name="Multi-Skill Agent",
description="An agent with multiple skills",
url="http://localhost:10001",
version="1.0.0",
capabilities=AgentCapabilities(),
default_input_modes=["text/plain"],
default_output_modes=["text/plain"],
skills=[
AgentSkill(
id="Research",
name="Research",
description="Conduct research",
tags=["research"],
examples=["Research topic"],
),
AgentSkill(
id="Analysis",
name="Analysis",
description="Analyze data",
tags=["analysis"],
examples=["Analyze data"],
),
],
)
cards = {
"http://localhost:10001/.well-known/agent-card.json": card,
}
result1 = resolve_agent_identifier("Research", sample_agent_configs[:1], cards)
assert result1 == "http://localhost:10001/.well-known/agent-card.json"
result2 = resolve_agent_identifier("Analysis", sample_agent_configs[:1], cards)
assert result2 == "http://localhost:10001/.well-known/agent-card.json"
def test_resolve_empty_agent_cards():
"""Test resolution with empty agent cards dictionary."""
configs = [
A2AConfig(endpoint="http://localhost:10001/.well-known/agent-card.json"),
]
cards = {}
result = resolve_agent_identifier(
"http://localhost:10001/.well-known/agent-card.json", configs, cards
)
assert result == "http://localhost:10001/.well-known/agent-card.json"
with pytest.raises(ValueError) as exc_info:
resolve_agent_identifier("SomeSkill", configs, cards)
error_msg = str(exc_info.value)
assert "Unknown A2A agent identifier 'SomeSkill'" in error_msg

View File

@@ -0,0 +1,287 @@
"""Integration test for A2A skill ID resolution (issue #3897)."""
import pytest
from a2a.types import AgentCapabilities, AgentCard, AgentSkill
from pydantic import BaseModel
from crewai.a2a.config import A2AConfig
from crewai.a2a.utils import (
create_agent_response_model,
extract_agent_identifiers_from_cards,
resolve_agent_identifier,
)
def test_skill_id_resolution_integration():
"""Test the complete flow of skill ID resolution as described in issue #3897.
This test replicates the exact scenario from the bug report:
1. User creates A2A config with endpoint URL
2. Remote agent has AgentCard with skill.id="Research"
3. LLM returns a2a_ids=["Research"] instead of the endpoint URL
4. System should resolve "Research" to the endpoint and proceed successfully
"""
a2a_config = A2AConfig(
endpoint="http://localhost:10001/.well-known/agent-card.json"
)
a2a_agents = [a2a_config]
agent_card = AgentCard(
name="Research Agent",
description="An expert research agent that can conduct thorough research",
url="http://localhost:10001",
version="1.0.0",
capabilities=AgentCapabilities(),
default_input_modes=["text/plain"],
default_output_modes=["text/plain"],
skills=[
AgentSkill(
id="Research",
name="Research",
description="Conduct comprehensive research on any topic",
tags=["research", "analysis", "information-gathering"],
examples=[
"Research the latest developments in quantum computing",
"What are the current trends in renewable energy?",
],
)
],
)
agent_cards = {
"http://localhost:10001/.well-known/agent-card.json": agent_card
}
identifiers = extract_agent_identifiers_from_cards(a2a_agents, agent_cards)
assert "http://localhost:10001/.well-known/agent-card.json" in identifiers
assert "Research" in identifiers
agent_response_model = create_agent_response_model(identifiers)
agent_response_data = {
"a2a_ids": ["Research"], # LLM uses skill ID instead of endpoint
"message": "Please research quantum computing developments",
"is_a2a": True,
}
agent_response = agent_response_model.model_validate(agent_response_data)
assert agent_response.a2a_ids == ("Research",)
assert agent_response.message == "Please research quantum computing developments"
assert agent_response.is_a2a is True
resolved_endpoint = resolve_agent_identifier(
"Research", a2a_agents, agent_cards
)
assert resolved_endpoint == "http://localhost:10001/.well-known/agent-card.json"
resolved_endpoint_direct = resolve_agent_identifier(
"http://localhost:10001/.well-known/agent-card.json",
a2a_agents,
agent_cards,
)
assert resolved_endpoint_direct == "http://localhost:10001/.well-known/agent-card.json"
def test_skill_id_validation_error_before_fix():
"""Test that demonstrates the original bug (for documentation purposes).
Before the fix, creating an AgentResponse model with only endpoints
would cause a validation error when the LLM returned a skill ID.
"""
endpoints_only = ("http://localhost:10001/.well-known/agent-card.json",)
agent_response_model_old = create_agent_response_model(endpoints_only)
agent_response_data = {
"a2a_ids": ["Research"],
"message": "Please research quantum computing",
"is_a2a": True,
}
with pytest.raises(Exception) as exc_info:
agent_response_model_old.model_validate(agent_response_data)
error_msg = str(exc_info.value)
assert "validation error" in error_msg.lower() or "literal" in error_msg.lower()
def test_multiple_agents_with_unique_skill_ids():
"""Test that multiple agents with unique skill IDs work correctly."""
a2a_agents = [
A2AConfig(endpoint="http://localhost:10001/.well-known/agent-card.json"),
A2AConfig(endpoint="http://localhost:10002/.well-known/agent-card.json"),
]
card1 = AgentCard(
name="Research Agent",
description="Research agent",
url="http://localhost:10001",
version="1.0.0",
capabilities=AgentCapabilities(),
default_input_modes=["text/plain"],
default_output_modes=["text/plain"],
skills=[
AgentSkill(
id="Research",
name="Research",
description="Conduct research",
tags=["research"],
)
],
)
card2 = AgentCard(
name="Writing Agent",
description="Writing agent",
url="http://localhost:10002",
version="1.0.0",
capabilities=AgentCapabilities(),
default_input_modes=["text/plain"],
default_output_modes=["text/plain"],
skills=[
AgentSkill(
id="Writing",
name="Writing",
description="Write content",
tags=["writing"],
)
],
)
agent_cards = {
"http://localhost:10001/.well-known/agent-card.json": card1,
"http://localhost:10002/.well-known/agent-card.json": card2,
}
identifiers = extract_agent_identifiers_from_cards(a2a_agents, agent_cards)
assert len(identifiers) == 4
assert "http://localhost:10001/.well-known/agent-card.json" in identifiers
assert "http://localhost:10002/.well-known/agent-card.json" in identifiers
assert "Research" in identifiers
assert "Writing" in identifiers
agent_response_model = create_agent_response_model(identifiers)
response1 = agent_response_model.model_validate({
"a2a_ids": ["Research"],
"message": "Do research",
"is_a2a": True,
})
assert response1.a2a_ids == ("Research",)
response2 = agent_response_model.model_validate({
"a2a_ids": ["Writing"],
"message": "Write content",
"is_a2a": True,
})
assert response2.a2a_ids == ("Writing",)
endpoint1 = resolve_agent_identifier("Research", a2a_agents, agent_cards)
assert endpoint1 == "http://localhost:10001/.well-known/agent-card.json"
endpoint2 = resolve_agent_identifier("Writing", a2a_agents, agent_cards)
assert endpoint2 == "http://localhost:10002/.well-known/agent-card.json"
def test_multi_turn_skill_id_resolution():
"""Test that skill IDs work in multi-turn A2A conversations.
This test verifies the fix in _handle_agent_response_and_continue()
that rebuilds the AgentResponse model with both endpoints and skill IDs
for subsequent turns in multi-turn conversations.
Scenario:
1. First turn: LLM returns skill ID "Research"
2. A2A agent responds
3. Second turn: LLM returns skill ID "Writing" (different agent)
4. Both turns should accept skill IDs without validation errors
"""
a2a_agents = [
A2AConfig(endpoint="http://localhost:10001/.well-known/agent-card.json"),
A2AConfig(endpoint="http://localhost:10002/.well-known/agent-card.json"),
]
card1 = AgentCard(
name="Research Agent",
description="Research agent",
url="http://localhost:10001",
version="1.0.0",
capabilities=AgentCapabilities(),
default_input_modes=["text/plain"],
default_output_modes=["text/plain"],
skills=[
AgentSkill(
id="Research",
name="Research",
description="Conduct research",
tags=["research"],
)
],
)
card2 = AgentCard(
name="Writing Agent",
description="Writing agent",
url="http://localhost:10002",
version="1.0.0",
capabilities=AgentCapabilities(),
default_input_modes=["text/plain"],
default_output_modes=["text/plain"],
skills=[
AgentSkill(
id="Writing",
name="Writing",
description="Write content",
tags=["writing"],
)
],
)
agent_cards_turn1 = {
"http://localhost:10001/.well-known/agent-card.json": card1,
}
identifiers_turn1 = extract_agent_identifiers_from_cards(a2a_agents, agent_cards_turn1)
model_turn1 = create_agent_response_model(identifiers_turn1)
response_turn1 = model_turn1.model_validate({
"a2a_ids": ["Research"],
"message": "Please research quantum computing",
"is_a2a": True,
})
assert response_turn1.a2a_ids == ("Research",)
endpoint_turn1 = resolve_agent_identifier("Research", a2a_agents, agent_cards_turn1)
assert endpoint_turn1 == "http://localhost:10001/.well-known/agent-card.json"
agent_cards_turn2 = {
"http://localhost:10001/.well-known/agent-card.json": card1,
"http://localhost:10002/.well-known/agent-card.json": card2,
}
identifiers_turn2 = extract_agent_identifiers_from_cards(a2a_agents, agent_cards_turn2)
model_turn2 = create_agent_response_model(identifiers_turn2)
assert "Research" in identifiers_turn2
assert "Writing" in identifiers_turn2
response_turn2 = model_turn2.model_validate({
"a2a_ids": ["Writing"],
"message": "Now write a report based on the research",
"is_a2a": True,
})
assert response_turn2.a2a_ids == ("Writing",)
endpoint_turn2 = resolve_agent_identifier("Writing", a2a_agents, agent_cards_turn2)
assert endpoint_turn2 == "http://localhost:10002/.well-known/agent-card.json"
response_turn3 = model_turn2.model_validate({
"a2a_ids": ["Research"],
"message": "Research more details",
"is_a2a": True,
})
assert response_turn3.a2a_ids == ("Research",)
endpoint_turn3 = resolve_agent_identifier("Research", a2a_agents, agent_cards_turn2)
assert endpoint_turn3 == "http://localhost:10001/.well-known/agent-card.json"