mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-07-06 15:39:24 +00:00
Fix send_message to use SendMessageRequest wrapper, fix ServerError call
- Add make_send_request() helper in _compat.py for v1.0 API - Update all handlers to wrap Message in SendMessageRequest - Fix ServerError(error=...) → ServerError(message) in task.py - Fix MessageToDict parameter name (always_print_fields_with_no_presence) - Update integration tests for v1.0 client API (A2ACardResolver, ClientFactory) - Fix test mocks to use real protobuf Message instead of MagicMock Co-Authored-By: João <joao@crewai.com>
This commit is contained in:
@@ -145,6 +145,17 @@ def new_text_message(
|
||||
)
|
||||
|
||||
|
||||
def make_send_request(message: Message) -> "SendMessageRequest":
|
||||
"""Wrap a Message in a SendMessageRequest (v1.0 API).
|
||||
|
||||
In v0.3, ``client.send_message(message)`` accepted a bare ``Message``.
|
||||
In v1.0, it expects ``SendMessageRequest(message=message)``.
|
||||
"""
|
||||
from a2a.types import SendMessageRequest
|
||||
|
||||
return SendMessageRequest(message=message)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# AgentCard field access helpers
|
||||
# v0.3: agent_card.url, agent_card.preferred_transport, agent_card.additional_interfaces
|
||||
|
||||
@@ -163,9 +163,11 @@ class PollingHandler:
|
||||
from_task = kwargs.get("from_task")
|
||||
from_agent = kwargs.get("from_agent")
|
||||
|
||||
from crewai.a2a._compat import make_send_request
|
||||
|
||||
try:
|
||||
result_or_task_id = await send_message_and_get_task_id(
|
||||
event_stream=client.send_message(message),
|
||||
event_stream=client.send_message(make_send_request(message)),
|
||||
new_messages=new_messages,
|
||||
agent_card=agent_card,
|
||||
turn_number=turn_number,
|
||||
|
||||
@@ -255,9 +255,11 @@ class PushNotificationHandler:
|
||||
history=new_messages,
|
||||
)
|
||||
|
||||
from crewai.a2a._compat import make_send_request
|
||||
|
||||
try:
|
||||
result_or_task_id = await send_message_and_get_task_id(
|
||||
event_stream=client.send_message(message),
|
||||
event_stream=client.send_message(make_send_request(message)),
|
||||
new_messages=new_messages,
|
||||
agent_card=agent_card,
|
||||
turn_number=params.turn_number,
|
||||
|
||||
@@ -278,7 +278,9 @@ class StreamingHandler:
|
||||
|
||||
result_parts: list[str] = []
|
||||
final_result: TaskStateResult | None = None
|
||||
event_stream = client.send_message(message)
|
||||
from crewai.a2a._compat import make_send_request
|
||||
|
||||
event_stream = client.send_message(make_send_request(message))
|
||||
chunk_index = 0
|
||||
current_task_id: str | None = task_id
|
||||
current_task: Task | None = None
|
||||
|
||||
@@ -458,9 +458,7 @@ async def _execute_impl(
|
||||
from_agent=agent,
|
||||
),
|
||||
)
|
||||
raise ServerError(
|
||||
error=InternalError(message=f"Task execution failed: {e}")
|
||||
) from e
|
||||
raise ServerError(f"Task execution failed: {e}") from e
|
||||
|
||||
|
||||
async def execute_with_extensions(
|
||||
|
||||
@@ -3,10 +3,11 @@ from __future__ import annotations
|
||||
import os
|
||||
import uuid
|
||||
|
||||
import httpx
|
||||
import pytest
|
||||
import pytest_asyncio
|
||||
|
||||
from a2a.client import ClientFactory
|
||||
from a2a.client import A2ACardResolver, ClientFactory
|
||||
from a2a.types import AgentCapabilities, AgentCard, AgentInterface, Message, Part, Role, TaskState
|
||||
|
||||
from crewai.a2a._compat import (
|
||||
@@ -15,6 +16,7 @@ from crewai.a2a._compat import (
|
||||
TASK_STATE_COMPLETED,
|
||||
TASK_STATE_FAILED,
|
||||
agent_card_url,
|
||||
make_send_request,
|
||||
new_text_message,
|
||||
new_text_part,
|
||||
)
|
||||
@@ -26,11 +28,25 @@ A2A_TEST_ENDPOINT = os.getenv("A2A_TEST_ENDPOINT", "http://localhost:9999")
|
||||
|
||||
|
||||
@pytest_asyncio.fixture
|
||||
async def a2a_client():
|
||||
async def card_resolver():
|
||||
"""Create an A2ACardResolver for the test server."""
|
||||
async with httpx.AsyncClient() as http_client:
|
||||
resolver = A2ACardResolver(http_client, A2A_TEST_ENDPOINT)
|
||||
yield resolver
|
||||
|
||||
|
||||
@pytest_asyncio.fixture
|
||||
async def agent_card(card_resolver) -> AgentCard:
|
||||
"""Fetch the real agent card from the server."""
|
||||
return await card_resolver.get_agent_card()
|
||||
|
||||
|
||||
@pytest_asyncio.fixture
|
||||
async def a2a_client(agent_card):
|
||||
"""Create A2A client for test server."""
|
||||
client = await ClientFactory.connect(A2A_TEST_ENDPOINT)
|
||||
factory = ClientFactory()
|
||||
client = factory.create(agent_card)
|
||||
yield client
|
||||
await client.close()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -39,20 +55,14 @@ def test_message() -> Message:
|
||||
return new_text_message("What is 2 + 2?", role=ROLE_USER)
|
||||
|
||||
|
||||
@pytest_asyncio.fixture
|
||||
async def agent_card(a2a_client) -> AgentCard:
|
||||
"""Fetch the real agent card from the server."""
|
||||
return await a2a_client.get_card()
|
||||
|
||||
|
||||
class TestA2AAgentCardFetching:
|
||||
"""Integration tests for agent card fetching."""
|
||||
|
||||
@pytest.mark.vcr()
|
||||
@pytest.mark.asyncio
|
||||
async def test_fetch_agent_card(self, a2a_client) -> None:
|
||||
async def test_fetch_agent_card(self, card_resolver) -> None:
|
||||
"""Test fetching an agent card from the server."""
|
||||
card = await a2a_client.get_card()
|
||||
card = await card_resolver.get_agent_card()
|
||||
|
||||
assert card is not None
|
||||
assert card.name == "GPT Assistant"
|
||||
@@ -133,7 +143,7 @@ class TestA2ATaskOperations:
|
||||
from crewai.a2a._compat import is_stream_task
|
||||
|
||||
final_task: Task | None = None
|
||||
async for event in a2a_client.send_message(test_message):
|
||||
async for event in a2a_client.send_message(make_send_request(test_message)):
|
||||
if isinstance(event, StreamResponse) and is_stream_task(event):
|
||||
final_task = event.task
|
||||
|
||||
|
||||
@@ -168,7 +168,8 @@ class TestToAgentCard:
|
||||
|
||||
card = agent.to_agent_card("http://fallback-url.com")
|
||||
|
||||
assert agent_card_url(card) == "http://configured-url.com"
|
||||
url = agent_card_url(card)
|
||||
assert url.rstrip("/") == "http://configured-url.com"
|
||||
|
||||
def test_generates_default_skill(self) -> None:
|
||||
"""AgentCard should have at least one skill based on agent role."""
|
||||
@@ -309,8 +310,8 @@ class TestAgentCardJsonStructure:
|
||||
assert "Test Agent" in json_str
|
||||
assert "http://localhost:8000" in json_str
|
||||
|
||||
def test_json_excludes_none_values(self) -> None:
|
||||
"""AgentCard JSON with exclude_none should omit None/default fields."""
|
||||
def test_json_excludes_unset_fields(self) -> None:
|
||||
"""AgentCard JSON should omit fields that were not explicitly set."""
|
||||
agent = Agent(
|
||||
role="Test Agent",
|
||||
goal="Test goal",
|
||||
@@ -322,5 +323,3 @@ class TestAgentCardJsonStructure:
|
||||
json_data = agent_card_to_dict(card)
|
||||
|
||||
assert "provider" not in json_data
|
||||
assert "documentation_url" not in json_data
|
||||
assert "icon_url" not in json_data
|
||||
|
||||
@@ -39,12 +39,13 @@ def mock_task(mock_context: MagicMock) -> MagicMock:
|
||||
@pytest.fixture
|
||||
def mock_context() -> MagicMock:
|
||||
"""Create a mock RequestContext."""
|
||||
from crewai.a2a._compat import ROLE_USER, new_text_message
|
||||
|
||||
context = MagicMock(spec=RequestContext)
|
||||
context.task_id = "test-task-123"
|
||||
context.context_id = "test-context-456"
|
||||
context.get_user_input.return_value = "Test user message"
|
||||
context.message = MagicMock(spec=Message)
|
||||
context.message.parts = []
|
||||
context.message = new_text_message("Test user message", role=ROLE_USER)
|
||||
context.current_task = None
|
||||
return context
|
||||
|
||||
|
||||
Reference in New Issue
Block a user