Text-based AI game module¶
This example builds the core of a text adventure: three signatures for scene generation, NPC dialogue, and action resolution, composed into a single GameAI class.
Signatures¶
trait StoryGenerator extends Spec:
def location: InputField[String]
def player_info: InputField[String]
def story_progress: InputField[Int]
def recent_actions: InputField[String]
def scene_description: OutputField[String]
def available_actions: OutputField[List[String]]
def npcs_present: OutputField[List[String]]
def items_available: OutputField[List[String]]
A Spec trait declares the inputs and outputs of a generation step. InputField[String], OutputField[Int], OutputField[List[String]], and OutputField[Map[String, Int]] all map to the corresponding Scala types, so the output structure is known at compile time. The example defines three such traits: StoryGenerator, DialogueGenerator, and ActionResolver.
Composing the module¶
private val storyGen = ChainOfThought(Signature.of[StoryGenerator])
private val dialogueGen = ChainOfThought(Signature.of[DialogueGenerator])
private val actionResolver = ChainOfThought(Signature.of[ActionResolver])
GameAI holds one ChainOfThought predictor per signature, each built from Signature.of[T]. The class threads the predictors' outputs into its own methods rather than extending a base module type.
Results¶
def generateScene(player: Player, context: GameContext, recentActions: String = "")(using
RuntimeContext
)
: Either[DspyError, Scene] =
storyGen((
location = context.currentLocation,
player_info = player.info,
story_progress = context.storyProgress,
recent_actions = recentActions
)).map(s =>
Scene(
description = s.output.scene_description,
actions = s.output.available_actions,
npcs = s.output.npcs_present,
items = s.output.items_available
)
)
Each method calls a predictor with a named-tuple of inputs and maps the result into a plain case class (Scene, Dialogue, or ActionOutcome). The predictor returns Either[DspyError, Out], and s.output exposes the declared output fields with their declared types. RuntimeContext is passed implicitly.
Running it¶
Notes¶
This page covers the signatures and the GameAI module that composes them. The surrounding game plumbing is out of scope: the Player and GameContext save/load JSON, and the console rendering, menus, character creation, and input game loop. Minimal Player and GameContext carriers are kept so the formatted-string inputs match what the signatures expect. Drive GameAI from your own loop, threading the returned Scene, Dialogue, and ActionOutcome values back into state.
Full source: AiTextGame.scala