Prompting language models for JSON without schema enforcement introduces parsing failures. Test this behavior in extract_json.py:
extract_json.py
1# extract_json.py23import json
45from crewai import Agent, Crew, Task
6from crewai_tools import FileReadTool
7from dotenv import load_dotenv
89load_dotenv()1011reader = Agent(12 role="Contract analyst",13 goal="Pull every commercially important term out of a contract, with its section number",14 backstory="You read contracts line by line for a procurement team.",15 tools=[FileReadTool()],16)1718extract = Task(19 description="Read the contract at {contract_path} and pull out its key terms.",20 expected_output=(21"A JSON object with these keys: provider, customer, effective_date, "22"initial_term_months, auto_renews, renewal_term_months, notice_days, annual_fee_usd."23),24 agent=reader,25)2627result = Crew(agents=[reader], tasks=[extract]).kickoff(28 inputs={"contract_path":"contracts/acme-saas-agreement.md"}29)30print(result.raw)3132terms = json.loads(result.raw)33print(terms["effective_date"])
Run the script to observe the error:
Terminal
1(env)$ python extract_json.py
The execution fails because the model wraps the JSON string in markdown code fences:
Define typed data schemas with Pydantic in contract_crew/models.py:
contract_crew/models.py
1# contract_crew/models.py23from datetime import date
45from pydantic import BaseModel, Field
678classClause(BaseModel):9 section:str= Field(description="The section number, like 2.2")10 topic:str= Field(description="A short label for the clause, like Auto-renewal")11 text:str= Field(description="The clause, quoted word for word from the contract")121314classContractTerms(BaseModel):15 provider:str= Field(description="Legal name of the company providing the service")16 customer:str= Field(description="Legal name of the customer")17 effective_date: date = Field(description="The date the agreement starts")18 initial_term_months:int= Field(description="Length of the initial term, in months")19 auto_renews:bool= Field(description="True if the agreement renews automatically")20 renewal_term_months:int|None= Field(description="Length of each renewal term in months, or null if it does not renew")21 notice_days:int|None= Field(description="Days of notice needed before a term ends to stop it renewing, or null")22 annual_fee_usd:float= Field(description="The annual fee, in US dollars")23 key_clauses:list[Clause]= Field(24 description="Every clause that affects cost, deadlines, data, or liability, or that lets one party change the deal"25)
Each field description guides the language model during extraction, while Pydantic guarantees type casting and date parsing at runtime.
Attach ContractTerms to extract_terms in contract_crew/crew.py:
Update contract_crew/config/tasks.yaml to focus purely on extraction criteria, since the Pydantic schema defines output structure:
YAML
1extract_terms:2description:>3 Read the contract at {contract_path}. List its key terms: parties, dates,
4 term and renewal, fees and payment, service levels, data handling,
5 termination, liability, and any clause that lets one party change the
6 deal on its own.7expected_output:>8 The contract's key terms, with every important clause quoted word for
9 word along with its section number.10agent: reader