Developer tools · · 5 min read
Pydantic: validate AI project briefs before creating tasks
Reject missing fields, misleading types and incomplete integration details with a small, tested Python validator for AI-generated project briefs.
By Sociologix Editorial

A readable brief is not automatically a usable handoff
An AI assistant might turn a customer conversation into a tidy project brief while omitting the service, inventing an approval flag or marking an integration ready without naming its systems. Before that output creates tasks, your application needs a defined acceptance rule.
This AI-assisted editorial guide uses official sources checked October 12, 2026. We ran the original example below with Python 3.13.15 and Pydantic 2.14.0. One complete fixture passed and five deliberately defective fixtures were rejected. The exercise used synthetic data without calling an AI model, CRM or task system.
1. Define what “ready for review” means
Our example requires one supported service, at least one nonblank goal and an explicit integration Boolean. If an integration is requested, its target systems must be named. Those are example business rules for a planning queue, not universal requirements for every inquiry.
Pydantic normally permits some type conversion. Strict mode reduces that coercion, so the example rejects the string “true” where it requires a Boolean. Strict behavior varies by type and input method; JSON date handling, for example, has documented exceptions. Do not assume strict mode means every possible conversion disappears. [1]
The model also forbids undeclared fields. This makes an unexpected “approved” property a visible validation failure instead of silently dropping it. An after-model validator checks the relationship between the integration flag and its targets. [2][3]
2. Install the version used in the example
Use a new folder and a virtual environment so the exercise does not change another project’s dependencies. The PowerShell commands below pin the Pydantic release used for our checks; its published package is available on PyPI. [4]
On macOS or Linux, create the environment with python3 and use .venv/bin/python for subsequent commands. No provider API key is needed. Save both Python blocks that follow, in order, into a file named brief_demo.py.
python -m venv .venv
.venv/Scripts/python.exe -m pip install "pydantic==2.14.0"3. Validate types and relationships together
The reusable Text constraint trims surrounding whitespace and rejects an empty result. The goals list needs at least one item. The integration check returns the validated instance on success and raises ValueError when a requested integration lacks targets, following the documented after-validator pattern. [3]
The systems list defaults to empty. That is acceptable here only when needs_integration is explicitly false. Missing needs_integration does not default to false: it remains a required field. This distinction keeps absent information from quietly becoming a planning decision.
import json
from typing import Annotated, Literal
from pydantic import (
BaseModel, ConfigDict, Field, StringConstraints,
ValidationError, model_validator,
)
Text = Annotated[str, StringConstraints(
strip_whitespace=True, min_length=1,
)]
class ProjectBrief(BaseModel):
model_config = ConfigDict(strict=True, extra="forbid")
service: Literal["website", "ai-agent", "automation"]
goals: list[Text] = Field(min_length=1)
needs_integration: bool
systems: list[Text] = Field(default_factory=list)
@model_validator(mode="after")
def integration_has_targets(self):
if self.needs_integration and not self.systems:
raise ValueError("Name the systems before integration planning")
return self4. Run a small rejection matrix
Append this block, then run .venv/Scripts/python.exe brief_demo.py. model_validate_json validates the JSON text against the model. Do not substitute model_construct: that API skips validation. [2]
Our run accepted “complete” and rejected “string flag,” “extra field,” “blank goal,” “unnamed integration” and “missing service.” The assertions check the accept/reject outcome for each fixture. These checks establish the behavior of these six inputs, not the accuracy of an AI extractor or coverage of every possible payload.
valid = {
"service": "automation",
"goals": ["Route mock requests into a review queue"],
"needs_integration": True,
"systems": ["Demo CRM"],
}
missing_service = {k: v for k, v in valid.items() if k != "service"}
cases = [
("complete", valid, True),
("string flag", {**valid, "needs_integration": "true"}, False),
("extra field", {**valid, "approved": True}, False),
("blank goal", {**valid, "goals": [" "]}, False),
("unnamed integration", {**valid, "systems": []}, False),
("missing service", missing_service, False),
]
for name, payload, expected in cases:
try:
brief = ProjectBrief.model_validate_json(json.dumps(payload))
except ValidationError as exc:
accepted = False
issues = [(e["loc"], e["type"]) for e in exc.errors()]
print(name, "rejected", issues)
else:
accepted = True
print(name, "accepted")
assert accepted == expected, name5. Return a useful correction request
Pydantic exposes structured error details, including a location and error type. The example prints just those two fields rather than the entire submitted payload. A model-level relationship failure can have an empty location because it concerns the whole object. [5]
In an application, map known failures to clear prompts: “Which system should we connect?” is more useful than an internal exception dump. Keep the draft pending until the missing fact is supplied. Preserve the original submission separately from a corrected version so reviewers can see what changed.
If an AI worker retries extraction, send only the necessary error summary and the permitted source text. Limit retries and route unresolved cases to a person. Never invent a CRM name or change true to false merely to make the validator pass. These are our implementation recommendations; the script does not implement a retry service.
6. Keep validation separate from truth and permission
A fabricated system name can still be a valid nonempty string. Compare the extracted facts with the customer’s actual request before treating them as confirmed. Maintain separate states for structurally valid, reviewed and authorized work.
Place this check on the server before downstream task creation. The caller’s identity, customer access, duplicate-request handling and approval to start work need their own controls. Test those separately with a mock task destination before connecting a real project queue. The useful outcome is a clearer handoff with explicit unknowns, not an automatic claim that every accepted brief is correct.
Sources & further reading
Make your AI handoffs easier to review
Sociologix can help connect AI-assisted intake to validated briefs, clarification queues and controlled project handoffs. Bring a sample process and the information your team needs before work can begin.
Talk to Sociologix