fix(task): parse pydantic output before guardrail invocation#4650
fix(task): parse pydantic output before guardrail invocation#4650giulio-leone wants to merge 1 commit intocrewAIInc:mainfrom
Conversation
|
Friendly ping — CI is green and this is ready for review. Happy to address any feedback. Thanks! |
ffac07f to
0a7e9c6
Compare
There was a problem hiding this comment.
Pull request overview
Fixes an inconsistency in Task execution where structured outputs (output_pydantic / output_json) were not exported before guardrails ran on the first attempt, making TaskOutput.pydantic/json_dict unavailable to guardrails until retries.
Changes:
- Update both async (
_aexecute_core) and sync (_execute_core) paths to call_export_output()even when guardrails are configured. - Add a regression test ensuring
TaskOutput.pydanticis populated for guardrails on the first invocation.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
lib/crewai/src/crewai/task.py |
Always exports structured output before invoking guardrails (sync + async). |
lib/crewai/tests/test_task_guardrails.py |
Adds regression test asserting guardrails receive non-None Pydantic output on first attempt. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
e2dd079 to
e8ab7fa
Compare
769f502 to
9a49654
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
lib/crewai/src/crewai/task.py
Outdated
| try: | ||
| pydantic_output, json_output = self._export_output(result) | ||
| except Exception: | ||
| self.logger.debug("Pre-guardrail output export failed, continuing with raw output") |
There was a problem hiding this comment.
Broad try/except silences errors for non-guardrail tasks
Medium Severity
The old code called _export_output() without try/except for tasks without guardrails, allowing errors like ValidationError to propagate to the caller. The new code merges all non-BaseModel results into one else branch with a blanket except Exception, so tasks without guardrails now silently swallow _export_output() failures and produce pydantic=None / json_dict=None instead of raising. This masks legitimate conversion errors (e.g., ValidationError, TypeError from missing agent) that previously surfaced to users. The try/except guard is only needed when guardrails are present.
Additional Locations (1)
9a49654 to
875bb63
Compare
97e4e67 to
47680d2
Compare


Problem
When a task has
output_pydanticand a guardrail configured,TaskOutput.pydanticisNoneon the first guardrail invocation but correctly parsed on retry attempts. This makes it impossible to write guardrail functions that inspect structured Pydantic output.Root Cause
In both
execute_task()(async) andexecute_sync(), when the result is a string and guardrails are present,_export_output()was intentionally skipped:On retries (line ~1155),
_export_output()was always called, creating inconsistent behavior.Fix
Remove the conditional skip so
_export_output()is always called regardless of guardrail presence:Applied to both sync and async execution paths.
Test
Added
test_guardrail_pydantic_output_available_on_first_attemptthat verifies:pydanticon the first (and only) invocationAll 20 existing guardrail tests pass (2 pre-existing failures unrelated to this change).
Fixes #4369
Note
Medium Risk
Touches core task execution output handling (sync and async), which could subtly change when/what structured parsing occurs, but failures are swallowed with a debug log to preserve raw output behavior.
Overview
Guardrails now receive structured outputs on the first pass. In
Tasksync + async execution paths, string results are now always run through_export_output()before any guardrail invocation, soTaskOutput.pydantic/json_dictare populated consistently (instead of only after a retry).Parsing failures are handled defensively (debug log + continue with raw output), and tests were updated/expanded with a regression case (
test_guardrail_pydantic_output_available_on_first_attempt) to lock in the new behavior. A tiny formatting-only change was made increw_agent_executor.py.Written by Cursor Bugbot for commit 47680d2. This will update automatically on new commits. Configure here.