Structured Output
Get type-safe AI responses using JSON Schema
Structured output allows you to define response formats using JSON Schema, ensuring AI returns data that matches your expected structure. This is useful for scenarios where you need to parse AI responses for game logic.
For example, you can have AI generate quest information, item attributes, NPC states, and other structured data instead of plain text.
We are still exploring ways to make Schema design more convenient. For now, you can directly have NPCs or ChatClient output JSON-formatted text and then parse it. AI models generally follow this pattern well.
Before You Begin
- Make sure you've completed SDK initialization
- Understand the basics of JSON Schema
- Be familiar with Text Generation basics
Create a Schema Library
Schema Library is a ScriptableObject for centrally managing your JSON Schema definitions.
Create Schema Library Asset
- In Unity Editor, right-click in the Project window
- Select Create > Developerworks SDK > Schema Library
- Name the file
SchemaLibraryand save it to aResourcesfolder
Schema Library must be named SchemaLibrary and placed in a Resources folder for the SDK to find it automatically.
Add Schema Definitions
Select the created Schema Library and add schemas in the Inspector:
- Click the Add Schema button
- Fill in the following:
- Name: Unique identifier for the schema (e.g.,
quest_info) - Description: Schema description
- JSON Schema: Complete JSON Schema definition
- Name: Unique identifier for the schema (e.g.,
JSON Schema Examples
Basic Object Schema
Define a simple quest information format:
{
"type": "object",
"properties": {
"quest_name": {
"type": "string",
"description": "Quest name"
},
"description": {
"type": "string",
"description": "Quest description"
},
"difficulty": {
"type": "string",
"enum": ["easy", "medium", "hard"],
"description": "Quest difficulty"
},
"reward_gold": {
"type": "number",
"description": "Gold reward"
}
},
"required": ["quest_name", "description", "difficulty", "reward_gold"]
}Nested Object Schema
Define item information with nested structure:
{
"type": "object",
"properties": {
"item_name": {
"type": "string",
"description": "Item name"
},
"item_type": {
"type": "string",
"enum": ["weapon", "armor", "consumable", "material"],
"description": "Item type"
},
"attributes": {
"type": "object",
"properties": {
"attack": {
"type": "number",
"description": "Attack power"
},
"defense": {
"type": "number",
"description": "Defense power"
},
"durability": {
"type": "number",
"description": "Durability"
}
}
},
"price": {
"type": "number",
"description": "Price"
}
},
"required": ["item_name", "item_type", "price"]
}Array Schema
Define dialogue options with arrays:
{
"type": "object",
"properties": {
"npc_response": {
"type": "string",
"description": "NPC's response"
},
"player_options": {
"type": "array",
"items": {
"type": "object",
"properties": {
"option_text": {
"type": "string",
"description": "Option text"
},
"option_id": {
"type": "number",
"description": "Option ID"
}
},
"required": ["option_text", "option_id"]
},
"description": "Dialogue options for the player"
}
},
"required": ["npc_response", "player_options"]
}Generate Structured Output
Using JObject (Flexible Approach)
Returning JObject provides maximum flexibility for dynamic JSON handling:
using Cysharp.Threading.Tasks;
using PlayKit_SDK;
using PlayKit_SDK.Public;
using Newtonsoft.Json.Linq;
using System.Collections.Generic;
using UnityEngine;
public class StructuredOutputExample : MonoBehaviour
{
async void Start()
{
var chatClient = PlayKitSDK.Factory.CreateChatClient();
// Prepare prompt
string prompt = "Generate a medium difficulty quest with 500 gold reward";
// Generate structured output
JObject result = await chatClient.GenerateStructuredAsync(
schemaName: "quest_info",
prompt: prompt,
cancellationToken: this.GetCancellationTokenOnDestroy()
);
if (result != null)
{
// Access JSON data
string questName = result["quest_name"]?.ToString();
string description = result["description"]?.ToString();
string difficulty = result["difficulty"]?.ToString();
int rewardGold = result["reward_gold"]?.ToObject<int>() ?? 0;
Debug.Log($"Quest: {questName}");
Debug.Log($"Description: {description}");
Debug.Log($"Difficulty: {difficulty}");
Debug.Log($"Reward: {rewardGold} gold");
}
}
}Using Generics (Type-Safe Approach)
Define C# classes and use generic methods to get type-safe objects:
using Cysharp.Threading.Tasks;
using PlayKit_SDK;
using PlayKit_SDK.Public;
using UnityEngine;
// Define corresponding C# class
[System.Serializable]
public class QuestInfo
{
public string quest_name;
public string description;
public string difficulty;
public int reward_gold;
}
public class TypedStructuredOutput : MonoBehaviour
{
async void Start()
{
var chatClient = PlayKitSDK.Factory.CreateChatClient();
string prompt = "Generate a simple beginner quest with 100 gold reward";
// Use generics to get type-safe object
QuestInfo quest = await chatClient.GenerateStructuredAsync<QuestInfo>(
schemaName: "quest_info",
prompt: prompt,
cancellationToken: this.GetCancellationTokenOnDestroy()
);
if (quest != null)
{
Debug.Log($"Quest: {quest.quest_name}");
Debug.Log($"Description: {quest.description}");
Debug.Log($"Difficulty: {quest.difficulty}");
Debug.Log($"Reward: {quest.reward_gold} gold");
// Use the strongly-typed object directly
CreateQuestInGame(quest);
}
}
void CreateQuestInGame(QuestInfo quest)
{
// Create quest in game
}
}Using Conversation History
You can pass complete conversation history to generate structured output:
var messages = new List<PlayKit_ChatMessage>
{
new PlayKit_ChatMessage
{
Role = "system",
Content = "You are a game quest designer who generates quests based on player requests."
},
new PlayKit_ChatMessage
{
Role = "user",
Content = "I want a quest about saving a village."
},
new PlayKit_ChatMessage
{
Role = "assistant",
Content = "I'll design a quest about saving a village for you."
},
new PlayKit_ChatMessage
{
Role = "user",
Content = "Please generate the quest details."
}
};
JObject result = await chatClient.GenerateStructuredAsync(
schemaName: "quest_info",
messages: messages,
cancellationToken: this.GetCancellationTokenOnDestroy()
);Without Schema Library
If you don't want to use Schema Library, pass the JSON Schema string directly:
string schemaJson = @"{
""type"": ""object"",
""properties"": {
""item_name"": { ""type"": ""string"" },
""price"": { ""type"": ""number"" }
},
""required"": [""item_name"", ""price""]
}";
JObject result = await chatClient.GenerateStructuredWithSchemaAsync(
schemaJson: schemaJson,
prompt: "Generate a basic healing potion",
cancellationToken: this.GetCancellationTokenOnDestroy()
);Schema Management
Check If Schema Exists
var chatClient = PlayKitSDK.Factory.CreateChatClient();
if (chatClient.HasSchema("quest_info"))
{
Debug.Log("Schema 'quest_info' exists");
}
else
{
Debug.LogWarning("Schema 'quest_info' not found");
}Get All Available Schemas
string[] availableSchemas = chatClient.GetAvailableSchemas();
Debug.Log("Available schemas:");
foreach (string schemaName in availableSchemas)
{
Debug.Log($"- {schemaName}");
}Dynamically Set Schema Library
// Load custom Schema Library
PlayKit_SchemaLibrary customLibrary = Resources.Load<PlayKit_SchemaLibrary>("CustomSchemaLibrary");
var chatClient = PlayKitSDK.Factory.CreateChatClient();
chatClient.SetSchemaLibrary(customLibrary);Practical Examples
Dynamic Game Item Generation
[System.Serializable]
public class GameItem
{
public string item_name;
public string item_type;
public ItemAttributes attributes;
public int price;
[System.Serializable]
public class ItemAttributes
{
public int attack;
public int defense;
public int durability;
}
}
public class ItemGenerator : MonoBehaviour
{
private PlayKit_AIChatClient chatClient;
void Start()
{
chatClient = PlayKitSDK.Factory.CreateChatClient();
}
public async UniTask<GameItem> GenerateItem(string itemType, int playerLevel)
{
string prompt = $"Generate a {itemType} type equipment suitable for level {playerLevel} player";
GameItem item = await chatClient.GenerateStructuredAsync<GameItem>(
schemaName: "item_info",
prompt: prompt,
temperature: 0.8f,
cancellationToken: this.GetCancellationTokenOnDestroy()
);
if (item != null)
{
Debug.Log($"Generated item: {item.item_name}");
Debug.Log($"Type: {item.item_type}");
Debug.Log($"Attack: {item.attributes.attack}");
Debug.Log($"Defense: {item.attributes.defense}");
Debug.Log($"Durability: {item.attributes.durability}");
Debug.Log($"Price: {item.price}");
}
return item;
}
}Error Handling
Structured output may fail due to invalid Schema or AI unable to generate data matching the format:
try
{
JObject result = await chatClient.GenerateStructuredAsync(
"quest_info",
"Generate a quest",
this.GetCancellationTokenOnDestroy()
);
if (result != null)
{
// Validate required fields
if (result["quest_name"] == null)
{
Debug.LogError("Generated data missing required fields");
return;
}
// Use data
ProcessQuestData(result);
}
else
{
Debug.LogError("Failed to generate structured output");
}
}
catch (System.Exception ex)
{
Debug.LogError($"Error: {ex.Message}");
}Best Practices
- Clear schema design: Use descriptive field names and detailed
description - Mark required fields: Use
requiredarray to specify mandatory fields - Use enum constraints: For limited options, use
enumto restrict values - Validate output: Always check for null return, verify required fields exist
- Provide clear prompts: Clearly state what data you need in your prompt
- Adjust temperature: For structured output, use lower temperature (0.3-0.7) for more stable results
- Centralize schema management: Use Schema Library instead of hardcoded JSON strings
Next Steps
- Learn about Image Generation to generate game image assets
- Read the API Reference for complete API documentation