mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-01-09 08:08:32 +00:00
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>
This commit is contained in:
@@ -1,8 +1,10 @@
|
|||||||
import datetime
|
import datetime
|
||||||
import json
|
import json
|
||||||
|
import logging
|
||||||
import threading
|
import threading
|
||||||
import uuid
|
import uuid
|
||||||
from concurrent.futures import Future
|
from concurrent.futures import Future
|
||||||
|
from typing import ClassVar
|
||||||
from copy import copy
|
from copy import copy
|
||||||
from hashlib import md5
|
from hashlib import md5
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -49,6 +51,7 @@ class Task(BaseModel):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
__hash__ = object.__hash__ # type: ignore
|
__hash__ = object.__hash__ # type: ignore
|
||||||
|
logger: ClassVar[logging.Logger] = logging.getLogger(__name__)
|
||||||
used_tools: int = 0
|
used_tools: int = 0
|
||||||
tools_errors: int = 0
|
tools_errors: int = 0
|
||||||
delegations: int = 0
|
delegations: int = 0
|
||||||
@@ -401,22 +404,33 @@ class Task(BaseModel):
|
|||||||
return OutputFormat.RAW
|
return OutputFormat.RAW
|
||||||
|
|
||||||
def _save_file(self, result: Any) -> None:
|
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:
|
if self.output_file is None:
|
||||||
raise ValueError("output_file is not set.")
|
raise ValueError("output_file is not set.")
|
||||||
|
|
||||||
resolved_path = Path(self.output_file).expanduser().resolve()
|
try:
|
||||||
directory = resolved_path.parent
|
resolved_path = Path(self.output_file).expanduser().resolve()
|
||||||
|
directory = resolved_path.parent
|
||||||
|
|
||||||
if not directory.exists():
|
if not directory.exists():
|
||||||
directory.mkdir(parents=True, exist_ok=True)
|
directory.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
with resolved_path.open("w", encoding="utf-8") as file:
|
with resolved_path.open("w", encoding="utf-8") as file:
|
||||||
if isinstance(result, dict):
|
if isinstance(result, dict):
|
||||||
import json
|
import json
|
||||||
|
json.dump(result, file, ensure_ascii=False, indent=2)
|
||||||
json.dump(result, file, ensure_ascii=False, indent=2)
|
else:
|
||||||
else:
|
file.write(str(result))
|
||||||
file.write(str(result))
|
except (OSError, IOError) as e:
|
||||||
|
raise RuntimeError(f"Failed to save output file: {e}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
|
|||||||
@@ -736,6 +736,48 @@ def test_interpolate_inputs():
|
|||||||
assert task.expected_output == "Bullet point list of 5 interesting ideas about ML."
|
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():
|
def test_task_output_str_with_pydantic():
|
||||||
from crewai.tasks.output_format import OutputFormat
|
from crewai.tasks.output_format import OutputFormat
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user