Compare commits

..

5 Commits

Author SHA1 Message Date
Devin AI
e18477235b style: fix ruff formatting in browser_toolkit.py
Co-Authored-By: João <joao@crewai.com>
2026-03-26 14:00:52 +00:00
Devin AI
a4890e5626 fix: add Python 3.14 compatibility by replacing asyncio.get_event_loop() with get_running_loop()
Python 3.14 changed asyncio.get_event_loop() to raise RuntimeError when
no running event loop exists instead of silently creating one. This broke
all async code paths that relied on get_event_loop().

Changes:
- Replace asyncio.get_event_loop() with asyncio.get_running_loop() in all
  async contexts across crewai core and crewai-tools
- Update requires-python from '<3.14' to '<3.15' in all pyproject.toml files
- Add comprehensive tests for Python 3.14 async compatibility
- Regenerate uv.lock for the updated version constraint

Closes #5109

Co-Authored-By: João <joao@crewai.com>
2026-03-26 13:57:11 +00:00
João Moura
6193e082e1 docs: update changelog and version for v1.12.2 (#5103)
Some checks failed
CodeQL Advanced / Analyze (actions) (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Check Documentation Broken Links / Check broken links (push) Has been cancelled
2026-03-26 03:54:26 -03:00
João Moura
33f33c6fcc feat: bump versions to 1.12.2 (#5101) 2026-03-26 03:33:10 -03:00
alex-clawd
74976b157d fix: preserve method return value as flow output for @human_feedback with emit (#5099)
* fix: preserve method return value as flow output for @human_feedback with emit

When a @human_feedback decorated method with emit= is the final method in a
flow (no downstream listeners triggered), the flow's final output was
incorrectly set to the collapsed outcome string (e.g., 'approved') instead
of the method's actual return value (e.g., a state dict).

Root cause: _process_feedback() returns the collapsed_outcome string when
emit is set, and this string was being stored as the method's result in
_method_outputs.

The fix:
1. In human_feedback.py: After _process_feedback, stash the real method_output
   on the flow instance as _human_feedback_method_output when emit is set.

2. In flow.py: After appending a method result to _method_outputs, check if
   _human_feedback_method_output is set. If so, replace the last entry with
   the stashed real output and clear the stash.

This ensures:
- Routing still works correctly (collapsed outcome used for @listen matching)
- The flow's final result is the actual method return value
- If downstream listeners execute, their results become the final output

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* style: ruff format flow.py

* fix: use per-method dict stash for concurrency safety and None returns

Addresses review comments:
- Replace single flow-level slot with dict keyed by method name,
  safe under concurrent @human_feedback+emit execution
- Dict key presence (not value) indicates stashed output,
  correctly preserving None return values
- Added test for None return value preservation

---------

Co-authored-by: Joao Moura <joao@crewai.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-03-26 03:28:17 -03:00
31 changed files with 6883 additions and 4590 deletions

View File

@@ -4,6 +4,29 @@ description: "تحديثات المنتج والتحسينات وإصلاحات
icon: "clock"
mode: "wide"
---
<Update label="25 مارس 2026">
## v1.12.2
[عرض الإصدار على GitHub](https://github.com/crewAIInc/crewAI/releases/tag/1.12.2)
## ما الذي تغير
### الميزات
- إضافة مرحلة إصدار المؤسسات إلى إصدار أدوات المطورين
### إصلاحات الأخطاء
- الحفاظ على قيمة إرجاع الطريقة كإخراج تدفق لـ @human_feedback مع emit
### الوثائق
- تحديث سجل التغييرات والإصدار لـ v1.12.1
- مراجعة سياسة الأمان وتعليمات الإبلاغ
## المساهمون
@alex-clawd, @greysonlalonde, @joaomdmoura, @theCyberTech
</Update>
<Update label="25 مارس 2026">
## v1.12.1

File diff suppressed because it is too large Load Diff

View File

@@ -4,6 +4,29 @@ description: "Product updates, improvements, and bug fixes for CrewAI"
icon: "clock"
mode: "wide"
---
<Update label="Mar 25, 2026">
## v1.12.2
[View release on GitHub](https://github.com/crewAIInc/crewAI/releases/tag/1.12.2)
## What's Changed
### Features
- Add enterprise release phase to devtools release
### Bug Fixes
- Preserve method return value as flow output for @human_feedback with emit
### Documentation
- Update changelog and version for v1.12.1
- Revise security policy and reporting instructions
## Contributors
@alex-clawd, @greysonlalonde, @joaomdmoura, @theCyberTech
</Update>
<Update label="Mar 25, 2026">
## v1.12.1

View File

@@ -134,29 +134,6 @@ result = flow.kickoff(
)
```
You can also define file types directly in your flow state for structured file handling:
```python
from pydantic import BaseModel
from crewai.flow.flow import Flow, start
from crewai_files import ImageFile, PDFFile
class DocumentState(BaseModel):
document: PDFFile
cover_image: ImageFile
title: str = ""
class DocumentFlow(Flow[DocumentState]):
@start()
def process(self):
content = self.state.document.read()
return {"processed": True}
```
<Note type="info" title="CrewAI Platform Integration">
When deploying flows to the CrewAI Platform (AMP), file fields in your state automatically render as file upload dropzones in the UI. For API usage, you can pass URL strings directly and Pydantic coerces them to file objects automatically. See [Flows - File Inputs](/en/concepts/flows#file-inputs) for details.
</Note>
### With Standalone Agents
Pass files directly to agent kickoff:

View File

@@ -341,69 +341,6 @@ flow.kickoff()
By providing both unstructured and structured state management options, CrewAI Flows empowers developers to build AI workflows that are both flexible and robust, catering to a wide range of application requirements.
## File Inputs
Flows support file inputs through the `crewai-files` package, enabling you to build workflows that process images, PDFs, and other file types. When you use file types like `ImageFile` or `PDFFile` in your flow state, they integrate seamlessly with both local development and the CrewAI Platform.
<Note type="info" title="Optional Dependency">
File support requires the optional `crewai-files` package. Install it with:
```bash
uv add 'crewai[file-processing]'
```
</Note>
### Using File Types in Flow State
You can include file types directly in your structured flow state:
```python
from pydantic import BaseModel
from crewai.flow.flow import Flow, start
from crewai_files import ImageFile, PDFFile
class DocumentProcessingState(BaseModel):
document: PDFFile # Renders as file upload in CrewAI Platform
cover_image: ImageFile # Renders as image upload
title: str = "" # Renders as text input
class DocumentFlow(Flow[DocumentProcessingState]):
@start()
def process_document(self):
# Access the file - works with URLs, paths, or uploaded files
content = self.state.document.read()
# Or pass to an agent with VisionTool, etc.
return {"processed": True}
```
### CrewAI Platform Integration
When you deploy a flow to the CrewAI Platform (AMP), file fields in your state automatically render as file upload dropzones in the UI. This makes it easy to build user-facing applications that accept file uploads without any additional frontend work.
| State Field Type | Platform UI Rendering |
|:-----------------|:----------------------|
| `ImageFile` | Image upload dropzone |
| `PDFFile` | PDF upload dropzone |
| `AudioFile` | Audio upload dropzone |
| `VideoFile` | Video upload dropzone |
| `TextFile` | Text file upload dropzone |
| `str`, `int`, etc. | Standard form inputs |
### API Usage
When calling your flow via API, you can pass URL strings directly for file fields. Pydantic automatically coerces URLs into the appropriate file type:
```python
# API request body - URLs are automatically converted to file objects
{
"document": "https://example.com/report.pdf",
"cover_image": "https://example.com/cover.png",
"title": "Q4 Report"
}
```
For more details on file types, sources, and provider support, see the [Files documentation](/en/concepts/files).
## Flow Persistence
The @persist decorator enables automatic state persistence in CrewAI Flows, allowing you to maintain flow state across restarts or different workflow executions. This decorator can be applied at either the class level or method level, providing flexibility in how you manage state persistence.

View File

@@ -4,6 +4,29 @@ description: "CrewAI의 제품 업데이트, 개선 사항 및 버그 수정"
icon: "clock"
mode: "wide"
---
<Update label="2026년 3월 25일">
## v1.12.2
[GitHub 릴리스 보기](https://github.com/crewAIInc/crewAI/releases/tag/1.12.2)
## 변경 사항
### 기능
- devtools 릴리스에 기업 릴리스 단계 추가
### 버그 수정
- @human_feedback과 함께 emit을 사용할 때 메서드 반환 값을 흐름 출력으로 유지
### 문서
- v1.12.1에 대한 변경 로그 및 버전 업데이트
- 보안 정책 및 보고 지침 수정
## 기여자
@alex-clawd, @greysonlalonde, @joaomdmoura, @theCyberTech
</Update>
<Update label="2026년 3월 25일">
## v1.12.1

View File

@@ -4,6 +4,29 @@ description: "Atualizações de produto, melhorias e correções do CrewAI"
icon: "clock"
mode: "wide"
---
<Update label="25 mar 2026">
## v1.12.2
[Ver release no GitHub](https://github.com/crewAIInc/crewAI/releases/tag/1.12.2)
## O que Mudou
### Recursos
- Adicionar fase de lançamento empresarial ao lançamento do devtools
### Correções de Bugs
- Preservar o valor de retorno do método como saída de fluxo para @human_feedback com emit
### Documentação
- Atualizar changelog e versão para v1.12.1
- Revisar política de segurança e instruções de relatório
## Contributors
@alex-clawd, @greysonlalonde, @joaomdmoura, @theCyberTech
</Update>
<Update label="25 mar 2026">
## v1.12.1

View File

@@ -6,7 +6,7 @@ readme = "README.md"
authors = [
{ name = "Greyson LaLonde", email = "greyson@crewai.com" }
]
requires-python = ">=3.10, <3.14"
requires-python = ">=3.10, <3.15"
dependencies = [
"Pillow~=12.1.1",
"pypdf~=6.9.1",

View File

@@ -152,4 +152,4 @@ __all__ = [
"wrap_file_source",
]
__version__ = "1.12.1"
__version__ = "1.12.2"

View File

@@ -6,12 +6,12 @@ readme = "README.md"
authors = [
{ name = "João Moura", email = "joaomdmoura@gmail.com" },
]
requires-python = ">=3.10, <3.14"
requires-python = ">=3.10, <3.15"
dependencies = [
"pytube~=15.0.0",
"requests~=2.32.5",
"docker~=7.1.0",
"crewai==1.12.1",
"crewai==1.12.2",
"tiktoken~=0.8.0",
"beautifulsoup4~=4.13.4",
"python-docx~=1.2.0",

View File

@@ -309,4 +309,4 @@ __all__ = [
"ZapierActionTools",
]
__version__ = "1.12.1"
__version__ = "1.12.2"

View File

@@ -47,7 +47,7 @@ class BrowserSessionManager:
Returns:
An async browser instance specific to the thread
"""
loop = asyncio.get_event_loop()
loop = asyncio.get_running_loop()
while True:
with self._lock:
if thread_id in self._async_sessions:

View File

@@ -94,11 +94,9 @@ class BrowserBaseTool(BaseTool):
try:
import nest_asyncio # type: ignore[import-untyped]
loop = asyncio.get_event_loop()
loop = asyncio.get_running_loop()
nest_asyncio.apply(loop)
result: str = asyncio.get_event_loop().run_until_complete(
self._arun(*args, **kwargs)
)
result: str = loop.run_until_complete(self._arun(*args, **kwargs))
return result
except Exception as e:
return f"Error in patched _run: {e!s}"
@@ -118,7 +116,7 @@ class BrowserBaseTool(BaseTool):
def _is_in_asyncio_loop(self) -> bool:
"""Check if we're currently in an asyncio event loop."""
try:
loop = asyncio.get_event_loop()
loop = asyncio.get_running_loop()
return loop.is_running()
except RuntimeError:
return False
@@ -544,14 +542,13 @@ class BrowserToolkit:
def _nest_current_loop(self) -> None:
"""Apply nest_asyncio if we're in an asyncio loop."""
try:
loop = asyncio.get_event_loop()
if loop.is_running():
try:
import nest_asyncio
loop = asyncio.get_running_loop()
try:
import nest_asyncio
nest_asyncio.apply(loop)
except Exception as e:
logger.warning(f"Failed to apply nest_asyncio: {e!s}")
nest_asyncio.apply(loop)
except Exception as e:
logger.warning(f"Failed to apply nest_asyncio: {e!s}")
except RuntimeError:
pass

View File

@@ -168,7 +168,7 @@ class SnowflakeSearchTool(BaseTool):
with self._pool_lock:
if self._connection_pool:
return self._connection_pool.pop()
return await asyncio.get_event_loop().run_in_executor(
return await asyncio.get_running_loop().run_in_executor(
self._thread_pool, self._create_connection
)

View File

@@ -6,7 +6,7 @@ readme = "README.md"
authors = [
{ name = "Joao Moura", email = "joao@crewai.com" }
]
requires-python = ">=3.10, <3.14"
requires-python = ">=3.10, <3.15"
dependencies = [
# Core Dependencies
"pydantic~=2.11.9",
@@ -54,7 +54,7 @@ Repository = "https://github.com/crewAIInc/crewAI"
[project.optional-dependencies]
tools = [
"crewai-tools==1.12.1",
"crewai-tools==1.12.2",
]
embeddings = [
"tiktoken~=0.8.0"

View File

@@ -42,7 +42,7 @@ def _suppress_pydantic_deprecation_warnings() -> None:
_suppress_pydantic_deprecation_warnings()
__version__ = "1.12.1"
__version__ = "1.12.2"
_telemetry_submitted = False

View File

@@ -362,7 +362,7 @@ class MemoryTUI(App[None]):
panel.loading = True
try:
scope = self._selected_scope if self._selected_scope != "/" else None
loop = asyncio.get_event_loop()
loop = asyncio.get_running_loop()
matches = await loop.run_in_executor(
None,
lambda: self._memory.recall(query, scope=scope, limit=10, depth="deep"),

View File

@@ -5,7 +5,7 @@ description = "{{name}} using crewAI"
authors = [{ name = "Your Name", email = "you@example.com" }]
requires-python = ">=3.10,<3.14"
dependencies = [
"crewai[tools]==1.12.1"
"crewai[tools]==1.12.2"
]
[project.scripts]

View File

@@ -5,7 +5,7 @@ description = "{{name}} using crewAI"
authors = [{ name = "Your Name", email = "you@example.com" }]
requires-python = ">=3.10,<3.14"
dependencies = [
"crewai[tools]==1.12.1"
"crewai[tools]==1.12.2"
]
[project.scripts]

View File

@@ -5,7 +5,7 @@ description = "Power up your crews with {{folder_name}}"
readme = "README.md"
requires-python = ">=3.10,<3.14"
dependencies = [
"crewai[tools]==1.12.1"
"crewai[tools]==1.12.2"
]
[tool.crewai]

View File

@@ -883,7 +883,9 @@ class Flow(Generic[T], metaclass=FlowMeta):
self.human_feedback_history: list[HumanFeedbackResult] = []
self.last_human_feedback: HumanFeedbackResult | None = None
self._pending_feedback_context: PendingFeedbackContext | None = None
self._human_feedback_method_output: Any = None # Stashed real output from @human_feedback with emit
# Per-method stash for real @human_feedback output (keyed by method name)
# Used to decouple routing outcome from method return value when emit is set
self._human_feedback_method_outputs: dict[str, Any] = {}
self.suppress_flow_events: bool = suppress_flow_events
# User input history (for self.ask())
@@ -2295,10 +2297,12 @@ class Flow(Generic[T], metaclass=FlowMeta):
# For @human_feedback methods with emit, the result is the collapsed outcome
# (e.g., "approved") used for routing. But we want the actual method output
# to be the stored result (for final flow output). Replace the last entry
# if a stashed output exists.
if self._human_feedback_method_output is not None:
self._method_outputs[-1] = self._human_feedback_method_output
self._human_feedback_method_output = None
# if a stashed output exists. Dict-based stash is concurrency-safe and
# handles None return values (presence in dict = stashed, not value).
if method_name in self._human_feedback_method_outputs:
self._method_outputs[-1] = self._human_feedback_method_outputs.pop(
method_name
)
self._method_execution_counts[method_name] = (
self._method_execution_counts.get(method_name, 0) + 1

View File

@@ -594,8 +594,9 @@ def human_feedback(
# Stash the real method output for final flow result when emit is set
# (result is the collapsed outcome string for routing, but we want to
# preserve the actual method output as the flow's final result)
# Uses per-method dict for concurrency safety and to handle None returns
if emit:
self._human_feedback_method_output = method_output
self._human_feedback_method_outputs[func.__name__] = method_output
return result
@@ -624,8 +625,9 @@ def human_feedback(
# Stash the real method output for final flow result when emit is set
# (result is the collapsed outcome string for routing, but we want to
# preserve the actual method output as the flow's final result)
# Uses per-method dict for concurrency safety and to handle None returns
if emit:
self._human_feedback_method_output = method_output
self._human_feedback_method_outputs[func.__name__] = method_output
return result

View File

@@ -86,7 +86,7 @@ class ChromaDBClient(BaseClient):
yield
return
lock_cm = store_lock(self._lock_name)
loop = asyncio.get_event_loop()
loop = asyncio.get_running_loop()
await loop.run_in_executor(None, lock_cm.__enter__)
try:
yield

View File

@@ -266,7 +266,7 @@ class CrewStructuredTool:
# Run sync functions in a thread pool
import asyncio
return await asyncio.get_event_loop().run_in_executor(
return await asyncio.get_running_loop().run_in_executor(
None, lambda: self.func(**parsed_args, **kwargs)
)
except Exception:

View File

@@ -184,7 +184,7 @@ def create_streaming_state(
if use_async:
async_queue = asyncio.Queue()
loop = asyncio.get_event_loop()
loop = asyncio.get_running_loop()
handler = _create_stream_handler(current_task_info, sync_queue, async_queue, loop)
crewai_event_bus.register_handler(LLMStreamChunkEvent, handler)

View File

@@ -726,3 +726,31 @@ class TestHumanFeedbackFinalOutputPreservation:
# _method_outputs should contain the real output
assert len(flow._method_outputs) == 1
assert flow._method_outputs[0] == {"data": "real output"}
@patch("builtins.input", return_value="looks good")
@patch("builtins.print")
def test_none_return_value_is_preserved(self, mock_print, mock_input):
"""A method returning None should preserve None as flow output, not the outcome string."""
class NoneReturnFlow(Flow):
@start()
@human_feedback(
message="Review:",
emit=["approved", "rejected"],
llm="gpt-4o-mini",
)
def process(self):
# Method does work but returns None (implicit)
pass
flow = NoneReturnFlow()
with (
patch.object(flow, "_request_human_feedback", return_value=""),
patch.object(flow, "_collapse_to_outcome", return_value="approved"),
):
result = flow.kickoff()
# Final output should be None (the method's real return), not "approved"
assert result is None, f"Expected None, got {result!r}"
assert flow.last_human_feedback.outcome == "approved"

View File

@@ -0,0 +1,210 @@
"""Tests for Python 3.14 compatibility.
Python 3.14 changed asyncio.get_event_loop() to raise RuntimeError when no
running event loop exists instead of creating one. All async code paths must
use asyncio.get_running_loop() instead.
See: https://github.com/crewAIInc/crewAI/issues/5109
"""
import asyncio
from contextlib import asynccontextmanager
from typing import Any
from unittest.mock import AsyncMock, MagicMock, patch
import pytest
from crewai.tools.structured_tool import CrewStructuredTool
from crewai.utilities.streaming import create_streaming_state
class TestStructuredToolAsyncCompat:
"""Test that CrewStructuredTool.ainvoke uses get_running_loop correctly."""
@pytest.mark.asyncio
async def test_ainvoke_sync_func_uses_running_loop(self) -> None:
"""ainvoke() with a sync function must use the running event loop."""
def sync_func(x: int) -> int:
"""A sync function."""
return x * 2
tool = CrewStructuredTool.from_function(
func=sync_func, name="double", description="Doubles a number"
)
result = await tool.ainvoke({"x": 5})
assert result == 10
@pytest.mark.asyncio
async def test_ainvoke_async_func(self) -> None:
"""ainvoke() with an async function should call it directly."""
async def async_func(x: int) -> int:
"""An async function."""
return x * 3
tool = CrewStructuredTool.from_function(
func=async_func, name="triple", description="Triples a number"
)
result = await tool.ainvoke({"x": 4})
assert result == 12
@pytest.mark.asyncio
async def test_ainvoke_sync_func_runs_in_executor(self) -> None:
"""Verify ainvoke offloads sync functions to an executor via the running loop."""
import threading
call_thread_ids: list[int] = []
def sync_func(x: int) -> int:
"""A sync function that records its thread."""
call_thread_ids.append(threading.current_thread().ident or 0)
return x + 1
tool = CrewStructuredTool.from_function(
func=sync_func, name="inc", description="Increment"
)
result = await tool.ainvoke({"x": 1})
assert result == 2
assert len(call_thread_ids) == 1
# Sync func should run in a different thread (executor)
assert call_thread_ids[0] != threading.current_thread().ident
class TestStreamingStateAsyncCompat:
"""Test that create_streaming_state uses get_running_loop correctly."""
@pytest.mark.asyncio
async def test_create_streaming_state_async_uses_running_loop(self) -> None:
"""create_streaming_state(use_async=True) must use the running loop."""
task_info = {
"index": 0,
"name": "test",
"id": "test-id",
"agent_role": "tester",
"agent_id": "agent-id",
}
state = create_streaming_state(
current_task_info=task_info,
result_holder=[],
use_async=True,
)
assert state.loop is not None
assert state.async_queue is not None
assert state.loop is asyncio.get_running_loop()
def test_create_streaming_state_sync_no_loop_needed(self) -> None:
"""create_streaming_state(use_async=False) should not require a loop."""
task_info = {
"index": 0,
"name": "test",
"id": "test-id",
"agent_role": "tester",
"agent_id": "agent-id",
}
state = create_streaming_state(
current_task_info=task_info,
result_holder=[],
use_async=False,
)
assert state.loop is None
assert state.async_queue is None
@pytest.mark.asyncio
async def test_create_streaming_state_async_uses_get_running_loop_not_get_event_loop(
self,
) -> None:
"""Verify create_streaming_state does not call asyncio.get_event_loop()."""
task_info = {
"index": 0,
"name": "test",
"id": "test-id",
"agent_role": "tester",
"agent_id": "agent-id",
}
with patch("crewai.utilities.streaming.asyncio") as mock_asyncio:
mock_asyncio.Queue = asyncio.Queue
mock_asyncio.get_running_loop.return_value = asyncio.get_running_loop()
create_streaming_state(
current_task_info=task_info,
result_holder=[],
use_async=True,
)
mock_asyncio.get_running_loop.assert_called_once()
mock_asyncio.get_event_loop.assert_not_called()
class TestChromaDBClientAsyncCompat:
"""Test that ChromaDBClient._alocked uses get_running_loop correctly."""
@pytest.mark.asyncio
async def test_alocked_without_lock_name(self) -> None:
"""_alocked should yield immediately when no lock name is set."""
from crewai.rag.chromadb.client import ChromaDBClient
mock_client = MagicMock()
mock_ef = MagicMock()
client = ChromaDBClient(
client=mock_client,
embedding_function=mock_ef,
lock_name=None,
)
async with client._alocked():
pass # Should not raise
@pytest.mark.asyncio
async def test_alocked_uses_get_running_loop_not_get_event_loop(self) -> None:
"""Verify _alocked does not call asyncio.get_event_loop()."""
from crewai.rag.chromadb.client import ChromaDBClient
mock_client = MagicMock()
mock_ef = MagicMock()
client = ChromaDBClient(
client=mock_client,
embedding_function=mock_ef,
lock_name="test-lock",
)
with patch("crewai.rag.chromadb.client.asyncio") as mock_asyncio:
loop = asyncio.get_running_loop()
mock_asyncio.get_running_loop.return_value = loop
mock_cm = MagicMock()
with patch("crewai.rag.chromadb.client.store_lock", return_value=mock_cm):
async with client._alocked():
pass
mock_asyncio.get_running_loop.assert_called()
mock_asyncio.get_event_loop.assert_not_called()
class TestGetRunningLoopInAsyncContext:
"""General tests ensuring get_running_loop works in async contexts."""
@pytest.mark.asyncio
async def test_get_running_loop_available_in_async_context(self) -> None:
"""asyncio.get_running_loop() should work in an async context."""
loop = asyncio.get_running_loop()
assert loop is not None
assert loop.is_running()
@pytest.mark.asyncio
async def test_run_in_executor_with_running_loop(self) -> None:
"""run_in_executor should work with get_running_loop()."""
loop = asyncio.get_running_loop()
def sync_work() -> str:
return "done"
result = await loop.run_in_executor(None, sync_work)
assert result == "done"
def test_get_running_loop_raises_outside_async(self) -> None:
"""get_running_loop() should raise RuntimeError outside async context."""
with pytest.raises(RuntimeError):
asyncio.get_running_loop()

View File

@@ -271,7 +271,7 @@ async def test_mixed_sync_async_handler_execution():
timeout=5
)
await asyncio.get_event_loop().run_in_executor(None, wait_for_completion)
await asyncio.get_running_loop().run_in_executor(None, wait_for_completion)
assert len(sync_executed) == 5
assert len(async_executed) == 5

View File

@@ -1,3 +1,3 @@
"""CrewAI development tools."""
__version__ = "1.12.1"
__version__ = "1.12.2"

View File

@@ -1,7 +1,7 @@
name = "crewai-workspace"
description = "Cutting-edge framework for orchestrating role-playing, autonomous AI agents. By fostering collaborative intelligence, CrewAI empowers agents to work together seamlessly, tackling complex tasks."
readme = "README.md"
requires-python = ">=3.10,<3.14"
requires-python = ">=3.10,<3.15"
authors = [
{ name = "Joao Moura", email = "joao@crewai.com" }
]

9113
uv.lock generated

File diff suppressed because it is too large Load Diff