Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 23 additions & 3 deletions lib/crewai/src/crewai/utilities/converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,24 @@
from crewai.llms.base_llm import BaseLLM

_JSON_PATTERN: Final[re.Pattern[str]] = re.compile(r"({.*})", re.DOTALL)
_CODE_FENCE_PATTERN: Final[re.Pattern[str]] = re.compile(
r"```(?:json)?\s*\n?(.*?)\n?\s*```", re.DOTALL
)
_I18N = get_i18n()


def _strip_code_fences(text: str) -> str:
"""Strip markdown code fences from a string if present.

Handles patterns like ```json\\n{...}\\n``` and ```{...}```.
Returns the inner content if fences are found, otherwise the original text.
"""
match = _CODE_FENCE_PATTERN.search(text)
if match:
return match.group(1).strip()
return text.strip()


class ConverterError(Exception):
"""Error raised when Converter fails to parse the input."""

Expand Down Expand Up @@ -65,7 +80,9 @@ def to_pydantic(self, current_attempt: int = 1) -> BaseModel:
if isinstance(response, BaseModel):
result = response
else:
result = self.model.model_validate_json(response)
result = self.model.model_validate_json(
_strip_code_fences(response)
)
else:
response = self.llm.call(
[
Expand All @@ -74,8 +91,11 @@ def to_pydantic(self, current_attempt: int = 1) -> BaseModel:
]
)
try:
# Try to directly validate the response JSON
result = self.model.model_validate_json(response)
# Try to directly validate the response JSON,
# stripping markdown code fences if present
result = self.model.model_validate_json(
_strip_code_fences(response)
)
except ValidationError:
# If direct validation fails, attempt to extract valid JSON
result = handle_partial_json( # type: ignore[assignment]
Expand Down