Skip to content

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

OPENAI_API_KEY=sk-... sbt "examples/runMain dspy4s.examples.tutorials.ai_text_game.aiTextGameMain"

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