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)
  • ⚠️ Requires send_message tool to be attached
  • ✅ 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

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

Using the Structured Generation Tool

1# Send message and instruct agent to use the tool
2response = client.agents.messages.create(
3 agent_id=agent_state.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# Extract structured data from tool call
13for message in response.messages:
14 if message.message_type == "tool_call_message":
15 import json
16 args = json.loads(message.tool_call.arguments)
17 rank = args["rank"]
18 reason = args["reason"]
19 print(f"Rank: {rank}")
20 print(f"Reason: {reason}")
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 overrides the schema for the send_message tool (which appears as AssistantMessage in the API), but it doesn’t affect other 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
  • The send_message tool must be attached to the agent (it’s included by default but can be detached)

Basic JSON Mode

1from letta_client import Letta
2
3# Create client (Letta Cloud)
4client = Letta(token="LETTA_API_KEY")
5
6# Create agent with basic JSON mode (OpenAI/compatible providers only)
7agent_state = client.agents.create(
8 model="openai/gpt-4o-mini",
9 embedding="openai/text-embedding-3-small",
10 memory_blocks=[
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 response_format={"type": "json_object"}
21)
22
23# Send message expecting JSON response
24response = client.agents.messages.create(
25 agent_id=agent_state.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
34for message in response.messages:
35 print(message)

Advanced JSON Schema Mode

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

1from letta_client import Letta
2
3client = Letta(token="LETTA_API_KEY")
4
5# Define structured schema (from OpenAI structured outputs guide)
6response_format = {
7 "type": "json_schema",
8 "json_schema": {
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
42agent_state = client.agents.create(
43 model="openai/gpt-4o-mini",
44 embedding="openai/text-embedding-3-small",
45 memory_blocks=[]
46)
47
48# Update agent with response format
49agent_state = client.agents.update(
50 agent_id=agent_state.id,
51 response_format=response_format
52)
53
54# Send message
55response = client.agents.messages.create(
56 agent_id=agent_state.id,
57 messages=[
58 {"role": "user", "content": "How do you rank sushi? Include categories for taste, presentation, and value."}
59 ]
60)
61
62for message in response.messages:
63 print(message)

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)
2client.agents.update(
3 agent_id=agent_state.id,
4 response_format={"type": "json_object"}
5)
6
7# Or remove JSON mode
8client.agents.update(
9 agent_id=agent_state.id,
10 response_format=None
11)