JSON Mode & Structured Output

Get structured JSON responses from your Letta agents

Letta provides two ways to get structured JSON output from agents: Structured Generation through Tools (recommended) and the response_format parameter.

Quick Comparison

Recommended: Use Structured Generation through Tools - works with all providers (Anthropic, OpenAI, Google, etc.) and integrates naturally with Letta’s tool-calling architecture.

Structured Generation through Tools:

  • ✅ Universal provider compatibility
  • ✅ Both reasoning AND structured output
  • ✅ Per-message control
  • ✅ Works even as “dummy tool” for pure formatting

response_format parameter:

  • ⚠️ OpenAI-compatible providers only (NOT Anthropic)

  • ⚠️ Persistent agent state (affects all future responses)

  • ✅ Built-in provider schema enforcement

Create a tool that defines your desired response format. The tool arguments become your structured data, and you can extract them from the tool call.

Creating a Structured Generation Tool

1import { LettaClient } from '@letta-ai/letta-client'
2
3// Create client (Letta Cloud)
4const client = new LettaClient({ token: "LETTA_API_KEY" });
5
6// Or for self-hosted
7// const client = new LettaClient({ baseUrl: "http://localhost:8283" });
8
9// First create the tool
10const toolCode = `def generate_rank(rank: int, reason: str):
11 """Generate a ranking with explanation.
12
13 Args:
14 rank (int): The numerical rank from 1-10.
15 reason (str): The reasoning behind the rank.
16 """
17 print("Rank generated")
18 return`;
19
20const tool = await client.tools.create({
21 sourceCode: toolCode,
22 sourceType: "python"
23});
24
25// Create agent with the structured generation tool
26const agentState = await client.agents.create({
27 model: "openai/gpt-4o-mini",
28 embedding: "openai/text-embedding-3-small",
29 memoryBlocks: [
30 {
31 label: "human",
32 value: "The human's name is Chad. They are a food enthusiast who enjoys trying different cuisines."
33 },
34 {
35 label: "persona",
36 value: "I am a helpful food critic assistant. I provide detailed rankings and reviews of different foods and restaurants."
37 }
38 ],
39 toolIds: [tool.id]
40});

Using the Structured Generation Tool

1// Send message and instruct agent to use the tool
2const response = await client.agents.messages.create(
3 agentState.id, {
4 messages: [
5 {
6 role: "user",
7 content: "How do you rank sushi as a food? Please use the generate_rank tool to provide your response."
8 }
9 ]
10 }
11);
12
13// Extract structured data from tool call
14for (const message of response.messages) {
15 if (message.messageType === "tool_call_message") {
16 const args = JSON.parse(message.toolCall.arguments);
17 console.log(`Rank: ${args.rank}`);
18 console.log(`Reason: ${args.reason}`);
19 }
20}
21
22// Example output:
23// Rank: 8
24// Reason: Sushi is a highly regarded cuisine known for its fresh ingredients...

The agent will call the tool, and you can extract the structured arguments:

1{
2 "rank": 8,
3 "reason": "Sushi is a highly regarded cuisine known for its fresh ingredients, artistic presentation, and cultural significance."
4}

Using response_format for Provider-Native JSON Mode

The response_format parameter enables structured output/JSON mode from LLM providers that support it. This approach is fundamentally different from tools because response_format becomes a persistent part of the agent’s state - once set, all future responses from that agent will follow the format until explicitly changed.

Under the hood, response_format constrains the agent’s assistant messages to follow the specified schema, but it doesn’t affect tools - those continue to work normally with their original schemas.

Requirements for response_format:

  • Only works with providers that support structured outputs (like OpenAI) - NOT Anthropic or other providers

Basic JSON Mode

1import { LettaClient } from '@letta-ai/letta-client'
2
3// Create client (Letta Cloud)
4const client = new LettaClient({ token: "LETTA_API_KEY" });
5
6// Create agent with basic JSON mode (OpenAI/compatible providers only)
7const agentState = await client.agents.create({
8 model: "openai/gpt-4o-mini",
9 embedding: "openai/text-embedding-3-small",
10 memoryBlocks: [
11 {
12 label: "human",
13 value: "The human's name is Chad. They work as a data analyst and prefer clear, organized information."
14 },
15 {
16 label: "persona",
17 value: "I am a helpful assistant who provides clear and well-organized responses."
18 }
19 ],
20 responseFormat: { type: "json_object" }
21});
22
23// Send message expecting JSON response
24const response = await client.agents.messages.create(
25 agentState.id, {
26 messages: [
27 {
28 role: "user",
29 content: "How do you rank sushi as a food? Please respond in JSON format with rank and reason fields."
30 }
31 ]
32 }
33);
34
35for (const message of response.messages) {
36 console.log(message);
37}

Advanced JSON Schema Mode

For more precise control, you can use OpenAI’s json_schema mode with strict validation:

1import { LettaClient } from '@letta-ai/letta-client'
2
3const client = new LettaClient({ token: "LETTA_API_KEY" });
4
5// Define structured schema (from OpenAI structured outputs guide)
6const responseFormat = {
7 type: "json_schema",
8 jsonSchema: {
9 name: "food_ranking",
10 schema: {
11 type: "object",
12 properties: {
13 rank: {
14 type: "integer",
15 minimum: 1,
16 maximum: 10
17 },
18 reason: {
19 type: "string"
20 },
21 categories: {
22 type: "array",
23 items: {
24 type: "object",
25 properties: {
26 name: { type: "string" },
27 score: { type: "integer" }
28 },
29 required: ["name", "score"],
30 additionalProperties: false
31 }
32 }
33 },
34 required: ["rank", "reason", "categories"],
35 additionalProperties: false
36 },
37 strict: true
38 }
39};
40
41// Create agent
42const agentState = await client.agents.create({
43 model: "openai/gpt-4o-mini",
44 embedding: "openai/text-embedding-3-small",
45 memoryBlocks: []
46});
47
48// Update agent with response format
49const updatedAgent = await client.agents.update(
50 agentState.id,
51 { responseFormat }
52);
53
54// Send message
55const response = await client.agents.messages.create(
56 agentState.id, {
57 messages: [
58 { role: "user", content: "How do you rank sushi? Include categories for taste, presentation, and value." }
59 ]
60 }
61);
62
63for (const message of response.messages) {
64 console.log(message);
65}

With structured JSON schema, the agent’s response will be strictly validated:

1{
2 "rank": 8,
3 "reason": "Sushi is highly regarded for its fresh ingredients and artful presentation",
4 "categories": [
5 {"name": "taste", "score": 9},
6 {"name": "presentation", "score": 10},
7 {"name": "value", "score": 6}
8 ]
9}

Updating Agent Response Format

You can update an existing agent’s response format:

1// Update agent to use JSON mode (OpenAI/compatible only)
2await client.agents.update(agentState.id, {
3 responseFormat: { type: "json_object" }
4});
5
6// Or remove JSON mode
7await client.agents.update(agentState.id, {
8 responseFormat: null
9});