Compare commits

..

1 Commits

Author SHA1 Message Date
Gabe
2f5928e4bb fix: only treat interpolatable placeholders as crew inputs 2026-06-09 13:42:42 -03:00
25 changed files with 151 additions and 314 deletions

View File

@@ -4,26 +4,6 @@ description: "تحديثات المنتج والتحسينات وإصلاحات
icon: "clock"
mode: "wide"
---
<Update label="9 يونيو 2026">
## v1.14.7a4
[عرض الإصدار على GitHub](https://github.com/crewAIInc/crewAI/releases/tag/1.14.7a4)
## ما الذي تغير
### الميزات
- نقل وقت التشغيل @listen/@router لقراءة من FlowDefinition
- إضافة واجهات خلفية افتراضية قابلة للتوصيل للذاكرة، والمعرفة، وrag، وflow
### الوثائق
- تحديث سجل التغييرات والإصدار لـ v1.14.7a3
## المساهمون
@greysonlalonde, @mattatcha, @vinibrsl
</Update>
<Update label="8 يونيو 2026">
## v1.14.7a3

View File

@@ -4,26 +4,6 @@ description: "Product updates, improvements, and bug fixes for CrewAI"
icon: "clock"
mode: "wide"
---
<Update label="Jun 09, 2026">
## v1.14.7a4
[View release on GitHub](https://github.com/crewAIInc/crewAI/releases/tag/1.14.7a4)
## What's Changed
### Features
- Migrate @listen/@router runtime to read from FlowDefinition
- Add pluggable default backends for memory, knowledge, rag, and flow
### Documentation
- Update changelog and version for v1.14.7a3
## Contributors
@greysonlalonde, @mattatcha, @vinibrsl
</Update>
<Update label="Jun 08, 2026">
## v1.14.7a3

View File

@@ -1,98 +0,0 @@
---
title: Discovery
description: "Identify the highest-impact AI automation use cases for your business."
icon: "compass"
mode: "wide"
---
## Overview
Discovery is a new engine inside CrewAI AMP that helps companies identify the best automation use cases for their business.
The bottleneck in AI adoption is not building agents — it's knowing _what_ to build and _how_ to build it for production. Discovery closes that gap.
{/* TODO: Add screenshot of Discovery dashboard */}
Instead of weeks of stakeholder interviews, consultant engagements, and slide decks, Discovery leverages CrewAI's deep knowledge of agent patterns and what works in production to match your business context against proven approaches. Within minutes, you get actionable, evidence-based recommendations specific to your organization.
## How It Works
<Steps>
<Step title="Describe Your Business">
Tell Discovery about your organization — your processes, challenges, goals, and the teams involved. The more context you provide, the more precise the recommendations.
</Step>
<Step title="Multi-Signal Matching">
Discovery runs cohort analysis and structural pattern recognition using CrewAI's world model, matching your business context to automation patterns already running successfully at scale.
</Step>
<Step title="Review Use Cases">
Within minutes, you receive a set of use cases specific to your company — not generic templates. Each one shows what the automation does, expected impact, complexity, and how it would work in your organization.
{/* TODO: Add screenshot of use case recommendations */}
</Step>
<Step title="Build">
Select a use case and go directly into Crew Studio or export to code to start building.
</Step>
</Steps>
{/* TODO: Add screenshot of Discovery flow / results page */}
## Key Features
<CardGroup cols={2}>
<Card title="Business-Specific Recommendations" icon="bullseye">
Not generic templates. Real use cases matched to your organization based on CrewAI's knowledge of what works in production.
</Card>
<Card title="Impact & Complexity Scoring" icon="chart-mixed">
Each recommendation includes expected impact, implementation complexity, and how it fits your org — so you can prioritize with confidence.
</Card>
<Card title="Iterative Discovery" icon="arrows-rotate">
Run Discovery multiple times across different business units. It becomes part of how you plan and iterate on your AI roadmap.
</Card>
<Card title="Evidence-Based" icon="flask-vial">
Every recommendation is grounded in what CrewAI knows actually works in production — not guesswork or intuition.
</Card>
</CardGroup>
## From Discovery to Production
Discovery fits at the very beginning of the CrewAI workflow — it's the "what to build" step before the "how to build" step.
{/* TODO: Add diagram showing Discovery → Crew Studio → Automations flow */}
The end-to-end flow:
1. **Discovery** identifies the use case and provides the blueprint
2. **Crew Studio** or code lets you build the automation
3. **Automations** deploys it to production
This means you go from "we should use AI somewhere" to a running production automation with a clear, guided path — no guesswork at any stage.
## Use Cases
<CardGroup cols={2}>
<Card title="New to AI Agents" icon="seedling">
Don't know where to start? Discovery identifies the highest-impact opportunities specific to your business, so you begin with what matters most.
</Card>
<Card title="Scaling AI Programs" icon="rocket">
Already have some automations? Discovery finds the next wave of use cases across departments, helping you expand beyond initial pilots.
</Card>
<Card title="Cross-Department Rollout" icon="building">
Run Discovery for different business units to build a company-wide AI roadmap with use cases tailored to each team's needs.
</Card>
<Card title="ROI Prioritization" icon="chart-line">
Need to justify AI investment? Discovery provides evidence-based impact estimates grounded in real-world results.
</Card>
</CardGroup>
## Related
<CardGroup cols={3}>
<Card title="Crew Studio" href="/en/enterprise/features/crew-studio" icon="pencil">
Build automations with AI assistance and a visual editor.
</Card>
<Card title="Automations" href="/en/enterprise/features/automations" icon="bolt">
Deploy and manage your automations in production.
</Card>
<Card title="Marketplace" href="/en/enterprise/features/marketplace" icon="store">
Browse pre-built automations and components.
</Card>
</CardGroup>

View File

@@ -4,26 +4,6 @@ description: "CrewAI의 제품 업데이트, 개선 사항 및 버그 수정"
icon: "clock"
mode: "wide"
---
<Update label="2026년 6월 9일">
## v1.14.7a4
[GitHub 릴리스 보기](https://github.com/crewAIInc/crewAI/releases/tag/1.14.7a4)
## 변경 사항
### 기능
- @listen/@router 런타임을 FlowDefinition에서 읽도록 마이그레이션
- 메모리, 지식, rag 및 flow에 대한 플러그형 기본 백엔드 추가
### 문서
- v1.14.7a3에 대한 변경 로그 및 버전 업데이트
## 기여자
@greysonlalonde, @mattatcha, @vinibrsl
</Update>
<Update label="2026년 6월 8일">
## v1.14.7a3

View File

@@ -4,26 +4,6 @@ description: "Atualizações de produto, melhorias e correções do CrewAI"
icon: "clock"
mode: "wide"
---
<Update label="09 jun 2026">
## v1.14.7a4
[Ver release no GitHub](https://github.com/crewAIInc/crewAI/releases/tag/1.14.7a4)
## O Que Mudou
### Funcionalidades
- Migrar a execução @listen/@router para ler a partir de FlowDefinition
- Adicionar backends padrão plugáveis para memória, conhecimento, rag e flow
### Documentação
- Atualizar changelog e versão para v1.14.7a3
## Contributors
@greysonlalonde, @mattatcha, @vinibrsl
</Update>
<Update label="08 jun 2026">
## v1.14.7a3

View File

@@ -8,7 +8,7 @@ authors = [
]
requires-python = ">=3.10, <3.14"
dependencies = [
"crewai-core==1.14.7a4",
"crewai-core==1.14.7a3",
"click>=8.1.7,<9",
"pydantic>=2.11.9,<2.13",
"pydantic-settings~=2.10.1",

View File

@@ -1 +1 @@
__version__ = "1.14.7a4"
__version__ = "1.14.7a3"

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.14.7a4"
"crewai[tools]==1.14.7a3"
]
[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.14.7a4"
"crewai[tools]==1.14.7a3"
]
[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.14.7a4"
"crewai[tools]==1.14.7a3"
]
[tool.crewai]

View File

@@ -1 +1 @@
__version__ = "1.14.7a4"
__version__ = "1.14.7a3"

View File

@@ -152,4 +152,4 @@ __all__ = [
"wrap_file_source",
]
__version__ = "1.14.7a4"
__version__ = "1.14.7a3"

View File

@@ -10,7 +10,7 @@ requires-python = ">=3.10, <3.14"
dependencies = [
"pytube~=15.0.0",
"requests>=2.33.0,<3",
"crewai==1.14.7a4",
"crewai==1.14.7a3",
"tiktoken>=0.8.0,<0.13",
"beautifulsoup4~=4.13.4",
"python-docx~=1.2.0",

View File

@@ -330,4 +330,4 @@ __all__ = [
"ZapierActionTools",
]
__version__ = "1.14.7a4"
__version__ = "1.14.7a3"

View File

@@ -8,8 +8,8 @@ authors = [
]
requires-python = ">=3.10, <3.14"
dependencies = [
"crewai-core==1.14.7a4",
"crewai-cli==1.14.7a4",
"crewai-core==1.14.7a3",
"crewai-cli==1.14.7a3",
# Core Dependencies
"pydantic>=2.11.9,<2.13",
"openai>=2.30.0,<3",
@@ -54,7 +54,7 @@ Repository = "https://github.com/crewAIInc/crewAI"
[project.optional-dependencies]
tools = [
"crewai-tools==1.14.7a4",
"crewai-tools==1.14.7a3",
]
embeddings = [
"tiktoken>=0.8.0,<0.13"

View File

@@ -48,7 +48,7 @@ def _suppress_pydantic_deprecation_warnings() -> None:
_suppress_pydantic_deprecation_warnings()
__version__ = "1.14.7a4"
__version__ = "1.14.7a3"
_LAZY_IMPORTS: dict[str, tuple[str, str]] = {
"Memory": ("crewai.memory.unified_memory", "Memory"),

View File

@@ -7,7 +7,6 @@ from copy import copy as shallow_copy
from hashlib import md5
import json
from pathlib import Path
import re
from typing import (
TYPE_CHECKING,
Annotated,
@@ -142,7 +141,10 @@ from crewai.utilities.streaming import (
signal_end,
signal_error,
)
from crewai.utilities.string_utils import sanitize_tool_name
from crewai.utilities.string_utils import (
extract_template_variables,
sanitize_tool_name,
)
from crewai.utilities.task_output_storage_handler import TaskOutputStorageHandler
from crewai.utilities.training_handler import CrewTrainingHandler
@@ -1960,20 +1962,24 @@ class Crew(FlowTrackable, BaseModel):
Scans each task's 'description' + 'expected_output', and each agent's
'role', 'goal', and 'backstory'.
Only placeholders that interpolation can actually fill are returned;
non-identifier expressions such as ``{x if x else "y"}`` are ignored so
they are not surfaced as required inputs (matching interpolation
behavior, see :func:`extract_template_variables`).
Returns a set of all discovered placeholder names.
"""
placeholder_pattern = re.compile(r"\{(.+?)}")
required_inputs: set[str] = set()
for task in self.tasks:
# description and expected_output might contain e.g. {topic}, {user_name}
text = f"{task.description or ''} {task.expected_output or ''}"
required_inputs.update(placeholder_pattern.findall(text))
required_inputs.update(extract_template_variables(text))
for agent in self.agents:
# role, goal, backstory might have placeholders like {role_detail}, etc.
text = f"{agent.role or ''} {agent.goal or ''} {agent.backstory or ''}"
required_inputs.update(placeholder_pattern.findall(text))
required_inputs.update(extract_template_variables(text))
return required_inputs

View File

@@ -780,11 +780,10 @@ class TraceCollectionListener(BaseEventListener):
def _try_initialize_flow_batch_from_context(self, event: Any) -> bool:
"""Claim a flow trace batch when an action event fires inside kickoff.
When ``suppress_flow_events=True`` (infrastructure flows such as
``AgentExecutor`` and the memory flows), flow and method lifecycle
events are not emitted, so the batch is claimed from the flow context
(``current_flow_id``) to keep LLM/tool events from falling back to an
implicit crew batch.
When ``suppress_flow_events=True``, console panels are hidden but
``FlowStartedEvent`` and method lifecycle events still emit; if no
batch exists yet, LLM/tool events must not fall back to implicit crew
batches.
"""
from crewai.flow.flow_context import current_flow_id, current_flow_name

View File

@@ -1420,17 +1420,16 @@ class Flow(_ConversationalMixin, BaseModel, Generic[T], metaclass=FlowMeta):
if self.persistence is not None:
self.persistence.clear_pending_feedback(context.flow_id)
if not self.suppress_flow_events:
crewai_event_bus.emit(
self,
MethodExecutionFinishedEvent(
type="method_execution_finished",
flow_name=self.name or self.__class__.__name__,
method_name=context.method_name,
result=collapsed_outcome if emit else result,
state=self._state,
),
)
crewai_event_bus.emit(
self,
MethodExecutionFinishedEvent(
type="method_execution_finished",
flow_name=self.name or self.__class__.__name__,
method_name=context.method_name,
result=collapsed_outcome if emit else result,
state=self._state,
),
)
# Clear resumption flag before triggering listeners
# This allows methods to re-execute in loops (e.g., implement_changes → suggest_changes → implement_changes)
@@ -2477,19 +2476,20 @@ class Flow(_ConversationalMixin, BaseModel, Generic[T], metaclass=FlowMeta):
kwargs or {}
)
if not self.suppress_flow_events:
future = crewai_event_bus.emit(
self,
MethodExecutionStartedEvent(
type="method_execution_started",
method_name=method_name,
flow_name=self.name or self.__class__.__name__,
params=dumped_params,
state=self._copy_and_serialize_state(),
),
)
if future:
self._event_futures.append(future)
# MethodExecution events always fire — ``suppress_flow_events``
# only hides the Rich console panel, not observability events.
future = crewai_event_bus.emit(
self,
MethodExecutionStartedEvent(
type="method_execution_started",
method_name=method_name,
flow_name=self.name or self.__class__.__name__,
params=dumped_params,
state=self._copy_and_serialize_state(),
),
)
if future:
self._event_futures.append(future)
# Set method name in context so ask() can read it without
# stack inspection. Must happen before copy_context() so the
@@ -2531,18 +2531,19 @@ class Flow(_ConversationalMixin, BaseModel, Generic[T], metaclass=FlowMeta):
self._completed_methods.add(method_name)
finished_event_id: str | None = None
if not self.suppress_flow_events:
finished_event = MethodExecutionFinishedEvent(
type="method_execution_finished",
method_name=method_name,
flow_name=self.name or self.__class__.__name__,
state=self._copy_and_serialize_state(),
result=result,
)
finished_event_id = finished_event.event_id
future = crewai_event_bus.emit(self, finished_event)
if future:
self._event_futures.append(future)
# MethodExecution events always fire even when console panels are
# suppressed; tracing depends on them.
finished_event = MethodExecutionFinishedEvent(
type="method_execution_finished",
method_name=method_name,
flow_name=self.name or self.__class__.__name__,
state=self._copy_and_serialize_state(),
result=result,
)
finished_event_id = finished_event.event_id
future = crewai_event_bus.emit(self, finished_event)
if future:
self._event_futures.append(future)
return result, finished_event_id
except Exception as e:

View File

@@ -23,6 +23,26 @@ def _duplicate_separator_pattern(separator: str) -> re.Pattern[str]:
return re.compile(f"(?:{re.escape(separator)}){{2,}}")
def extract_template_variables(input_string: str | None) -> list[str]:
"""Return the template variable names referenced in a string.
Only recognizes placeholders that interpolation can actually fill, i.e.
``{name}`` where ``name`` starts with a letter/underscore and contains only
letters, numbers, underscores, and hyphens. Expressions such as
``{x if x else "y"}`` or JSON snippets are intentionally ignored so they are
never treated as required inputs.
Args:
input_string: The string to scan. May be ``None`` or empty.
Returns:
The matched variable names, in order of appearance (with duplicates).
"""
if not input_string:
return []
return _VARIABLE_PATTERN.findall(input_string)
def sanitize_tool_name(name: str, max_length: int = _MAX_TOOL_NAME_LENGTH) -> str:
"""Sanitize tool name for LLM provider compatibility.

View File

@@ -3895,6 +3895,29 @@ def test_fetch_inputs():
)
def test_fetch_inputs_ignores_non_identifier_placeholders():
agent = Agent(
role="Report writer",
goal="Write a report for {company_name}.",
backstory="Expert reporter.",
)
task = Task(
description=(
'Greet {company_name if company_name else "Individual Client"} '
"and summarize {search_period}."
),
expected_output="A summary for {company_name}.",
agent=agent,
)
crew = Crew(agents=[agent], tasks=[task])
# Only the simple {company_name} placeholders are returned; the inline conditional
# expression (which interpolation cannot fill) is ignored.
assert crew.fetch_inputs() == {"company_name", "search_period"}
@pytest.mark.vcr()
def test_task_tools_preserve_code_execution_tools():
"""

View File

@@ -1281,11 +1281,7 @@ class TestFlowTracingWhenSuppressed:
assert started == ["QuietFlow"]
def test_method_execution_suppressed_when_flow_events_suppressed(self) -> None:
"""``suppress_flow_events=True`` silences MethodExecution events so
infrastructure flows (AgentExecutor, memory) don't emit one trace span
per internal control-flow method."""
def test_method_execution_emitted_when_panel_events_suppressed(self) -> None:
class QuietFlow(Flow[ChatState]):
suppress_flow_events = True
@@ -1307,8 +1303,8 @@ class TestFlowTracingWhenSuppressed:
with patch.object(crewai_event_bus, "emit", side_effect=track_emit):
QuietFlow().kickoff()
assert started == []
assert finished == []
assert started == ["begin"]
assert finished == ["begin"]
def test_llm_action_inside_flow_claims_flow_trace_batch(self) -> None:
listener = TraceCollectionListener()

View File

@@ -838,74 +838,6 @@ def test_flow_method_execution_finished_includes_serialized_state():
assert final_output == "final_result"
def test_suppress_flow_events_silences_method_lifecycle_events():
"""``suppress_flow_events=True`` emits no MethodExecution* events on the
bus (used by infrastructure flows like AgentExecutor so their control-flow
methods don't pollute traces), while default flows still emit them."""
captured: list[tuple[str, str]] = []
class SuppressedFlow(Flow):
suppress_flow_events: bool = True
@start()
def begin(self):
return "started"
@listen("begin")
def process(self):
return "done"
class ControlFlow(Flow):
@start()
def begin(self):
return "started"
@listen("begin")
def process(self):
return "done"
with crewai_event_bus.scoped_handlers():
@crewai_event_bus.on(MethodExecutionStartedEvent)
def _on_started(source, event):
captured.append(("started", type(source).__name__))
@crewai_event_bus.on(MethodExecutionFinishedEvent)
def _on_finished(source, event):
captured.append(("finished", type(source).__name__))
SuppressedFlow().kickoff()
wait_for_event_handlers()
assert [e for e in captured if e[1] == "SuppressedFlow"] == [], (
"suppress_flow_events=True must emit no MethodExecution* events"
)
captured.clear()
ControlFlow().kickoff()
wait_for_event_handlers()
control = [e for e in captured if e[1] == "ControlFlow"]
assert ("started", "ControlFlow") in control
assert ("finished", "ControlFlow") in control
def test_infrastructure_flows_suppress_flow_events_by_default():
"""Pin the infra flows that must stay silent in traces.
The gating in ``_execute_method`` only helps if these flows actually set
``suppress_flow_events=True``; without this guard, removing the flag from
AgentExecutor would silently bring back the verbose per-method trace spans.
"""
from crewai.experimental.agent_executor import AgentExecutor
from crewai.memory.encoding_flow import EncodingFlow
from crewai.memory.recall_flow import RecallFlow
assert AgentExecutor.model_fields["suppress_flow_events"].default is True
for flow_cls in (EncodingFlow, RecallFlow):
flow = flow_cls(storage=None, llm=None, embedder=None)
assert flow.suppress_flow_events is True
@pytest.mark.vcr()
def test_llm_emits_call_started_event():
started_events: list[LLMCallStartedEvent] = []

View File

@@ -1,7 +1,45 @@
from typing import Any, Dict, List, Union
import pytest
from crewai.utilities.string_utils import interpolate_only
from crewai.utilities.string_utils import (
extract_template_variables,
interpolate_only,
)
class TestExtractTemplateVariables:
"""Tests for extract_template_variables in string_utils.py."""
def test_extracts_simple_identifiers(self):
assert extract_template_variables("Hi {name}, see {topic}.") == [
"name",
"topic",
]
def test_allows_underscores_and_hyphens(self):
assert extract_template_variables("{user_name} {role-detail}") == [
"user_name",
"role-detail",
]
def test_ignores_inline_expressions(self):
text = '{company_name if company_name else "Individual Client"}'
assert extract_template_variables(text) == []
def test_ignores_json_like_braces(self):
assert extract_template_variables('{"key": "value"}') == []
def test_matches_what_interpolation_fills(self):
text = 'Use {topic} and {x if x else "y"}.'
variables = extract_template_variables(text)
assert variables == ["topic"]
# interpolation fills exactly the extracted variable and leaves the rest
result = interpolate_only(text, {"topic": "AI"})
assert result == 'Use AI and {x if x else "y"}.'
@pytest.mark.parametrize("value", [None, ""])
def test_handles_empty_input(self, value):
assert extract_template_variables(value) == []
class TestInterpolateOnly:

View File

@@ -1,3 +1,3 @@
"""CrewAI development tools."""
__version__ = "1.14.7a4"
__version__ = "1.14.7a3"