mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-07-02 21:58:11 +00:00
fix(cli): guard crew chat description helpers against LLM failures
Some checks failed
CodeQL Advanced / Analyze (actions) (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Vulnerability Scan / pip-audit (push) Has been cancelled
Build uv cache / build-cache (3.10) (push) Has been cancelled
Build uv cache / build-cache (3.11) (push) Has been cancelled
Build uv cache / build-cache (3.12) (push) Has been cancelled
Build uv cache / build-cache (3.13) (push) Has been cancelled
Mark stale issues and pull requests / stale (push) Has been cancelled
Some checks failed
CodeQL Advanced / Analyze (actions) (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Vulnerability Scan / pip-audit (push) Has been cancelled
Build uv cache / build-cache (3.10) (push) Has been cancelled
Build uv cache / build-cache (3.11) (push) Has been cancelled
Build uv cache / build-cache (3.12) (push) Has been cancelled
Build uv cache / build-cache (3.13) (push) Has been cancelled
Mark stale issues and pull requests / stale (push) Has been cancelled
This commit is contained in:
@@ -25,6 +25,9 @@ from crewai.utilities.version import get_crewai_version
|
||||
|
||||
MIN_REQUIRED_VERSION: Final[Literal["0.98.0"]] = "0.98.0"
|
||||
|
||||
DEFAULT_INPUT_DESCRIPTION: Final[str] = "Input value for the crew's tasks and agents."
|
||||
DEFAULT_CREW_DESCRIPTION: Final[str] = "A CrewAI crew."
|
||||
|
||||
|
||||
def check_conversational_crews_version(
|
||||
crewai_version: str, pyproject_data: dict[str, Any]
|
||||
@@ -381,7 +384,10 @@ def load_crew_and_name() -> tuple[Crew, str]:
|
||||
|
||||
|
||||
def generate_crew_chat_inputs(
|
||||
crew: Crew, crew_name: str, chat_llm: LLM | BaseLLM
|
||||
crew: Crew,
|
||||
crew_name: str,
|
||||
chat_llm: LLM | BaseLLM,
|
||||
generate_descriptions: bool = True,
|
||||
) -> ChatInputs:
|
||||
"""
|
||||
Generates the ChatInputs required for the crew by analyzing the tasks and agents.
|
||||
@@ -390,21 +396,28 @@ def generate_crew_chat_inputs(
|
||||
crew (Crew): The crew object containing tasks and agents.
|
||||
crew_name (str): The name of the crew.
|
||||
chat_llm: The chat language model to use for AI calls.
|
||||
generate_descriptions: When True (default), use the LLM to generate
|
||||
input and crew descriptions. When False, skip all LLM calls and
|
||||
return static defaults. Production callers that invoke this at
|
||||
startup should pass ``False`` to avoid blocking on the LLM.
|
||||
|
||||
Returns:
|
||||
ChatInputs: An object containing the crew's name, description, and input fields.
|
||||
"""
|
||||
# Extract placeholders from tasks and agents
|
||||
required_inputs = fetch_required_inputs(crew)
|
||||
|
||||
# Generate descriptions for each input using AI
|
||||
input_fields = []
|
||||
for input_name in required_inputs:
|
||||
description = generate_input_description_with_ai(input_name, crew, chat_llm)
|
||||
if generate_descriptions:
|
||||
description = generate_input_description_with_ai(input_name, crew, chat_llm)
|
||||
else:
|
||||
description = DEFAULT_INPUT_DESCRIPTION
|
||||
input_fields.append(ChatInputField(name=input_name, description=description))
|
||||
|
||||
# Generate crew description using AI
|
||||
crew_description = generate_crew_description_with_ai(crew, chat_llm)
|
||||
if generate_descriptions:
|
||||
crew_description = generate_crew_description_with_ai(crew, chat_llm)
|
||||
else:
|
||||
crew_description = DEFAULT_CREW_DESCRIPTION
|
||||
|
||||
return ChatInputs(
|
||||
crew_name=crew_name, crew_description=crew_description, inputs=input_fields
|
||||
@@ -482,7 +495,15 @@ def generate_input_description_with_ai(
|
||||
"Context:\n"
|
||||
f"{context}"
|
||||
)
|
||||
response = chat_llm.call(messages=[{"role": "user", "content": prompt}])
|
||||
try:
|
||||
response = chat_llm.call(messages=[{"role": "user", "content": prompt}])
|
||||
except Exception as exc:
|
||||
click.secho(
|
||||
f"Warning: failed to generate input description for '{input_name}' "
|
||||
f"({exc}); using default.",
|
||||
fg="yellow",
|
||||
)
|
||||
return DEFAULT_INPUT_DESCRIPTION
|
||||
return str(response).strip()
|
||||
|
||||
|
||||
@@ -532,5 +553,12 @@ def generate_crew_description_with_ai(crew: Crew, chat_llm: LLM | BaseLLM) -> st
|
||||
"Context:\n"
|
||||
f"{context}"
|
||||
)
|
||||
response = chat_llm.call(messages=[{"role": "user", "content": prompt}])
|
||||
try:
|
||||
response = chat_llm.call(messages=[{"role": "user", "content": prompt}])
|
||||
except Exception as exc:
|
||||
click.secho(
|
||||
f"Warning: failed to generate crew description ({exc}); using default.",
|
||||
fg="yellow",
|
||||
)
|
||||
return DEFAULT_CREW_DESCRIPTION
|
||||
return str(response).strip()
|
||||
|
||||
116
lib/crewai/tests/cli/test_crew_chat.py
Normal file
116
lib/crewai/tests/cli/test_crew_chat.py
Normal file
@@ -0,0 +1,116 @@
|
||||
"""Tests for ``crewai.cli.crew_chat`` startup-safety helpers."""
|
||||
|
||||
from unittest import mock
|
||||
|
||||
from crewai.cli.crew_chat import (
|
||||
DEFAULT_CREW_DESCRIPTION,
|
||||
DEFAULT_INPUT_DESCRIPTION,
|
||||
generate_crew_chat_inputs,
|
||||
generate_crew_description_with_ai,
|
||||
generate_input_description_with_ai,
|
||||
)
|
||||
|
||||
|
||||
def _make_crew(
|
||||
*,
|
||||
task_description: str = "",
|
||||
expected_output: str = "",
|
||||
agent_role: str = "",
|
||||
agent_goal: str = "",
|
||||
agent_backstory: str = "",
|
||||
inputs: set[str] | None = None,
|
||||
) -> mock.Mock:
|
||||
task = mock.Mock()
|
||||
task.description = task_description
|
||||
task.expected_output = expected_output
|
||||
|
||||
agent = mock.Mock()
|
||||
agent.role = agent_role
|
||||
agent.goal = agent_goal
|
||||
agent.backstory = agent_backstory
|
||||
|
||||
crew = mock.Mock()
|
||||
crew.tasks = [task]
|
||||
crew.agents = [agent]
|
||||
crew.fetch_inputs = mock.Mock(return_value=inputs or set())
|
||||
return crew
|
||||
|
||||
|
||||
def test_generate_input_description_falls_back_on_llm_failure() -> None:
|
||||
crew = _make_crew(task_description="Summarize {topic} for the team.")
|
||||
chat_llm = mock.Mock()
|
||||
chat_llm.call.side_effect = RuntimeError("APIConnectionError")
|
||||
|
||||
description = generate_input_description_with_ai("topic", crew, chat_llm)
|
||||
|
||||
assert description == DEFAULT_INPUT_DESCRIPTION
|
||||
chat_llm.call.assert_called_once()
|
||||
|
||||
|
||||
def test_generate_crew_description_falls_back_on_llm_failure() -> None:
|
||||
crew = _make_crew(task_description="Summarize topic for the team.")
|
||||
chat_llm = mock.Mock()
|
||||
chat_llm.call.side_effect = RuntimeError("APIConnectionError")
|
||||
|
||||
description = generate_crew_description_with_ai(crew, chat_llm)
|
||||
|
||||
assert description == DEFAULT_CREW_DESCRIPTION
|
||||
chat_llm.call.assert_called_once()
|
||||
|
||||
|
||||
def test_generate_input_description_returns_llm_response_on_success() -> None:
|
||||
crew = _make_crew(task_description="Summarize {topic} for the team.")
|
||||
chat_llm = mock.Mock()
|
||||
chat_llm.call.return_value = " the subject to summarize "
|
||||
|
||||
description = generate_input_description_with_ai("topic", crew, chat_llm)
|
||||
|
||||
assert description == "the subject to summarize"
|
||||
|
||||
|
||||
def test_generate_crew_chat_inputs_skips_llm_when_descriptions_disabled() -> None:
|
||||
crew = _make_crew(
|
||||
task_description="Summarize {topic} for the team.",
|
||||
inputs={"topic"},
|
||||
)
|
||||
chat_llm = mock.Mock()
|
||||
|
||||
chat_inputs = generate_crew_chat_inputs(
|
||||
crew, "demo-crew", chat_llm, generate_descriptions=False
|
||||
)
|
||||
|
||||
assert chat_inputs.crew_name == "demo-crew"
|
||||
assert chat_inputs.crew_description == DEFAULT_CREW_DESCRIPTION
|
||||
assert len(chat_inputs.inputs) == 1
|
||||
assert chat_inputs.inputs[0].name == "topic"
|
||||
assert chat_inputs.inputs[0].description == DEFAULT_INPUT_DESCRIPTION
|
||||
chat_llm.call.assert_not_called()
|
||||
|
||||
|
||||
def test_generate_crew_chat_inputs_uses_llm_by_default() -> None:
|
||||
crew = _make_crew(
|
||||
task_description="Summarize {topic} for the team.",
|
||||
inputs={"topic"},
|
||||
)
|
||||
chat_llm = mock.Mock()
|
||||
chat_llm.call.side_effect = ["the subject to summarize", "summarize topics"]
|
||||
|
||||
chat_inputs = generate_crew_chat_inputs(crew, "demo-crew", chat_llm)
|
||||
|
||||
assert chat_inputs.crew_description == "summarize topics"
|
||||
assert chat_inputs.inputs[0].description == "the subject to summarize"
|
||||
assert chat_llm.call.call_count == 2
|
||||
|
||||
|
||||
def test_generate_crew_chat_inputs_falls_back_when_llm_fails_mid_run() -> None:
|
||||
crew = _make_crew(
|
||||
task_description="Summarize {topic} for the team.",
|
||||
inputs={"topic"},
|
||||
)
|
||||
chat_llm = mock.Mock()
|
||||
chat_llm.call.side_effect = RuntimeError("APIConnectionError")
|
||||
|
||||
chat_inputs = generate_crew_chat_inputs(crew, "demo-crew", chat_llm)
|
||||
|
||||
assert chat_inputs.crew_description == DEFAULT_CREW_DESCRIPTION
|
||||
assert chat_inputs.inputs[0].description == DEFAULT_INPUT_DESCRIPTION
|
||||
Reference in New Issue
Block a user