CrewAI Tutorial (TypeScript): parsing structured output for advanced developers
This tutorial shows how to make a CrewAI TypeScript agent return structured data you can parse reliably instead of scraping free-form text. You need this when downstream code expects strict fields like status, riskScore, or recommendation and you cannot afford brittle string parsing.
What You'll Need
- •Node.js 18+
- •A TypeScript project with
ts-nodeor a build step viatsc - •
crewai - •An LLM API key, for example:
- •
OPENAI_API_KEY
- •
- •A valid CrewAI-compatible model configured in your environment
- •Basic familiarity with:
- •
Agent - •
Task - •
Crew - •async/await in TypeScript
- •
Step-by-Step
- •Start by setting up a typed response shape. The important part is that your agent output matches a schema your application can trust, not just a human-readable summary.
export type ClaimAssessment = {
claimId: string;
status: "approved" | "needs_review" | "rejected";
riskScore: number;
reasons: string[];
};
export const claimSchemaHint = `
Return JSON only with this shape:
{
"claimId": string,
"status": "approved" | "needs_review" | "rejected",
"riskScore": number,
"reasons": string[]
}
`;
- •Create the agent and task with explicit instructions to emit JSON only. The key is to force deterministic structure in the prompt, then parse it as data after execution.
import { Agent, Task } from "crewai";
export const claimsAgent = new Agent({
role: "Claims analyst",
goal: "Assess insurance claims and return structured JSON",
backstory: "You review claim evidence and produce machine-readable outputs.",
});
export const assessClaimTask = new Task({
description: `
Assess this claim:
- claimId: CLM-1042
- amount: 7800
- notes: Water damage reported after heavy rain
${claimSchemaHint}
Do not include markdown, code fences, or extra commentary.
`,
expectedOutput: "Valid JSON matching the schema hint",
agent: claimsAgent,
});
- •Run the crew and parse the result defensively. In production, never assume the model obeyed perfectly; extract the JSON block if needed and validate required fields before using it.
import { Crew, Process } from "crewai";
function extractJson(text: string): string {
const trimmed = text.trim();
const fenced = trimmed.match(/```json\s*([\s\S]*?)\s*```/i);
if (fenced?.[1]) return fenced[1].trim();
const firstBrace = trimmed.indexOf("{");
const lastBrace = trimmed.lastIndexOf("}");
if (firstBrace >= 0 && lastBrace > firstBrace) {
return trimmed.slice(firstBrace, lastBrace + 1);
}
throw new Error("No JSON object found in model output");
}
async function main() {
const crew = new Crew({
agents: [claimsAgent],
tasks: [assessClaimTask],
process: Process.sequential,
verbose: false,
});
const result = await crew.kickoff();
const raw = String(result);
const parsed = JSON.parse(extractJson(raw)) as ClaimAssessment;
console.log(parsed.status, parsed.riskScore, parsed.reasons.length);
}
void main();
- •Add runtime validation before your app trusts the payload. TypeScript types disappear at runtime, so use a real guard to reject malformed responses early.
function isClaimAssessment(value: unknown): value is ClaimAssessment {
if (!value || typeof value !== "object") return false;
const v = value as Record<string, unknown>;
return (
typeof v.claimId === "string" &&
(v.status === "approved" || v.status === "needs_review" || v.status === "rejected") &&
typeof v.riskScore === "number" &&
Array.isArray(v.reasons) &&
v.reasons.every((item) => typeof item === "string")
);
}
const candidate = JSON.parse(extractJson(String(result)));
if (!isClaimAssessment(candidate)) {
throw new Error("Invalid structured output from CrewAI");
}
- •Wire the parsed result into real application logic. Once validated, you can persist it, feed it into rules engines, or hand it off to another service without special casing model text.
type ClaimDecisionRecord = ClaimAssessment & {
reviewedAt: string;
};
function buildDecisionRecord(input: ClaimAssessment): ClaimDecisionRecord {
return {
...input,
reviewedAt: new Date().toISOString(),
};
}
const decisionRecord = buildDecisionRecord(candidate);
console.log(JSON.stringify(decisionRecord, null, 2));
Testing It
Run the script several times and verify that every response parses into the same object shape. If you see markdown fences or extra prose, tighten the prompt and keep the JSON extraction fallback.
Check failure cases too. Force an invalid response by changing the prompt to request plain English, then confirm your parser throws before bad data reaches downstream systems.
For production readiness, add tests around extractJson() and isClaimAssessment(). Those two functions are where most real-world failures show up when models drift or prompts get edited later.
Next Steps
- •Add Zod or another schema validator so your runtime checks are declarative and easier to maintain.
- •Split the workflow into multiple CrewAI tasks where one agent extracts fields and another agent validates them.
- •Store structured outputs in a queue or database table for auditability and replay.
Keep learning
- •The complete AI Agents Roadmap — my full 8-step breakdown
- •Free: The AI Agent Starter Kit — PDF checklist + starter code
- •Work with me — I build AI for banks and insurance companies
By Cyprian Aarons, AI Consultant at Topiax.
Want the complete 8-step roadmap?
Grab the free AI Agent Starter Kit — architecture templates, compliance checklists, and a 7-email deep-dive course.
Get the Starter Kit