Compare commits

..

21 Commits

Author SHA1 Message Date
Devin AI
f26833f751 fix: Sort imports using ruff --fix
Co-Authored-By: Joe Moura <joao@crewai.com>
2024-12-22 04:33:16 +00:00
Devin AI
5fe15a8dba fix: Resolve merge conflict and maintain organized imports
Co-Authored-By: Joe Moura <joao@crewai.com>
2024-12-22 04:31:39 +00:00
Devin AI
d8f5a9fb71 fix: Apply ruff automatic import sorting
Co-Authored-By: Joe Moura <joao@crewai.com>
2024-12-22 04:29:13 +00:00
Devin AI
55883c6083 fix: Consolidate imports and fix formatting
Co-Authored-By: Joe Moura <joao@crewai.com>
2024-12-22 04:26:47 +00:00
Devin AI
072f0cbef6 fix: Reorganize imports using ruff --fix
Co-Authored-By: Joe Moura <joao@crewai.com>
2024-12-22 04:25:37 +00:00
Devin AI
7beb511206 fix: Sort imports to fix lint issues
Co-Authored-By: Joe Moura <joao@crewai.com>
2024-12-22 04:22:11 +00:00
Devin AI
5b2e41b8eb feat: Add interpolate_only method and improve error handling
- Add interpolate_only method for string interpolation while preserving JSON structure
- Add comprehensive test coverage for interpolate_only
- Add proper type annotation for logger using ClassVar
- Improve error handling and documentation for _save_file method

