Gl/feat/a2a refactor (#3793)

* feat: agent metaclass, refactor a2a to wrappers

* feat: a2a schemas and utils

* chore: move agent class, update imports

* refactor: organize imports to avoid circularity, add a2a to console

* feat: pass response_model through call chain

* feat: add standard openapi spec serialization to tools and structured output

* feat: a2a events

* chore: add a2a to pyproject

* docs: minimal base for learn docs

* fix: adjust a2a conversation flow, allow llm to decide exit until max_retries

* fix: inject agent skills into initial prompt

* fix: format agent card as json in prompt

* refactor: simplify A2A agent prompt formatting and improve skill display

* chore: wide cleanup

* chore: cleanup logic, add auth cache, use json for messages in prompt

* chore: update docs

* fix: doc snippets formatting

* feat: optimize A2A agent card fetching and improve error reporting

* chore: move imports to top of file

* chore: refactor hasattr check

* chore: add httpx-auth, update lockfile

* feat: create base public api

* chore: cleanup modules, add docstrings, types

* fix: exclude extra fields in prompt

* chore: update docs

* tests: update to correct import

* chore: lint for ruff, add missing import

* fix: tweak openai streaming logic for response model

* tests: add reimport for test

* tests: add reimport for test

* fix: don't set a2a attr if not set

* fix: don't set a2a attr if not set

* chore: update cassettes

* tests: fix tests

* fix: use instructor and dont pass response_format for litellm

* chore: consolidate event listeners, add typing

* fix: address race condition in test, update cassettes

* tests: add correct mocks, rerun cassette for json

* tests: update cassette

* chore: regenerate cassette after new run

* fix: make token manager access-safe

* fix: make token manager access-safe

* merge

* chore: update test and cassete for output pydantic

* fix: tweak to disallow deadlock

* chore: linter

* fix: adjust event ordering for threading

* fix: use conditional for batch check

* tests: tweak for emission

* tests: simplify api + event check

* fix: ensure non-function calling llms see json formatted string

* tests: tweak message comparison

* fix: use internal instructor for litellm structure responses

---------

Co-authored-by: Mike Plachta <mike@crewai.com>
This commit is contained in:
Greyson LaLonde
2025-11-01 02:42:03 +01:00
committed by GitHub
parent e229ef4e19
commit e134e5305b
71 changed files with 9790 additions and 4592 deletions

View File

@@ -1,11 +1,13 @@
"""Test Agent creation and execution basic functionality."""
from io import StringIO
import json
import threading
from collections import defaultdict
from concurrent.futures import Future
from hashlib import md5
import re
import sys
from unittest.mock import ANY, MagicMock, call, patch
from crewai.agent import Agent
@@ -2442,37 +2444,51 @@ def test_memory_events_are_emitted():
@crewai_event_bus.on(MemorySaveStartedEvent)
def handle_memory_save_started(source, event):
events["MemorySaveStartedEvent"].append(event)
with condition:
events["MemorySaveStartedEvent"].append(event)
condition.notify_all()
@crewai_event_bus.on(MemorySaveCompletedEvent)
def handle_memory_save_completed(source, event):
events["MemorySaveCompletedEvent"].append(event)
with condition:
events["MemorySaveCompletedEvent"].append(event)
condition.notify_all()
@crewai_event_bus.on(MemorySaveFailedEvent)
def handle_memory_save_failed(source, event):
events["MemorySaveFailedEvent"].append(event)
with condition:
events["MemorySaveFailedEvent"].append(event)
condition.notify_all()
@crewai_event_bus.on(MemoryQueryStartedEvent)
def handle_memory_query_started(source, event):
events["MemoryQueryStartedEvent"].append(event)
with condition:
events["MemoryQueryStartedEvent"].append(event)
condition.notify_all()
@crewai_event_bus.on(MemoryQueryCompletedEvent)
def handle_memory_query_completed(source, event):
events["MemoryQueryCompletedEvent"].append(event)
with condition:
events["MemoryQueryCompletedEvent"].append(event)
condition.notify_all()
@crewai_event_bus.on(MemoryQueryFailedEvent)
def handle_memory_query_failed(source, event):
events["MemoryQueryFailedEvent"].append(event)
with condition:
events["MemoryQueryFailedEvent"].append(event)
condition.notify_all()
@crewai_event_bus.on(MemoryRetrievalStartedEvent)
def handle_memory_retrieval_started(source, event):
events["MemoryRetrievalStartedEvent"].append(event)
with condition:
events["MemoryRetrievalStartedEvent"].append(event)
condition.notify_all()
@crewai_event_bus.on(MemoryRetrievalCompletedEvent)
def handle_memory_retrieval_completed(source, event):
with condition:
events["MemoryRetrievalCompletedEvent"].append(event)
condition.notify()
condition.notify_all()
math_researcher = Agent(
role="Researcher",
@@ -2497,10 +2513,17 @@ def test_memory_events_are_emitted():
with condition:
success = condition.wait_for(
lambda: len(events["MemoryRetrievalCompletedEvent"]) >= 1, timeout=5
lambda: (
len(events["MemorySaveStartedEvent"]) >= 3
and len(events["MemorySaveCompletedEvent"]) >= 3
and len(events["MemoryQueryStartedEvent"]) >= 3
and len(events["MemoryQueryCompletedEvent"]) >= 3
and len(events["MemoryRetrievalCompletedEvent"]) >= 1
),
timeout=10,
)
assert success, "Timeout waiting for memory events"
assert success, f"Timeout waiting for memory events. Got: {dict(events)}"
assert len(events["MemorySaveStartedEvent"]) == 3
assert len(events["MemorySaveCompletedEvent"]) == 3
assert len(events["MemorySaveFailedEvent"]) == 0
@@ -2590,19 +2613,16 @@ def test_long_term_memory_with_memory_flag():
agent=math_researcher,
)
crew = Crew(
agents=[math_researcher],
tasks=[task1],
memory=True,
long_term_memory=LongTermMemory(),
)
with (
patch("crewai.utilities.printer.Printer.print") as mock_print,
patch(
"crewai.memory.long_term.long_term_memory.LongTermMemory.save"
) as save_memory,
patch("crewai.memory.long_term.long_term_memory.LongTermMemory.save") as save_memory,
):
crew = Crew(
agents=[math_researcher],
tasks=[task1],
memory=True,
long_term_memory=LongTermMemory(),
)
crew.kickoff()
mock_print.assert_not_called()
save_memory.assert_called_once()
@@ -2855,7 +2875,7 @@ def test_manager_agent_with_tools_raises_exception(researcher, writer):
@pytest.mark.vcr(filter_headers=["authorization"])
def test_crew_train_success(researcher, writer):
def test_crew_train_success(researcher, writer, monkeypatch):
task = Task(
description="Come up with a list of 5 interesting ideas to explore for an article, then write one amazing paragraph highlight for each idea that showcases how good an article about this topic could be. Return the list of ideas with their paragraph and your notes.",
expected_output="5 bullet points with a paragraph for each idea.",
@@ -2885,10 +2905,13 @@ def test_crew_train_success(researcher, writer):
condition.notify()
# Mock human input to avoid blocking during training
with patch("builtins.input", return_value="Great work!"):
crew.train(
n_iterations=2, inputs={"topic": "AI"}, filename="trained_agents_data.pkl"
)
# Use StringIO to simulate user input for multiple calls to input()
mock_inputs = StringIO("Great work!\n" * 10) # Provide enough inputs for all iterations
monkeypatch.setattr("sys.stdin", mock_inputs)
crew.train(
n_iterations=2, inputs={"topic": "AI"}, filename="trained_agents_data.pkl"
)
with condition:
success = condition.wait_for(lambda: len(received_events) == 2, timeout=5)