Skip to content

Commit

Permalink
Merge branch 'main' into feat/docling
Browse files Browse the repository at this point in the history
  • Loading branch information
bhancockio authored Dec 23, 2024
2 parents 9dda698 + c887ff1 commit abdc713
Show file tree
Hide file tree
Showing 2 changed files with 92 additions and 15 deletions.
65 changes: 50 additions & 15 deletions src/crewai/task.py
Original file line number Diff line number Diff line change
@@ -1,13 +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, Callable, 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 (
Expand Down Expand Up @@ -51,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
Expand Down Expand Up @@ -389,7 +402,18 @@ def interpolate_inputs(self, inputs: Dict[str, Any]) -> None:

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."""
Expand Down Expand Up @@ -471,22 +495,33 @@ def _get_output_format(self) -> OutputFormat:
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

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))
try:
resolved_path = Path(self.output_file).expanduser().resolve()
directory = resolved_path.parent

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))
except (OSError, IOError) as e:
raise RuntimeError(f"Failed to save output file: {e}")
return None

def __repr__(self):
Expand Down
42 changes: 42 additions & 0 deletions tests/task_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down

0 comments on commit abdc713

Please sign in to comment.