Co-Authored-By: Joe Moura <joao@crewai.com>
2024-12-22 04:19:47 +00:00
devin-ai-integration[bot]
22e5d39884 feat: Add task guardrails feature (#1742)
* feat: Add task guardrails feature

Add support for custom code guardrails in tasks that validate outputs
before proceeding to the next task. Features include:

- Optional task-level guardrail function
- Pre-next-task execution timing
- Tuple return format (success, data)
- Automatic result/error routing
- Configurable retry mechanism
- Comprehensive documentation and tests

Link to Devin run: https://app.devin.ai/sessions/39f6cfd6c5a24d25a7bd70ce070ed29a

Co-Authored-By: Joe Moura <joao@crewai.com>

* fix: Add type check for guardrail result and remove unused import

Co-Authored-By: Joe Moura <joao@crewai.com>

* fix: Remove unnecessary f-string prefix

Co-Authored-By: Joe Moura <joao@crewai.com>

* feat: Add guardrail validation improvements

- Add result/error exclusivity validation in GuardrailResult
- Make return type annotations optional in Task guardrail validator
- Improve error messages for validation failures

Co-Authored-By: Joe Moura <joao@crewai.com>

* docs: Add comprehensive guardrails documentation

- Add type hints and examples
- Add error handling best practices
- Add structured error response patterns
- Document retry mechanisms
- Improve documentation organization

Co-Authored-By: Joe Moura <joao@crewai.com>

* refactor: Update guardrail functions to handle TaskOutput objects

Co-Authored-By: Joe Moura <joao@crewai.com>

* feat: Add task guardrails feature

Add support for custom code guardrails in tasks that validate outputs
before proceeding to the next task. Features include:

- Optional task-level guardrail function
- Pre-next-task execution timing
- Tuple return format (success, data)
- Automatic result/error routing
- Configurable retry mechanism
- Comprehensive documentation and tests

Link to Devin run: https://app.devin.ai/sessions/39f6cfd6c5a24d25a7bd70ce070ed29a

Co-Authored-By: Joe Moura <joao@crewai.com>

* fix: Add type check for guardrail result and remove unused import

Co-Authored-By: Joe Moura <joao@crewai.com>

* fix: Remove unnecessary f-string prefix

Co-Authored-By: Joe Moura <joao@crewai.com>

* feat: Add guardrail validation improvements

- Add result/error exclusivity validation in GuardrailResult
- Make return type annotations optional in Task guardrail validator
- Improve error messages for validation failures

Co-Authored-By: Joe Moura <joao@crewai.com>

* docs: Add comprehensive guardrails documentation

- Add type hints and examples
- Add error handling best practices
- Add structured error response patterns
- Document retry mechanisms
- Improve documentation organization

Co-Authored-By: Joe Moura <joao@crewai.com>

* refactor: Update guardrail functions to handle TaskOutput objects

Co-Authored-By: Joe Moura <joao@crewai.com>

* style: Fix import sorting in task guardrails files

Co-Authored-By: Joe Moura <joao@crewai.com>

* fixing docs

* Fixing guardarils implementation

* docs: Enhance guardrail validator docstring with runtime validation rationale

Co-Authored-By: Joe Moura <joao@crewai.com>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Joe Moura <joao@crewai.com>
Co-authored-by: Brandon Hancock (bhancock_ai) <109994880+bhancockio@users.noreply.github.com>
Co-authored-by: João Moura <joaomdmoura@gmail.com>
2024-12-22 00:52:02 -03:00
Brandon Hancock (bhancock_ai)
e6f620877d Merge branch 'main' into main 2024-12-20 10:34:39 -05:00
PJ
9ee6824ccd Correcting a small grammatical issue that was bugging me: from _satisfy the expect criteria_ to _satisfies the expected criteria_ (#1783)
Signed-off-by: PJ Hagerty <pjhagerty@gmail.com>
Co-authored-by: Brandon Hancock (bhancock_ai) <109994880+bhancockio@users.noreply.github.com>
2024-12-20 10:17:34 -05:00
Vini Brasil
da73865f25 Add tool.crewai.type pyproject attribute in templates (#1789) 2024-12-20 10:36:18 -03:00
Vini Brasil
627b9f1abb Remove relative import in flow main.py template (#1782) 2024-12-18 10:47:44 -03:00
alan blount
1b8001bf98 Gemini 2.0 (#1773)
* Update llms.mdx (Gemini 2.0)

- Add Gemini 2.0 flash to Gemini table.
- Add link to 2 hosting paths for Gemini in Tip.
- Change to lower case model slugs vs names, user convenience.
- Add https://artificialanalysis.ai/ as alternate leaderboard.
- Move Gemma to "other" tab.

* Update llm.py (gemini 2.0)

Add setting for Gemini 2.0 context window to llm.py

---------

Co-authored-by: Brandon Hancock (bhancock_ai) <109994880+bhancockio@users.noreply.github.com>
2024-12-17 16:44:10 -05:00
Tony Kipkemboi
e59e07e4f7 Merge pull request #1777 from crewAIInc/fix/python-max-version
Fix/python max version
2024-12-17 16:09:44 -05:00
Frieda (Jingying) Huang
43cb2d1f66 Fixed yaml config is not escaped properly for output requirements 2024-12-15 13:28:17 -05:00
Frieda Huang
4e9b70201e Merge branch 'crewAIInc:main' into main 2024-12-15 11:30:27 -05:00
Frieda Huang
059b0cf5b4 Merge branch 'crewAIInc:main' into main 2024-12-10 22:48:49 -05:00
Frieda Huang
652ddcc1c5 Merge branch 'crewAIInc:main' into main 2024-12-10 07:40:02 -05:00
Brandon Hancock (bhancock_ai)
964d4bfdbf Merge branch 'main' into main 2024-12-09 09:54:20 -05:00
Frieda (Jingying) Huang
c103d7eab7 Merge branch 'main' of https://github.com/frieda-huang/crewAI 2024-12-08 09:25:06 -05:00
Frieda (Jingying) Huang
4fe9f5d8bd Fixed output_file not respecting system path 2024-12-08 09:21:12 -05:00
14 changed files with 655 additions and 45 deletions

View File

@@ -29,7 +29,7 @@ Large Language Models (LLMs) are the core intelligence behind CrewAI agents. The
## Available Models and Their Capabilities
Here's a detailed breakdown of supported models and their capabilities, you can compare performance at [lmarena.ai](https://lmarena.ai/):
Here's a detailed breakdown of supported models and their capabilities, you can compare performance at [lmarena.ai](https://lmarena.ai/?leaderboard) and [artificialanalysis.ai](https://artificialanalysis.ai/):
<Tabs>
<Tab title="OpenAI">
@@ -121,12 +121,18 @@ Here's a detailed breakdown of supported models and their capabilities, you can
<Tab title="Gemini">
| Model | Context Window | Best For |
|-------|---------------|-----------|
| Gemini 1.5 Flash | 1M tokens | Balanced multimodal model, good for most tasks |
| Gemini 1.5 Flash 8B | 1M tokens | Fastest, most cost-efficient, good for high-frequency tasks |
| Gemini 1.5 Pro | 2M tokens | Best performing, wide variety of reasoning tasks including logical reasoning, coding, and creative collaboration |
| gemini-2.0-flash-exp | 1M tokens | Higher quality at faster speed, multimodal model, good for most tasks |
| gemini-1.5-flash | 1M tokens | Balanced multimodal model, good for most tasks |
| gemini-1.5-flash-8B | 1M tokens | Fastest, most cost-efficient, good for high-frequency tasks |
| gemini-1.5-pro | 2M tokens | Best performing, wide variety of reasoning tasks including logical reasoning, coding, and creative collaboration |
<Tip>
Google's Gemini models are all multimodal, supporting audio, images, video and text, supporting context caching, json schema, function calling, etc.
These models are available via API_KEY from
[The Gemini API](https://ai.google.dev/gemini-api/docs) and also from
[Google Cloud Vertex](https://cloud.google.com/vertex-ai/generative-ai/docs/migrate/migrate-google-ai) as part of the
[Model Garden](https://cloud.google.com/vertex-ai/generative-ai/docs/model-garden/explore-models).
</Tip>
</Tab>
<Tab title="Groq">
@@ -135,7 +141,6 @@ Here's a detailed breakdown of supported models and their capabilities, you can
| Llama 3.1 70B/8B | 131,072 tokens | High-performance, large context tasks |
| Llama 3.2 Series | 8,192 tokens | General-purpose tasks |
| Mixtral 8x7B | 32,768 tokens | Balanced performance and context |
| Gemma Series | 8,192 tokens | Efficient, smaller-scale tasks |
<Tip>
Groq is known for its fast inference speeds, making it suitable for real-time applications.
@@ -146,7 +151,7 @@ Here's a detailed breakdown of supported models and their capabilities, you can
|----------|---------------|--------------|
| Deepseek Chat | 128,000 tokens | Specialized in technical discussions |
| Claude 3 | Up to 200K tokens | Strong reasoning, code understanding |
| Gemini | Varies by model | Multimodal capabilities |
| Gemma Series | 8,192 tokens | Efficient, smaller-scale tasks |
<Info>
Provider selection should consider factors like:

View File

@@ -6,7 +6,7 @@ icon: list-check
## Overview of a Task
In the CrewAI framework, a `Task` is a specific assignment completed by an `Agent`.
In the CrewAI framework, a `Task` is a specific assignment completed by an `Agent`.
Tasks provide all necessary details for execution, such as a description, the agent responsible, required tools, and more, facilitating a wide range of action complexities.
@@ -263,8 +263,148 @@ analysis_task = Task(
)
```
## Task Guardrails
Task guardrails provide a way to validate and transform task outputs before they
are passed to the next task. This feature helps ensure data quality and provides
efeedback to agents when their output doesn't meet specific criteria.
### Using Task Guardrails
To add a guardrail to a task, provide a validation function through the `guardrail` parameter:
```python Code
from typing import Tuple, Union, Dict, Any
def validate_blog_content(result: str) -> Tuple[bool, Union[Dict[str, Any], str]]:
"""Validate blog content meets requirements."""
try:
# Check word count
word_count = len(result.split())
if word_count > 200:
return (False, {
"error": "Blog content exceeds 200 words",
"code": "WORD_COUNT_ERROR",
"context": {"word_count": word_count}
})
# Additional validation logic here
return (True, result.strip())
except Exception as e:
return (False, {
"error": "Unexpected error during validation",
"code": "SYSTEM_ERROR"
})
blog_task = Task(
description="Write a blog post about AI",
expected_output="A blog post under 200 words",
agent=blog_agent,
guardrail=validate_blog_content # Add the guardrail function
)
```
### Guardrail Function Requirements
1. **Function Signature**:
- Must accept exactly one parameter (the task output)
- Should return a tuple of `(bool, Any)`
- Type hints are recommended but optional
2. **Return Values**:
- Success: Return `(True, validated_result)`
- Failure: Return `(False, error_details)`
### Error Handling Best Practices
1. **Structured Error Responses**:
```python Code
def validate_with_context(result: str) -> Tuple[bool, Union[Dict[str, Any], str]]:
try:
# Main validation logic
validated_data = perform_validation(result)
return (True, validated_data)
except ValidationError as e:
return (False, {
"error": str(e),
"code": "VALIDATION_ERROR",
"context": {"input": result}
})
except Exception as e:
return (False, {
"error": "Unexpected error",
"code": "SYSTEM_ERROR"
})
```
2. **Error Categories**:
- Use specific error codes
- Include relevant context
- Provide actionable feedback
3. **Validation Chain**:
```python Code
from typing import Any, Dict, List, Tuple, Union
def complex_validation(result: str) -> Tuple[bool, Union[str, Dict[str, Any]]]:
"""Chain multiple validation steps."""
# Step 1: Basic validation
if not result:
return (False, {"error": "Empty result", "code": "EMPTY_INPUT"})
# Step 2: Content validation
try:
validated = validate_content(result)
if not validated:
return (False, {"error": "Invalid content", "code": "CONTENT_ERROR"})
# Step 3: Format validation
formatted = format_output(validated)
return (True, formatted)
except Exception as e:
return (False, {
"error": str(e),
"code": "VALIDATION_ERROR",
"context": {"step": "content_validation"}
})
```
### Handling Guardrail Results
When a guardrail returns `(False, error)`:
1. The error is sent back to the agent
2. The agent attempts to fix the issue
3. The process repeats until:
- The guardrail returns `(True, result)`
- Maximum retries are reached
Example with retry handling:
```python Code
from typing import Optional, Tuple, Union
def validate_json_output(result: str) -> Tuple[bool, Union[Dict[str, Any], str]]:
"""Validate and parse JSON output."""
try:
# Try to parse as JSON
data = json.loads(result)
return (True, data)
except json.JSONDecodeError as e:
return (False, {
"error": "Invalid JSON format",
"code": "JSON_ERROR",
"context": {"line": e.lineno, "column": e.colno}
})
task = Task(
description="Generate a JSON report",
expected_output="A valid JSON object",
agent=analyst,
guardrail=validate_json_output,
max_retries=3 # Limit retry attempts
)
```
## Getting Structured Consistent Outputs from Tasks
When you need to ensure that a task outputs a structured and consistent format, you can use the `output_pydantic` or `output_json` properties on a task. These properties allow you to define the expected output structure, making it easier to parse and utilize the results in your application.
<Note>
It's also important to note that the output of the final task of a crew becomes the final output of the actual crew itself.
@@ -608,6 +748,114 @@ While creating and executing tasks, certain validation mechanisms are in place t
These validations help in maintaining the consistency and reliability of task executions within the crewAI framework.
## Task Guardrails
Task guardrails provide a powerful way to validate, transform, or filter task outputs before they are passed to the next task. Guardrails are optional functions that execute before the next task starts, allowing you to ensure that task outputs meet specific requirements or formats.
### Basic Usage
```python Code
from typing import Tuple, Union
from crewai import Task
def validate_json_output(result: str) -> Tuple[bool, Union[dict, str]]:
"""Validate that the output is valid JSON."""
try:
json_data = json.loads(result)
return (True, json_data)
except json.JSONDecodeError:
return (False, "Output must be valid JSON")
task = Task(
description="Generate JSON data",
expected_output="Valid JSON object",
guardrail=validate_json_output
)
```
### How Guardrails Work
1. **Optional Attribute**: Guardrails are an optional attribute at the task level, allowing you to add validation only where needed.
2. **Execution Timing**: The guardrail function is executed before the next task starts, ensuring valid data flow between tasks.
3. **Return Format**: Guardrails must return a tuple of `(success, data)`:
- If `success` is `True`, `data` is the validated/transformed result
- If `success` is `False`, `data` is the error message
4. **Result Routing**:
- On success (`True`), the result is automatically passed to the next task
- On failure (`False`), the error is sent back to the agent to generate a new answer
### Common Use Cases
#### Data Format Validation
```python Code
def validate_email_format(result: str) -> Tuple[bool, Union[str, str]]:
"""Ensure the output contains a valid email address."""
import re
email_pattern = r'^[\w\.-]+@[\w\.-]+\.\w+$'
if re.match(email_pattern, result.strip()):
return (True, result.strip())
return (False, "Output must be a valid email address")
```
#### Content Filtering
```python Code
def filter_sensitive_info(result: str) -> Tuple[bool, Union[str, str]]:
"""Remove or validate sensitive information."""
sensitive_patterns = ['SSN:', 'password:', 'secret:']
for pattern in sensitive_patterns:
if pattern.lower() in result.lower():
return (False, f"Output contains sensitive information ({pattern})")
return (True, result)
```
#### Data Transformation
```python Code
def normalize_phone_number(result: str) -> Tuple[bool, Union[str, str]]:
"""Ensure phone numbers are in a consistent format."""
import re
digits = re.sub(r'\D', '', result)
if len(digits) == 10:
formatted = f"({digits[:3]}) {digits[3:6]}-{digits[6:]}"
return (True, formatted)
return (False, "Output must be a 10-digit phone number")
```
### Advanced Features
#### Chaining Multiple Validations
```python Code
def chain_validations(*validators):
"""Chain multiple validators together."""
def combined_validator(result):
for validator in validators:
success, data = validator(result)
if not success:
return (False, data)
result = data
return (True, result)
return combined_validator
# Usage
task = Task(
description="Get user contact info",
expected_output="Email and phone",
guardrail=chain_validations(
validate_email_format,
filter_sensitive_info
)
)
```
#### Custom Retry Logic
```python Code
task = Task(
description="Generate data",
expected_output="Valid data",
guardrail=validate_data,
max_retries=5 # Override default retry limit
)
```
## Creating Directories when Saving Files
You can now specify if a task should create directories when saving its output to a file. This is particularly useful for organizing outputs and ensuring that file paths are correctly structured.
@@ -629,7 +877,7 @@ save_output_task = Task(
## Conclusion
Tasks are the driving force behind the actions of agents in CrewAI.
By properly defining tasks and their outcomes, you set the stage for your AI agents to work effectively, either independently or as a collaborative unit.
Equipping tasks with appropriate tools, understanding the execution process, and following robust validation practices are crucial for maximizing CrewAI's potential,
Tasks are the driving force behind the actions of agents in CrewAI.
By properly defining tasks and their outcomes, you set the stage for your AI agents to work effectively, either independently or as a collaborative unit.
Equipping tasks with appropriate tools, understanding the execution process, and following robust validation practices are crucial for maximizing CrewAI's potential,
ensuring agents are effectively prepared for their assignments and that tasks are executed as intended.

View File

@@ -18,3 +18,6 @@ test = "{{folder_name}}.main:test"
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[tool.crewai]
type = "crew"

View File

@@ -5,7 +5,7 @@ from pydantic import BaseModel
from crewai.flow.flow import Flow, listen, start
from .crews.poem_crew.poem_crew import PoemCrew
from {{folder_name}}.crews.poem_crew.poem_crew import PoemCrew
class PoemState(BaseModel):

View File

@@ -15,3 +15,6 @@ plot = "{{folder_name}}.main:plot"
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[tool.crewai]
type = "flow"

View File

@@ -8,3 +8,5 @@ dependencies = [
"crewai[tools]>=0.86.0"
]
[tool.crewai]
type = "tool"

View File

@@ -44,6 +44,7 @@ LLM_CONTEXT_WINDOW_SIZES = {
"o1-preview": 128000,
"o1-mini": 128000,
# gemini
"gemini-2.0-flash": 1048576,
"gemini-1.5-pro": 2097152,
"gemini-1.5-flash": 1048576,
"gemini-1.5-flash-8b": 1048576,

View File

@@ -1,12 +1,25 @@
import datetime
import inspect
import json
import logging
import threading
import uuid
from concurrent.futures import Future
from copy import copy
from hashlib import md5
from pathlib import Path
from typing import Any, Dict, List, Optional, Set, Tuple, Type, Union
from typing import (
Any,
Callable,
ClassVar,
Dict,
List,
Optional,
Set,
Tuple,
Type,
Union,
)
from opentelemetry.trace import Span
from pydantic import (
@@ -20,6 +33,7 @@ from pydantic import (
from pydantic_core import PydanticCustomError
from crewai.agents.agent_builder.base_agent import BaseAgent
from crewai.tasks.guardrail_result import GuardrailResult
from crewai.tasks.output_format import OutputFormat
from crewai.tasks.task_output import TaskOutput
from crewai.telemetry.telemetry import Telemetry
@@ -49,6 +63,7 @@ class Task(BaseModel):
"""
__hash__ = object.__hash__ # type: ignore
logger: ClassVar[logging.Logger] = logging.getLogger(__name__)
used_tools: int = 0
tools_errors: int = 0
delegations: int = 0
@@ -110,6 +125,55 @@ class Task(BaseModel):
default=None,
)
processed_by_agents: Set[str] = Field(default_factory=set)
guardrail: Optional[Callable[[TaskOutput], Tuple[bool, Any]]] = Field(
default=None,
description="Function to validate task output before proceeding to next task"
)
max_retries: int = Field(
default=3,
description="Maximum number of retries when guardrail fails"
)
retry_count: int = Field(
default=0,
description="Current number of retries"
)
@field_validator("guardrail")
@classmethod
def validate_guardrail_function(cls, v: Optional[Callable]) -> Optional[Callable]:
"""Validate that the guardrail function has the correct signature and behavior.
While type hints provide static checking, this validator ensures runtime safety by:
1. Verifying the function accepts exactly one parameter (the TaskOutput)
2. Checking return type annotations match Tuple[bool, Any] if present
3. Providing clear, immediate error messages for debugging
This runtime validation is crucial because:
- Type hints are optional and can be ignored at runtime
- Function signatures need immediate validation before task execution
- Clear error messages help users debug guardrail implementation issues
Args:
v: The guardrail function to validate
Returns:
The validated guardrail function
Raises:
ValueError: If the function signature is invalid or return annotation
doesn't match Tuple[bool, Any]
"""
if v is not None:
sig = inspect.signature(v)
if len(sig.parameters) != 1:
raise ValueError("Guardrail function must accept exactly one parameter")
# Check return annotation if present, but don't require it
return_annotation = sig.return_annotation
if return_annotation != inspect.Signature.empty:
if not (return_annotation == Tuple[bool, Any] or str(return_annotation) == 'Tuple[bool, Any]'):
raise ValueError("If return type is annotated, it must be Tuple[bool, Any]")
return v
_telemetry: Telemetry = PrivateAttr(default_factory=Telemetry)
_execution_span: Optional[Span] = PrivateAttr(default=None)
@@ -254,7 +318,6 @@ class Task(BaseModel):
)
pydantic_output, json_output = self._export_output(result)
task_output = TaskOutput(
name=self.name,
description=self.description,
@@ -265,6 +328,37 @@ class Task(BaseModel):
agent=agent.role,
output_format=self._get_output_format(),
)
if self.guardrail:
guardrail_result = GuardrailResult.from_tuple(self.guardrail(task_output))
if not guardrail_result.success:
if self.retry_count >= self.max_retries:
raise Exception(
f"Task failed guardrail validation after {self.max_retries} retries. "
f"Last error: {guardrail_result.error}"
)
self.retry_count += 1
context = (
f"### Previous attempt failed validation: {guardrail_result.error}\n\n\n"
f"### Previous result:\n{task_output.raw}\n\n\n"
"Try again, making sure to address the validation error."
)
return self._execute_core(agent, context, tools)
if guardrail_result.result is None:
raise Exception(
"Task guardrail returned None as result. This is not allowed."
)
if isinstance(guardrail_result.result, str):
task_output.raw = guardrail_result.result
pydantic_output, json_output = self._export_output(guardrail_result.result)
task_output.pydantic = pydantic_output
task_output.json_dict = json_output
elif isinstance(guardrail_result.result, TaskOutput):
task_output = guardrail_result.result
self.output = task_output
self._set_end_execution_time(start_time)
@@ -308,7 +402,18 @@ class Task(BaseModel):
if inputs:
self.description = self._original_description.format(**inputs)
self.expected_output = self._original_expected_output.format(**inputs)
self.expected_output = self.interpolate_only(
input_string=self._original_expected_output, inputs=inputs
)
def interpolate_only(self, input_string: str, inputs: Dict[str, Any]) -> str:
"""Interpolate placeholders (e.g., {key}) in a string while leaving JSON untouched."""
escaped_string = input_string.replace("{", "{{").replace("}", "}}")
for key in inputs.keys():
escaped_string = escaped_string.replace(f"{{{{{key}}}}}", f"{{{key}}}")
return escaped_string.format(**inputs)
def increment_tools_errors(self) -> None:
"""Increment the tools errors counter."""
@@ -390,22 +495,33 @@ class Task(BaseModel):
return OutputFormat.RAW
def _save_file(self, result: Any) -> None:
"""Save task output to a file.
Args:
result: The result to save to the file. Can be a dict or any stringifiable object.
Raises:
ValueError: If output_file is not set
RuntimeError: If there is an error writing to the file
"""
if self.output_file is None:
raise ValueError("output_file is not set.")
resolved_path = Path(self.output_file).expanduser().resolve()
directory = resolved_path.parent
try:
resolved_path = Path(self.output_file).expanduser().resolve()
directory = resolved_path.parent
if not directory.exists():
directory.mkdir(parents=True, exist_ok=True)
if not directory.exists():
directory.mkdir(parents=True, exist_ok=True)
with resolved_path.open("w", encoding="utf-8") as file:
if isinstance(result, dict):
import json
json.dump(result, file, ensure_ascii=False, indent=2)
else:
file.write(str(result))
with resolved_path.open("w", encoding="utf-8") as file:
if isinstance(result, dict):
import json
json.dump(result, file, ensure_ascii=False, indent=2)
else:
file.write(str(result))
except (OSError, IOError) as e:
raise RuntimeError(f"Failed to save output file: {e}")
return None
def __repr__(self):

View File

@@ -0,0 +1,56 @@
"""
Module for handling task guardrail validation results.
This module provides the GuardrailResult class which standardizes
the way task guardrails return their validation results.
"""
from typing import Any, Optional, Tuple, Union
from pydantic import BaseModel, field_validator
class GuardrailResult(BaseModel):
"""Result from a task guardrail execution.
This class standardizes the return format of task guardrails,
converting tuple responses into a structured format that can
be easily handled by the task execution system.
Attributes:
success (bool): Whether the guardrail validation passed
result (Any, optional): The validated/transformed result if successful
error (str, optional): Error message if validation failed
"""
success: bool
result: Optional[Any] = None
error: Optional[str] = None
@field_validator("result", "error")
@classmethod
def validate_result_error_exclusivity(cls, v: Any, info) -> Any:
values = info.data
if "success" in values:
if values["success"] and v and "error" in values and values["error"]:
raise ValueError("Cannot have both result and error when success is True")
if not values["success"] and v and "result" in values and values["result"]:
raise ValueError("Cannot have both result and error when success is False")
return v
@classmethod
def from_tuple(cls, result: Tuple[bool, Union[Any, str]]) -> "GuardrailResult":
"""Create a GuardrailResult from a validation tuple.
Args:
result: A tuple of (success, data) where data is either
the validated result or error message.
Returns:
GuardrailResult: A new instance with the tuple data.
"""
success, data = result
return cls(
success=success,
result=data if success else None,
error=data if not success else None
)

View File

@@ -12,7 +12,7 @@
"tools": "\nYou ONLY have access to the following tools, and should NEVER make up tools that are not listed here:\n\n{tools}\n\nUse the following format:\n\nThought: you should always think about what to do\nAction: the action to take, only one name of [{tool_names}], just the name, exactly as it's written.\nAction Input: the input to the action, just a simple python dictionary, enclosed in curly braces, using \" to wrap keys and values.\nObservation: the result of the action\n\nOnce all necessary information is gathered:\n\nThought: I now know the final answer\nFinal Answer: the final answer to the original input question\n",
"no_tools": "\nTo give my best complete final answer to the task use the exact following format:\n\nThought: I now can give a great answer\nFinal Answer: Your final answer must be the great and the most complete as possible, it must be outcome described.\n\nI MUST use these formats, my job depends on it!",
"format": "I MUST either use a tool (use one at time) OR give my best final answer not both at the same time. To Use the following format:\n\nThought: you should always think about what to do\nAction: the action to take, should be one of [{tool_names}]\nAction Input: the input to the action, dictionary enclosed in curly braces\nObservation: the result of the action\n... (this Thought/Action/Action Input/Result can repeat N times)\nThought: I now can give a great answer\nFinal Answer: Your final answer must be the great and the most complete as possible, it must be outcome described\n\n",
"final_answer_format": "If you don't need to use any more tools, you must give your best complete final answer, make sure it satisfy the expect criteria, use the EXACT format below:\n\nThought: I now can give a great answer\nFinal Answer: my best complete final answer to the task.\n\n",
"final_answer_format": "If you don't need to use any more tools, you must give your best complete final answer, make sure it satisfies the expected criteria, use the EXACT format below:\n\nThought: I now can give a great answer\nFinal Answer: my best complete final answer to the task.\n\n",
"format_without_tools": "\nSorry, I didn't use the right format. I MUST either use a tool (among the available ones), OR give my best final answer.\nI just remembered the expected format I must follow:\n\nQuestion: the input question you must answer\nThought: you should always think about what to do\nAction: the action to take, should be one of [{tool_names}]\nAction Input: the input to the action\nObservation: the result of the action\n... (this Thought/Action/Action Input/Result can repeat N times)\nThought: I now can give a great answer\nFinal Answer: Your final answer must be the great and the most complete as possible, it must be outcome described\n\n",
"task_with_context": "{task}\n\nThis is the context you're working with:\n{context}",
"expected_output": "\nThis is the expect criteria for your final answer: {expected_output}\nyou MUST return the actual complete content as the final answer, not a summary.",

View File

@@ -26237,7 +26237,7 @@ interactions:
answer."}, {"role": "user", "content": "I did it wrong. Invalid Format: I missed
the ''Action:'' after ''Thought:''. I will do right next, and don''t use a tool
I have already used.\n\nIf you don''t need to use any more tools, you must give
your best complete final answer, make sure it satisfy the expect criteria, use
your best complete final answer, make sure it satisfies the expected criteria, use
the EXACT format below:\n\nThought: I now can give a great answer\nFinal Answer:
my best complete final answer to the task.\n\n"}], "model": "gpt-4o"}'
headers:
@@ -26590,7 +26590,7 @@ interactions:
answer."}, {"role": "user", "content": "I did it wrong. Invalid Format: I missed
the ''Action:'' after ''Thought:''. I will do right next, and don''t use a tool
I have already used.\n\nIf you don''t need to use any more tools, you must give
your best complete final answer, make sure it satisfy the expect criteria, use
your best complete final answer, make sure it satisfies the expected criteria, use
the EXACT format below:\n\nThought: I now can give a great answer\nFinal Answer:
my best complete final answer to the task.\n\n"}, {"role": "user", "content":
"I did it wrong. Tried to both perform Action and give a Final Answer at the
@@ -26941,7 +26941,7 @@ interactions:
answer."}, {"role": "user", "content": "I did it wrong. Invalid Format: I missed
the ''Action:'' after ''Thought:''. I will do right next, and don''t use a tool
I have already used.\n\nIf you don''t need to use any more tools, you must give
your best complete final answer, make sure it satisfy the expect criteria, use
your best complete final answer, make sure it satisfies the expected criteria, use
the EXACT format below:\n\nThought: I now can give a great answer\nFinal Answer:
my best complete final answer to the task.\n\n"}, {"role": "user", "content":
"I did it wrong. Tried to both perform Action and give a Final Answer at the
@@ -27292,7 +27292,7 @@ interactions:
answer."}, {"role": "user", "content": "I did it wrong. Invalid Format: I missed
the ''Action:'' after ''Thought:''. I will do right next, and don''t use a tool
I have already used.\n\nIf you don''t need to use any more tools, you must give
your best complete final answer, make sure it satisfy the expect criteria, use
your best complete final answer, make sure it satisfies the expected criteria, use
the EXACT format below:\n\nThought: I now can give a great answer\nFinal Answer:
my best complete final answer to the task.\n\n"}, {"role": "user", "content":
"I did it wrong. Tried to both perform Action and give a Final Answer at the
@@ -27647,7 +27647,7 @@ interactions:
answer."}, {"role": "user", "content": "I did it wrong. Invalid Format: I missed
the ''Action:'' after ''Thought:''. I will do right next, and don''t use a tool
I have already used.\n\nIf you don''t need to use any more tools, you must give
your best complete final answer, make sure it satisfy the expect criteria, use
your best complete final answer, make sure it satisfies the expected criteria, use
the EXACT format below:\n\nThought: I now can give a great answer\nFinal Answer:
my best complete final answer to the task.\n\n"}, {"role": "user", "content":
"I did it wrong. Tried to both perform Action and give a Final Answer at the
@@ -28005,7 +28005,7 @@ interactions:
answer."}, {"role": "user", "content": "I did it wrong. Invalid Format: I missed
the ''Action:'' after ''Thought:''. I will do right next, and don''t use a tool
I have already used.\n\nIf you don''t need to use any more tools, you must give
your best complete final answer, make sure it satisfy the expect criteria, use
your best complete final answer, make sure it satisfies the expected criteria, use
the EXACT format below:\n\nThought: I now can give a great answer\nFinal Answer:
my best complete final answer to the task.\n\n"}, {"role": "user", "content":
"I did it wrong. Tried to both perform Action and give a Final Answer at the
@@ -28364,7 +28364,7 @@ interactions:
answer."}, {"role": "user", "content": "I did it wrong. Invalid Format: I missed
the ''Action:'' after ''Thought:''. I will do right next, and don''t use a tool
I have already used.\n\nIf you don''t need to use any more tools, you must give
your best complete final answer, make sure it satisfy the expect criteria, use
your best complete final answer, make sure it satisfies the expected criteria, use
the EXACT format below:\n\nThought: I now can give a great answer\nFinal Answer:
my best complete final answer to the task.\n\n"}, {"role": "user", "content":
"I did it wrong. Tried to both perform Action and give a Final Answer at the
@@ -28718,7 +28718,7 @@ interactions:
answer."}, {"role": "user", "content": "I did it wrong. Invalid Format: I missed
the ''Action:'' after ''Thought:''. I will do right next, and don''t use a tool
I have already used.\n\nIf you don''t need to use any more tools, you must give
your best complete final answer, make sure it satisfy the expect criteria, use
your best complete final answer, make sure it satisfies the expected criteria, use
the EXACT format below:\n\nThought: I now can give a great answer\nFinal Answer:
my best complete final answer to the task.\n\n"}, {"role": "user", "content":
"I did it wrong. Tried to both perform Action and give a Final Answer at the
@@ -29082,7 +29082,7 @@ interactions:
answer."}, {"role": "user", "content": "I did it wrong. Invalid Format: I missed
the ''Action:'' after ''Thought:''. I will do right next, and don''t use a tool
I have already used.\n\nIf you don''t need to use any more tools, you must give
your best complete final answer, make sure it satisfy the expect criteria, use
your best complete final answer, make sure it satisfies the expected criteria, use
the EXACT format below:\n\nThought: I now can give a great answer\nFinal Answer:
my best complete final answer to the task.\n\n"}, {"role": "user", "content":
"I did it wrong. Tried to both perform Action and give a Final Answer at the
@@ -29441,7 +29441,7 @@ interactions:
answer."}, {"role": "user", "content": "I did it wrong. Invalid Format: I missed
the ''Action:'' after ''Thought:''. I will do right next, and don''t use a tool
I have already used.\n\nIf you don''t need to use any more tools, you must give
your best complete final answer, make sure it satisfy the expect criteria, use
your best complete final answer, make sure it satisfies the expected criteria, use
the EXACT format below:\n\nThought: I now can give a great answer\nFinal Answer:
my best complete final answer to the task.\n\n"}, {"role": "user", "content":
"I did it wrong. Tried to both perform Action and give a Final Answer at the
@@ -29802,7 +29802,7 @@ interactions:
answer."}, {"role": "user", "content": "I did it wrong. Invalid Format: I missed
the ''Action:'' after ''Thought:''. I will do right next, and don''t use a tool
I have already used.\n\nIf you don''t need to use any more tools, you must give
your best complete final answer, make sure it satisfy the expect criteria, use
your best complete final answer, make sure it satisfies the expected criteria, use
the EXACT format below:\n\nThought: I now can give a great answer\nFinal Answer:
my best complete final answer to the task.\n\n"}, {"role": "user", "content":
"I did it wrong. Tried to both perform Action and give a Final Answer at the
@@ -30170,7 +30170,7 @@ interactions:
answer."}, {"role": "user", "content": "I did it wrong. Invalid Format: I missed
the ''Action:'' after ''Thought:''. I will do right next, and don''t use a tool
I have already used.\n\nIf you don''t need to use any more tools, you must give
your best complete final answer, make sure it satisfy the expect criteria, use
your best complete final answer, make sure it satisfies the expected criteria, use
the EXACT format below:\n\nThought: I now can give a great answer\nFinal Answer:
my best complete final answer to the task.\n\n"}, {"role": "user", "content":
"I did it wrong. Tried to both perform Action and give a Final Answer at the
@@ -30533,7 +30533,7 @@ interactions:
answer."}, {"role": "user", "content": "I did it wrong. Invalid Format: I missed
the ''Action:'' after ''Thought:''. I will do right next, and don''t use a tool
I have already used.\n\nIf you don''t need to use any more tools, you must give
your best complete final answer, make sure it satisfy the expect criteria, use
your best complete final answer, make sure it satisfies the expected criteria, use
the EXACT format below:\n\nThought: I now can give a great answer\nFinal Answer:
my best complete final answer to the task.\n\n"}, {"role": "user", "content":
"I did it wrong. Tried to both perform Action and give a Final Answer at the
@@ -30907,7 +30907,7 @@ interactions:
answer."}, {"role": "user", "content": "I did it wrong. Invalid Format: I missed
the ''Action:'' after ''Thought:''. I will do right next, and don''t use a tool
I have already used.\n\nIf you don''t need to use any more tools, you must give
your best complete final answer, make sure it satisfy the expect criteria, use
your best complete final answer, make sure it satisfies the expected criteria, use
the EXACT format below:\n\nThought: I now can give a great answer\nFinal Answer:
my best complete final answer to the task.\n\n"}, {"role": "user", "content":
"I did it wrong. Tried to both perform Action and give a Final Answer at the
@@ -31273,7 +31273,7 @@ interactions:
answer."}, {"role": "user", "content": "I did it wrong. Invalid Format: I missed
the ''Action:'' after ''Thought:''. I will do right next, and don''t use a tool
I have already used.\n\nIf you don''t need to use any more tools, you must give
your best complete final answer, make sure it satisfy the expect criteria, use
your best complete final answer, make sure it satisfies the expected criteria, use
the EXACT format below:\n\nThought: I now can give a great answer\nFinal Answer:
my best complete final answer to the task.\n\n"}, {"role": "user", "content":
"I did it wrong. Tried to both perform Action and give a Final Answer at the
@@ -31644,7 +31644,7 @@ interactions:
answer."}, {"role": "user", "content": "I did it wrong. Invalid Format: I missed
the ''Action:'' after ''Thought:''. I will do right next, and don''t use a tool
I have already used.\n\nIf you don''t need to use any more tools, you must give
your best complete final answer, make sure it satisfy the expect criteria, use
your best complete final answer, make sure it satisfies the expected criteria, use
the EXACT format below:\n\nThought: I now can give a great answer\nFinal Answer:
my best complete final answer to the task.\n\n"}, {"role": "user", "content":
"I did it wrong. Tried to both perform Action and give a Final Answer at the
@@ -32015,7 +32015,7 @@ interactions:
answer."}, {"role": "user", "content": "I did it wrong. Invalid Format: I missed
the ''Action:'' after ''Thought:''. I will do right next, and don''t use a tool
I have already used.\n\nIf you don''t need to use any more tools, you must give
your best complete final answer, make sure it satisfy the expect criteria, use
your best complete final answer, make sure it satisfies the expected criteria, use
the EXACT format below:\n\nThought: I now can give a great answer\nFinal Answer:
my best complete final answer to the task.\n\n"}, {"role": "user", "content":
"I did it wrong. Tried to both perform Action and give a Final Answer at the

View File

@@ -247,7 +247,7 @@ interactions:
{"role": "user", "content": "I did it wrong. Invalid Format: I missed the ''Action:''
after ''Thought:''. I will do right next, and don''t use a tool I have already
used.\n\nIf you don''t need to use any more tools, you must give your best complete
final answer, make sure it satisfy the expect criteria, use the EXACT format
final answer, make sure it satisfies the expected criteria, use the EXACT format
below:\n\nThought: I now can give a great answer\nFinal Answer: my best complete
final answer to the task.\n\n"}], "model": "o1-preview"}'
headers:

View File

@@ -736,6 +736,48 @@ def test_interpolate_inputs():
assert task.expected_output == "Bullet point list of 5 interesting ideas about ML."
def test_interpolate_only():
"""Test the interpolate_only method for various scenarios including JSON structure preservation."""
task = Task(
description="Unused in this test",
expected_output="Unused in this test"
)
# Test JSON structure preservation
json_string = '{"info": "Look at {placeholder}", "nested": {"val": "{nestedVal}"}}'
result = task.interpolate_only(
input_string=json_string,
inputs={"placeholder": "the data", "nestedVal": "something else"}
)
assert '"info": "Look at the data"' in result
assert '"val": "something else"' in result
assert "{placeholder}" not in result
assert "{nestedVal}" not in result
# Test normal string interpolation
normal_string = "Hello {name}, welcome to {place}!"
result = task.interpolate_only(
input_string=normal_string,
inputs={"name": "John", "place": "CrewAI"}
)
assert result == "Hello John, welcome to CrewAI!"
# Test empty string
result = task.interpolate_only(
input_string="",
inputs={"unused": "value"}
)
assert result == ""
# Test string with no placeholders
no_placeholders = "Hello, this is a test"
result = task.interpolate_only(
input_string=no_placeholders,
inputs={"unused": "value"}
)
assert result == no_placeholders
def test_task_output_str_with_pydantic():
from crewai.tasks.output_format import OutputFormat

View File

@@ -0,0 +1,134 @@
"""Tests for task guardrails functionality."""
from unittest.mock import Mock
import pytest
from crewai.task import Task
from crewai.tasks.task_output import TaskOutput
def test_task_without_guardrail():
"""Test that tasks work normally without guardrails (backward compatibility)."""
agent = Mock()
agent.role = "test_agent"
agent.execute_task.return_value = "test result"
agent.crew = None
task = Task(
description="Test task",
expected_output="Output"
)
result = task.execute_sync(agent=agent)
assert isinstance(result, TaskOutput)
assert result.raw == "test result"
def test_task_with_successful_guardrail():
"""Test that successful guardrail validation passes transformed result."""
def guardrail(result: TaskOutput):
return (True, result.raw.upper())
agent = Mock()
agent.role = "test_agent"
agent.execute_task.return_value = "test result"
agent.crew = None
task = Task(
description="Test task",
expected_output="Output",
guardrail=guardrail
)
result = task.execute_sync(agent=agent)
assert isinstance(result, TaskOutput)
assert result.raw == "TEST RESULT"
def test_task_with_failing_guardrail():
"""Test that failing guardrail triggers retry with error context."""
def guardrail(result: TaskOutput):
return (False, "Invalid format")
agent = Mock()
agent.role = "test_agent"
agent.execute_task.side_effect = [
"bad result",
"good result"
]
agent.crew = None
task = Task(
description="Test task",
expected_output="Output",
guardrail=guardrail,
max_retries=1
)
# First execution fails guardrail, second succeeds
agent.execute_task.side_effect = ["bad result", "good result"]
with pytest.raises(Exception) as exc_info:
task.execute_sync(agent=agent)
assert "Task failed guardrail validation" in str(exc_info.value)
assert task.retry_count == 1
def test_task_with_guardrail_retries():
"""Test that guardrail respects max_retries configuration."""
def guardrail(result: TaskOutput):
return (False, "Invalid format")
agent = Mock()
agent.role = "test_agent"
agent.execute_task.return_value = "bad result"
agent.crew = None
task = Task(
description="Test task",
expected_output="Output",
guardrail=guardrail,
max_retries=2
)
with pytest.raises(Exception) as exc_info:
task.execute_sync(agent=agent)
assert task.retry_count == 2
assert "Task failed guardrail validation after 2 retries" in str(exc_info.value)
assert "Invalid format" in str(exc_info.value)
def test_guardrail_error_in_context():
"""Test that guardrail error is passed in context for retry."""
def guardrail(result: TaskOutput):
return (False, "Expected JSON, got string")
agent = Mock()
agent.role = "test_agent"
agent.crew = None
task = Task(
description="Test task",
expected_output="Output",
guardrail=guardrail,
max_retries=1
)
# Mock execute_task to succeed on second attempt
first_call = True
def execute_task(task, context, tools):
nonlocal first_call
if first_call:
first_call = False
return "invalid"
return '{"valid": "json"}'
agent.execute_task.side_effect = execute_task
with pytest.raises(Exception) as exc_info:
task.execute_sync(agent=agent)
assert "Task failed guardrail validation" in str(exc_info.value)
assert "Expected JSON, got string" in str(exc_info.value)