Structured Output

Structured output functionality enables Agents to generate typed data conforming to predefined schemas, rather than just free text, achieving reliable conversion from natural language to structured data.


Core Features

  • Type Safety: Define output structure using Java classes

  • Automatic Schema: Automatically generate JSON Schema from Java classes

  • Automatic Validation: Ensure output conforms to expected format

  • Two Modes: TOOL_CHOICE (forced) and PROMPT (compatible)

  • Graceful Handling: Automatically clean up intermediate history, keeping only final results


Quick Start

1. Define Output Schema

import java.util.List;

// Define structure using a simple Java class
public class ProductInfo {
    public String name;
    public Double price;
    public List<String> features;
    
    public ProductInfo() {}  // Must have no-arg constructor
}

2. Request Structured Output

import io.agentscope.core.message.Msg;
import io.agentscope.core.message.MsgRole;
import io.agentscope.core.message.TextBlock;

// Send query
Msg userMsg = Msg.builder()
    .role(MsgRole.USER)
    .content(TextBlock.builder()
        .text("Extract product information: iPhone 15 Pro, price $999, supports 5G")
        .build())
    .build();

// Request structured output
Msg response = agent.call(userMsg, ProductInfo.class).block();

// Extract typed data
ProductInfo data = response.getStructuredData(ProductInfo.class);

System.out.println("Product name: " + data.name);
System.out.println("Price: $" + data.price);
System.out.println("Features: " + data.features);

Output Example:

Product name: iPhone 15 Pro
Price: $999.0
Features: [5G, ...]

TOOL_CHOICE vs PROMPT

PROMPT (Compatible Mode)

ReActAgent agent = ReActAgent.builder()
    .name("AnalysisAgent")
    .model(model)
    .structuredOutputReminder(StructuredOutputReminder.PROMPT)
    .build();

Features:

  • Relies on prompts to guide model to call tool

  • If model doesn’t call, automatically adds reminder message and retries

  • Better compatibility, suitable for older models that don’t support tool_choice

  • May require multiple API calls, higher cost


Schema Definition

Basic Types

public class SimpleSchema {
    public String name;        // String
    public Integer age;        // Integer
    public Double score;       // Double
    public Boolean active;     // Boolean
}

Collection Types

public class CollectionSchema {
    public List<String> tags;           // String list
    public List<Integer> numbers;       // Integer list
    public Map<String, Object> metadata; // Key-value pairs
}

Nested Objects

public class Address {
    public String street;
    public String city;
    public String zipCode;
}

public class Person {
    public String name;
    public int age;
    public Address address;  // Nested object
    public List<String> hobbies;
}

Optional Fields

public class OptionalFields {
    public String required;        // Required (non-null)
    public String optional;        // Optional (can be null)
    public Integer count = 0;      // With default value
}

Complete Examples

The following are three complete examples from real-world application scenarios, demonstrating different uses of structured output.

Example 1: Product Requirements Extraction

import io.agentscope.core.ReActAgent;
import io.agentscope.core.memory.InMemoryMemory;
import io.agentscope.core.message.Msg;
import io.agentscope.core.message.MsgRole;
import io.agentscope.core.message.TextBlock;
import io.agentscope.core.model.DashScopeChatModel;
import io.agentscope.core.tool.Toolkit;
import java.util.List;

public class ProductAnalysisExample {
    
    // Define product requirements structure
    public static class ProductRequirements {
        public String productType;
        public String brand;
        public Integer minRam;
        public Double maxBudget;
        public List<String> features;
        
        public ProductRequirements() {}
    }
    
    public static void main(String[] args) {
        // Create Agent
        ReActAgent agent = ReActAgent.builder()
            .name("AnalysisAgent")
            .sysPrompt("You are an intelligent analysis assistant. "
                + "Analyze user requests and provide structured responses.")
            .model(DashScopeChatModel.builder()
                .apiKey(System.getenv("DASHSCOPE_API_KEY"))
                .modelName("qwen-max")
                .stream(true)
                .enableThinking(false)
                .build())
            .toolkit(new Toolkit())
            .memory(new InMemoryMemory())
            .build();
        
        // Prepare query
        String query = "I'm looking for a laptop. I need at least 16GB RAM, "
            + "prefer Apple brand, and my budget is around $2000. "
            + "It should be lightweight for travel.";
        
        Msg userMsg = Msg.builder()
            .role(MsgRole.USER)
            .content(TextBlock.builder()
                .text("Extract the product requirements from this query: " + query)
                .build())
            .build();
        
        try {
            // Get structured output
            Msg response = agent.call(userMsg, ProductRequirements.class).block();
            ProductRequirements result = response.getStructuredData(ProductRequirements.class);
            
            // Print extracted data
            System.out.println("Extracted structured data:");
            System.out.println("  Product Type: " + result.productType);
            System.out.println("  Brand: " + result.brand);
            System.out.println("  Min RAM: " + result.minRam + " GB");
            System.out.println("  Max Budget: $" + result.maxBudget);
            System.out.println("  Features: " + result.features);
            
        } catch (Exception e) {
            System.err.println("Error: " + e.getMessage());
            e.printStackTrace();
        }
    }
}

Output Example:

Extracted structured data:
  Product Type: laptop
  Brand: Apple
  Min RAM: 16 GB
  Max Budget: $2000.0
  Features: [lightweight, travel-friendly]

