PlayKit.ai
Text Generation

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

Create a Schema Library

Schema Library is a ScriptableObject for centrally managing your JSON Schema definitions.

Create Schema Library Asset

  1. In Unity Editor, right-click in the Project window
  2. Select Create > Developerworks SDK > Schema Library
  3. Name the file SchemaLibrary and save it to a Resources folder

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:

  1. Click the Add Schema button
  2. Fill in the following:
    • Name: Unique identifier for the schema (e.g., quest_info)
    • Description: Schema description
    • JSON Schema: Complete JSON Schema definition

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

  1. Clear schema design: Use descriptive field names and detailed description
  2. Mark required fields: Use required array to specify mandatory fields
  3. Use enum constraints: For limited options, use enum to restrict values
  4. Validate output: Always check for null return, verify required fields exist
  5. Provide clear prompts: Clearly state what data you need in your prompt
  6. Adjust temperature: For structured output, use lower temperature (0.3-0.7) for more stable results
  7. Centralize schema management: Use Schema Library instead of hardcoded JSON strings

Next Steps