mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-09-11 05:39:39 +00:00
Merge branch 'main' into fix/native-tool-call-responses-api-shape
Resolve pyproject.toml conflicts by keeping main's stricter aiohttp>=3.14.3 pin and cryptography advisory comments. Co-authored-by: Rip&Tear <theCyberTech@users.noreply.github.com>
This commit is contained in:
@@ -8,7 +8,7 @@ authors = [
|
||||
]
|
||||
requires-python = ">=3.10, <3.14"
|
||||
dependencies = [
|
||||
"crewai-core==1.15.10",
|
||||
"crewai-core==1.15.11",
|
||||
"click>=8.1.7,<9",
|
||||
"pydantic>=2.11.9,<2.13",
|
||||
"pydantic-settings>=2.14.2,<3",
|
||||
|
||||
@@ -1 +1 @@
|
||||
__version__ = "1.15.10"
|
||||
__version__ = "1.15.11"
|
||||
|
||||
@@ -1 +1 @@
|
||||
__version__ = "1.15.10"
|
||||
__version__ = "1.15.11"
|
||||
|
||||
@@ -152,4 +152,4 @@ __all__ = [
|
||||
"wrap_file_source",
|
||||
]
|
||||
|
||||
__version__ = "1.15.10"
|
||||
__version__ = "1.15.11"
|
||||
|
||||
@@ -10,7 +10,7 @@ requires-python = ">=3.10, <3.14"
|
||||
dependencies = [
|
||||
"pytube~=15.0.0",
|
||||
"requests>=2.33.0,<3",
|
||||
"crewai==1.15.10",
|
||||
"crewai==1.15.11",
|
||||
"tiktoken>=0.8.0,<0.13",
|
||||
"beautifulsoup4~=4.13.4",
|
||||
"python-docx~=1.2.0",
|
||||
|
||||
@@ -338,4 +338,4 @@ __all__ = [
|
||||
"ZapierActionTools",
|
||||
]
|
||||
|
||||
__version__ = "1.15.10"
|
||||
__version__ = "1.15.11"
|
||||
|
||||
@@ -135,7 +135,8 @@ class DataTypes:
|
||||
|
||||
if "docs" in url.netloc or ("docs" in url.path and url.scheme != "file"):
|
||||
return DataType.DOCS_SITE
|
||||
if "github.com" in url.netloc:
|
||||
hostname = (url.hostname or "").lower()
|
||||
if hostname == "github.com" or hostname.endswith(".github.com"):
|
||||
return DataType.GITHUB
|
||||
|
||||
return DataType.WEBSITE
|
||||
|
||||
32
lib/crewai-tools/tests/rag/test_data_types.py
Normal file
32
lib/crewai-tools/tests/rag/test_data_types.py
Normal file
@@ -0,0 +1,32 @@
|
||||
"""Tests for DataTypes content classification."""
|
||||
|
||||
from crewai_tools.rag.data_types import DataType, DataTypes
|
||||
|
||||
|
||||
class TestDataTypesFromContentGitHub:
|
||||
"""GitHub URL detection must use hostname matching, not substrings."""
|
||||
|
||||
def test_github_com_url(self) -> None:
|
||||
assert (
|
||||
DataTypes.from_content("https://github.com/crewai/crewai")
|
||||
== DataType.GITHUB
|
||||
)
|
||||
|
||||
def test_github_subdomain_url(self) -> None:
|
||||
assert (
|
||||
DataTypes.from_content("https://gist.github.com/user/abc")
|
||||
== DataType.GITHUB
|
||||
)
|
||||
|
||||
def test_spoofed_github_hostname_is_website(self) -> None:
|
||||
# Substring checks like `"github.com" in netloc` would misclassify this.
|
||||
assert (
|
||||
DataTypes.from_content("https://github.com.evil.example/crewai")
|
||||
== DataType.WEBSITE
|
||||
)
|
||||
|
||||
def test_github_in_path_is_not_github(self) -> None:
|
||||
assert (
|
||||
DataTypes.from_content("https://example.com/github.com/repo")
|
||||
== DataType.WEBSITE
|
||||
)
|
||||
@@ -163,8 +163,14 @@ def test_navigate_command(mock_run, stagehand_tool):
|
||||
command_type="navigate",
|
||||
)
|
||||
|
||||
# Assertions
|
||||
assert "https://example.com" in result
|
||||
# Assertions — compare the full mocked result (avoid URL substring checks)
|
||||
assert result == "Successfully navigated to https://example.com"
|
||||
mock_run.assert_called_once_with(
|
||||
stagehand_tool,
|
||||
instruction="Go to example.com",
|
||||
url="https://example.com",
|
||||
command_type="navigate",
|
||||
)
|
||||
|
||||
|
||||
@patch(
|
||||
|
||||
@@ -8,8 +8,8 @@ authors = [
|
||||
]
|
||||
requires-python = ">=3.10, <3.14"
|
||||
dependencies = [
|
||||
"crewai-core==1.15.10",
|
||||
"crewai-cli==1.15.10",
|
||||
"crewai-core==1.15.11",
|
||||
"crewai-cli==1.15.11",
|
||||
# Core Dependencies
|
||||
"pydantic>=2.11.9,<2.13",
|
||||
"openai>=2.30.0,<3",
|
||||
@@ -55,7 +55,7 @@ Repository = "https://github.com/crewAIInc/crewAI"
|
||||
|
||||
[project.optional-dependencies]
|
||||
tools = [
|
||||
"crewai-tools==1.15.10",
|
||||
"crewai-tools==1.15.11",
|
||||
]
|
||||
embeddings = [
|
||||
"tiktoken>=0.8.0,<0.13"
|
||||
|
||||
@@ -48,7 +48,7 @@ def _suppress_pydantic_deprecation_warnings() -> None:
|
||||
|
||||
_suppress_pydantic_deprecation_warnings()
|
||||
|
||||
__version__ = "1.15.10"
|
||||
__version__ = "1.15.11"
|
||||
|
||||
_LAZY_IMPORTS: dict[str, tuple[str, str]] = {
|
||||
"Memory": ("crewai.memory.unified_memory", "Memory"),
|
||||
|
||||
@@ -54,6 +54,7 @@ from crewai.events.types.flow_events import (
|
||||
MethodExecutionPausedEvent,
|
||||
MethodExecutionStartedEvent,
|
||||
)
|
||||
from crewai.events.types.hook_events import HookDispatchedEvent
|
||||
from crewai.events.types.knowledge_events import (
|
||||
KnowledgeQueryCompletedEvent,
|
||||
KnowledgeQueryFailedEvent,
|
||||
@@ -875,5 +876,12 @@ class EventListener(BaseEventListener):
|
||||
if has_hooks:
|
||||
self._telemetry.feature_usage_span("hooks:registered")
|
||||
|
||||
@crewai_event_bus.on(HookDispatchedEvent)
|
||||
def on_hook_dispatched(_: Any, event: HookDispatchedEvent) -> None:
|
||||
self._telemetry.hook_dispatched_span(
|
||||
interception_point=event.interception_point,
|
||||
outcome=event.outcome,
|
||||
)
|
||||
|
||||
|
||||
event_listener = EventListener()
|
||||
|
||||
@@ -1148,7 +1148,8 @@ class Telemetry:
|
||||
|
||||
Args:
|
||||
feature: Feature identifier, e.g. "planning:creation",
|
||||
"mcp:connection", "a2a:delegation".
|
||||
"mcp:connection", "a2a:delegation",
|
||||
"hooks:pre_tool_call", "hooks:aborted".
|
||||
"""
|
||||
|
||||
def _operation() -> None:
|
||||
@@ -1160,6 +1161,21 @@ class Telemetry:
|
||||
|
||||
self._safe_telemetry_operation(_operation)
|
||||
|
||||
def hook_dispatched_span(
|
||||
self,
|
||||
interception_point: str,
|
||||
outcome: str,
|
||||
) -> None:
|
||||
"""Records an interception-hook dispatch via Feature Usage.
|
||||
|
||||
Emits ``hooks:<point>`` on every dispatch, plus ``hooks:aborted`` when
|
||||
a hook aborted the operation (e.g. a policy check). No reasons,
|
||||
payloads, or other user content are recorded.
|
||||
"""
|
||||
self.feature_usage_span(f"hooks:{interception_point}")
|
||||
if outcome == "aborted":
|
||||
self.feature_usage_span("hooks:aborted")
|
||||
|
||||
def coding_agent_span(self) -> None:
|
||||
"""Records which AI coding assistant (if any) is running this process.
|
||||
|
||||
|
||||
@@ -38,18 +38,33 @@ def get_temperature_tool_schema() -> dict[str, Any]:
|
||||
|
||||
@pytest.fixture
|
||||
def mock_emit() -> MagicMock:
|
||||
"""Mock the event bus emit function."""
|
||||
from crewai.events.event_bus import CrewAIEventsBus
|
||||
"""Mock the singleton event bus emit used by LLM providers.
|
||||
|
||||
with patch.object(CrewAIEventsBus, "emit") as mock:
|
||||
yield mock
|
||||
Patch the singleton instance (not only the class) so a leftover
|
||||
instance-level ``emit`` from other tests cannot shadow the mock.
|
||||
"""
|
||||
from crewai.events.event_bus import CrewAIEventsBus, crewai_event_bus
|
||||
|
||||
with (
|
||||
patch.object(CrewAIEventsBus, "emit") as class_mock,
|
||||
patch.object(crewai_event_bus, "emit", new=class_mock),
|
||||
):
|
||||
yield class_mock
|
||||
|
||||
|
||||
def _event_from_emit_call(call: Any) -> Any:
|
||||
"""Return the event argument from an emit mock call."""
|
||||
event = call.kwargs.get("event")
|
||||
if event is None and len(call.args) >= 2:
|
||||
event = call.args[1]
|
||||
return event
|
||||
|
||||
|
||||
def get_tool_call_events(mock_emit: MagicMock) -> list[LLMStreamChunkEvent]:
|
||||
"""Extract tool call streaming events from mock emit calls."""
|
||||
tool_call_events = []
|
||||
for call in mock_emit.call_args_list:
|
||||
event = call[1].get("event") if len(call) > 1 else None
|
||||
event = _event_from_emit_call(call)
|
||||
if isinstance(event, LLMStreamChunkEvent) and event.call_type == LLMCallType.TOOL_CALL:
|
||||
tool_call_events.append(event)
|
||||
return tool_call_events
|
||||
@@ -59,7 +74,7 @@ def get_all_stream_events(mock_emit: MagicMock) -> list[LLMStreamChunkEvent]:
|
||||
"""Extract all streaming events from mock emit calls."""
|
||||
stream_events = []
|
||||
for call in mock_emit.call_args_list:
|
||||
event = call[1].get("event") if len(call) > 1 else None
|
||||
event = _event_from_emit_call(call)
|
||||
if isinstance(event, LLMStreamChunkEvent):
|
||||
stream_events.append(event)
|
||||
return stream_events
|
||||
|
||||
@@ -230,3 +230,73 @@ def test_no_signal_handler_traceback_in_non_main_thread():
|
||||
mock_holder["logger"].debug.assert_any_call(
|
||||
"Skipping signal handler registration: not running in main thread"
|
||||
)
|
||||
|
||||
|
||||
def test_hook_dispatched_span_counts_point_usage():
|
||||
with (
|
||||
patch.dict(
|
||||
os.environ,
|
||||
{
|
||||
"CREWAI_DISABLE_TELEMETRY": "false",
|
||||
"CREWAI_DISABLE_TRACKING": "false",
|
||||
"OTEL_SDK_DISABLED": "false",
|
||||
},
|
||||
),
|
||||
patch("crewai.telemetry.telemetry.TracerProvider"),
|
||||
):
|
||||
telemetry = Telemetry()
|
||||
with patch.object(telemetry, "feature_usage_span") as feature_usage_span:
|
||||
telemetry.hook_dispatched_span("pre_tool_call", "proceeded")
|
||||
|
||||
feature_usage_span.assert_called_once_with("hooks:pre_tool_call")
|
||||
|
||||
|
||||
def test_hook_dispatched_span_counts_aborts():
|
||||
with (
|
||||
patch.dict(
|
||||
os.environ,
|
||||
{
|
||||
"CREWAI_DISABLE_TELEMETRY": "false",
|
||||
"CREWAI_DISABLE_TRACKING": "false",
|
||||
"OTEL_SDK_DISABLED": "false",
|
||||
},
|
||||
),
|
||||
patch("crewai.telemetry.telemetry.TracerProvider"),
|
||||
):
|
||||
telemetry = Telemetry()
|
||||
with patch.object(telemetry, "feature_usage_span") as feature_usage_span:
|
||||
telemetry.hook_dispatched_span("pre_tool_call", "aborted")
|
||||
|
||||
feature_usage_span.assert_any_call("hooks:pre_tool_call")
|
||||
feature_usage_span.assert_any_call("hooks:aborted")
|
||||
assert feature_usage_span.call_count == 2
|
||||
|
||||
|
||||
def test_event_listener_tracks_hook_dispatched_events():
|
||||
from crewai.events.event_bus import crewai_event_bus
|
||||
from crewai.events.event_listener import event_listener
|
||||
from crewai.events.types.hook_events import HookDispatchedEvent
|
||||
|
||||
with (
|
||||
crewai_event_bus.scoped_handlers(),
|
||||
patch.object(
|
||||
event_listener._telemetry,
|
||||
"hook_dispatched_span",
|
||||
) as hook_dispatched_span,
|
||||
):
|
||||
event_listener.setup_listeners(crewai_event_bus)
|
||||
crewai_event_bus.emit(
|
||||
"test",
|
||||
HookDispatchedEvent(
|
||||
interception_point="pre_tool_call",
|
||||
outcome="aborted",
|
||||
hook_count=1,
|
||||
duration_ms=1.5,
|
||||
),
|
||||
)
|
||||
crewai_event_bus.flush()
|
||||
|
||||
hook_dispatched_span.assert_called_once_with(
|
||||
interception_point="pre_tool_call",
|
||||
outcome="aborted",
|
||||
)
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
"""CrewAI development tools."""
|
||||
|
||||
__version__ = "1.15.10"
|
||||
__version__ = "1.15.11"
|
||||
|
||||
Reference in New Issue
Block a user