Example 2: Contact Information Extraction

// Define contact information structure
public static class ContactInfo {
    public String name;
    public String email;
    public String phone;
    public String company;
    
    public ContactInfo() {}
}

// Usage example
String text = "Please contact John Smith at john.smith@example.com or "
    + "call him at +1-555-123-4567. His company is TechCorp Inc.";

Msg userMsg = Msg.builder()
    .role(MsgRole.USER)
    .content(TextBlock.builder()
        .text("Extract contact information from: " + text)
        .build())
    .build();

Msg response = agent.call(userMsg, ContactInfo.class).block();
ContactInfo result = response.getStructuredData(ContactInfo.class);

System.out.println("Extracted contact information:");
System.out.println("  Name: " + result.name);
System.out.println("  Email: " + result.email);
System.out.println("  Phone: " + result.phone);
System.out.println("  Company: " + result.company);

Output Example:

Extracted contact information:
  Name: John Smith
  Email: john.smith@example.com
  Phone: +1-555-123-4567
  Company: TechCorp Inc.

Example 3: Sentiment Analysis

// Define sentiment analysis structure
public static class SentimentAnalysis {
    public String overallSentiment;  // "positive", "negative", or "neutral"
    public Double positiveScore;     // 0.0 to 1.0
    public Double negativeScore;     // 0.0 to 1.0
    public Double neutralScore;      // 0.0 to 1.0
    public List<String> keyTopics;
    public String summary;
    
    public SentimentAnalysis() {}
}

// Usage example
String review = "This product exceeded my expectations! The quality is amazing "
    + "and the customer service was very helpful. However, "
    + "the shipping took a bit longer than expected.";

Msg userMsg = Msg.builder()
    .role(MsgRole.USER)
    .content(TextBlock.builder()
        .text("Analyze the sentiment of this review and provide scores: " + review)
        .build())
    .build();

Msg response = agent.call(userMsg, SentimentAnalysis.class).block();
SentimentAnalysis result = response.getStructuredData(SentimentAnalysis.class);

System.out.println("Sentiment analysis results:");
System.out.println("  Overall Sentiment: " + result.overallSentiment);
System.out.println("  Positive Score: " + result.positiveScore);
System.out.println("  Negative Score: " + result.negativeScore);
System.out.println("  Neutral Score: " + result.neutralScore);
System.out.println("  Key Topics: " + result.keyTopics);
System.out.println("  Summary: " + result.summary);

Output Example:

Sentiment analysis results:
  Overall Sentiment: positive
  Positive Score: 0.75
  Negative Score: 0.15
  Neutral Score: 0.10
  Key Topics: [quality, customer service, shipping]
  Summary: Mostly positive with minor concerns about delivery time

Advanced Usage

1. Complex Nested Structures

public class AnalysisReport {
    public Summary summary;
    public List<Finding> findings;
    public Recommendation recommendation;
    
    public static class Summary {
        public String overview;
        public int totalIssues;
    }
    
    public static class Finding {
        public String issue;
        public String severity;  // "high" / "medium" / "low"
        public String location;
    }
    
    public static class Recommendation {
        public List<String> actions;
        public int priority;
    }
}

2. Validation and Error Handling

try {
    Msg response = agent.call(userMsg, ProductInfo.class).block();
    ProductInfo data = response.getStructuredData(ProductInfo.class);
    
    // Business validation
    if (data.price == null || data.price < 0) {
        System.err.println("Invalid price");
    }
    
} catch (IllegalArgumentException e) {
    System.err.println("Schema conversion failed: " + e.getMessage());
} catch (Exception e) {
    System.err.println("Processing failed: " + e.getMessage());
}

3. Conditional Structured Output

// Dynamically decide whether structured output is needed
Class<?> schema = needsStructuredOutput ? ProductInfo.class : null;

Msg response = schema != null 
    ? agent.call(userMsg, schema).block()
    : agent.call(userMsg).block();

4. Multi-Step Structuring

// Step 1: Extract entities
Msg step1 = agent.call(userMsg, EntityList.class).block();
EntityList entities = step1.getStructuredData(EntityList.class);

// Step 2: Analyze relationships
Msg step2 = agent.call(
    Msg.builder()
        .role(MsgRole.USER)
        .content(TextBlock.builder()
            .text("Analyze the relationships between these entities: " + entities)
            .build())
        .build(),
    RelationshipGraph.class
).block();

Advanced Features

1. Custom Validation

Msg response = agent.call(userMsg, ProductInfo.class).block();
ProductInfo data = response.getStructuredData(ProductInfo.class);

// Custom business validation
if (data.price != null && data.price < 0) {
    throw new IllegalArgumentException("Price cannot be negative");
}

if (data.features == null || data.features.isEmpty()) {
    throw new IllegalArgumentException("Must have at least one feature");
}

2. Using Jackson Annotations

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonIgnore;

public class CustomSchema {
    @JsonProperty("product_name")  // Custom JSON field name
    public String productName;
    
    @JsonIgnore  // Ignore this field
    public transient String internalCache;
}

3. Generic Support

public class Response<T> {
    public boolean success;
    public String message;
    public T data;
}

// When using, specify concrete type
// Note: Generic erasure limitation, need to create concrete class
public class UserResponse extends Response<User> {}

More Resources