Structured Output gives a deeper dimension to your gameplay with NPCs. Structured Output is a mechanism through which the LLM backing your NPC returns user-defined, LLM instantiated structs to drive gameplay logic.
This means your LLM character can have deeper interactions, such as return answers to multiple choice questions, and decide if the price of an item is worth the cost, and more.
Both the response and Structured Output must be in English.
Overview
This section customizes your persona_npc_behavior with a single Structured Output for your persona. The interactions_response struct:
Records the number of times this NPC has interacted with the player.
The current feeling the persona has about this interaction with the player.
An array recording the history of the persona's feelings about all interactions with the player.
Structured Output requires that you:
Create a struct defining the Structured Output.
Provide instructions for how the persona should fill the Structured Output struct.
Define and subscribe to an event signaled upon receipt of Structured Output.
You can configure multiple Structured Outputs on a single persona at the same time and specify different subscriptions for each type of Structured Output for your persona.
Supported Types
Structured Output supports the following Verse types as fields inside structs:
intfloatboolenummessage
Displaying Messages
Message types are redacted when printed to the log or in-game, but you can display message types using devices that are designed to display them, such as:
billboard_devicehud_message_devicepopup_dialog_device
Prerequisites
Add Structured Output to NPC Behavior Script
The code below uses Structured Output to extend the capabilities of multiple LLMs by using a prompt_binding_definition type and RegisterAction() function to introduce new response types based on player prompts.
Prompt Binding is a struct given to the AI to register an action defined in the RegisterAction() function. Together these tell the LLM when to do a certain action or why a certain action should be used based on a player’s prompt.
Below is a Verse script containing an expanded persona_npc_behavior in the section persona_npc_behavior.verse. This expands upon the code in the Add an NPC to a Conversation documentation including Structured Output.
To amend the code in your existing persona_npc_behavior, follow these steps:
Ensure that you have your Verse Project open in VS Code:
In the Menu Bar, select Verse > Open Project in VS Code…
Once VS Code opens, find your
persona_npc_behavior.versefile in the VS Code Explorer.
Copy and paste the content of
persona_npc_behavior.versebelow into your Verse file in your project.Compile your Verse code either in the UEFN Toolbar or within VS Code.
For a step-by-step walkthrough of the Verse script, follow the steps under Explanation of Script Changes. This section picks up immediately after where the script explanation in Add Persona to NPC Character left off.
persona_npc_behavior.verse
using { /Fortnite.com/AI }
using { /Fortnite.com/Devices }
using { /Fortnite.com/Playspaces }
using { /UnrealEngine.com/Conversations }
using { /UnrealEngine.com/Temporary/Diagnostics }
using { /Verse.org/Chat }
using { /Verse.org/SceneGraph }
using { /Verse.org/Simulation }
Explanation of Script Changes
A struct named
interactions_responseholds variables that the functions pass to the LLM. The LLM uses the variables to evaluate the prompt and how to respond.Verseinteractions_response := struct: @ai_description("Use the Number field to output the number of times you have spoken to this player.") Number:int @ai_description("Use the Feeling field to output how you are feeling about your current interaction with the player.") Feeling:messageTwo different
@ai_descriptionstell the LLM what the variables are and the reason the LLM should use them when creating a response.Verse@ai_description("Use the Number field to output the number of times you have spoken to this player.") Number:int @ai_description("Use the Feeling field to output how you are feeling about your current interaction with the player.") Feeling:messageOnInteractionsResponseis the function that drives gameplay logic using Structured Output. The callback that fires whenever the AI session produces structured output matching theinteractions_responsestruct. It's the handler registered byRegisterActioninAddStructuredOutput(), and its Response parameter carries the data the model filled in.VerseOnInteractionsResponse(Response:interactions_response):void = Logger.Print("Structured response received") Logger.Print("Number of Interactions: {Response.Number}") Logger.Print("Feeling: {Localize(Response.Feeling)}") # Drive gameplay logic hereThe
AddStructuredOutputfunction registers the NPC's structured-output action with its AI session. This provides a way for the model to report interaction data back into gameplay. This code does two things. First it builds aprompt_binding_definition, a small descriptor giving the action aNameand aDescription(both wrapped through theMakeMessagehelper to turn the string literals into message values). The description text is what tells the model when to use this action: here, whenever the player has spoken to the NPC.Then it calls
Session.RegisterAction, passing the binding, true, theinteractions_responsestruct type, and theOnInteractionsResponsehandler. This tells the session that when it emits structured output shaped likeinteractions_response, it should route that data toOnInteractionsResponse.RegisterActionreturns a cancelable, which is wrapped inoption{}and stored inInteractionsResponseSubscriptionso the subscription can be torn down in theOnEndmethod.VerseAddStructuredOutput():void = if: NPCEntity := GetEntity[] PersonaComponent := NPCEntity.GetComponent[persona_component] Session := PersonaComponent.GetAISession() then: # Create a prompt_binding so that we can use it on RegisterAction InteractionBinding:prompt_binding_definition = prompt_binding_definition: Name := MakeMessage("InteractionBinding") Description := MakeMessage("Signals whenever the player has spoken to you.")OnBeginadds the structured output to the session and sets the player as the conversation target. In a game where players can join late, you want to ensure you add them on thePlayerAddedEventor when a player walks up to an NPC via a trigger.VerseOnBegin<override>()<suspends>:void= AddStructuredOutput() AddPlayersAsConversationTarget()In
OnEndtheInteractionsResponseSubscriptionis cleaned and removed from all players. This is good practice.VerseOnEnd<override>():void= if (Subscription := InteractionsResponseSubscription?): Subscription.Cancel() for (Player : GetEntity[].GetPlayspaceForEntity[].GetPlayers()): RemovePlayerAsConversationTarget(Player)
Test Structured Output
The NPC is now configured with Structured Output. Every time the persona speaks, the interactions_response is signaled.
Test your LLM character's behavior for more engaging responses and reactions to your prompts in-game.