Skip to content

Structured prompts

Koog uses the type-safe Kotlin DSL to create structured prompts with control over message types, their order, and content.

The structured prompts let you pre-configure conversation history with multiple messages, provide multimodal content, examples, tool calls, and their results.

Basic structure

The prompt() function creates a Prompt object with a unique ID and a list of messages:

val prompt = prompt("unique_prompt_id") {
    // List of messages
}

Message types

The Kotlin DSL supports the following types of messages, each of which corresponds to a specific role in a conversation:

  • System message: Provides the context, instructions, and constraints to the LLM, defining its behavior.
  • User message: Represents the user input that can contain text, images, audio, video, or documents.
  • Assistant message: Represents LLM responses that are used for few-shot learning or to continue the conversation.
  • Tool message: Represents tool calls and their results.
val prompt = prompt("unique_prompt_id") {
    // Add a system message to set the context
    system("You are a helpful assistant with access to tools.")
    // Add a user message
    user("What is 5 + 3 ?")
    // Add an assistant message
    assistant("The result is 8.")
}

System message

A system message defines the LLM behavior and sets the context for the entire conversation. It can specify the model's role, tone, provide guidelines and constraints on responses, and provide response examples.

To create the system message, provide a string to the system() function as an argument:

val prompt = prompt("assistant") {
    system("You are a helpful assistant that explains technical concepts.")
}

User messages

A user message represents input from the user. It can include plain text or multimodal content (such as images, audio, video, and documents).

To create the user message, provide a string to the user() function as an argument:

val prompt = prompt("question") {
    system("You are a helpful assistant.")
    user("What is Koog?")
}

For details about multimodal content, see Multimodal inputs.

Assistant messages

An assistant message represents an LLM response, which can be used for few-shot learning in future similar interactions, to continue a conversation, or to demonstrate the expected output structure.

To create the assistant message, provide a string to the assistant() function as an argument:

val prompt = prompt("article_review") {
    system("Evaluate the article.")

    // Example 1
    user("The article is clear and easy to understand.")
    assistant("positive")

    // Example 2
    user("The article is hard to read but it's clear and useful.")
    assistant("neutral")

    // Example 3
    user("The article is confusing and misleading.")
    assistant("negative")

    // New input to classify
    user("The article is interesting and helpful.")
}

Tool messages

A tool message represents a tool call and its result, which can be used to pre-fill the history of tool calls.

Tip

An LLM generates tool calls during execution. Pre-filling them is helpful for few-shot learning or demonstrating how the tools are expected to be used.

To create the tool message, call the tool() function:

val prompt = prompt("calculator_example") {
    system("You are a helpful assistant with access to tools.")
    user("What is 5 + 3?")
    tool {
        // Tool call
        call(
            id = "calculator_tool_id",
            tool = "calculator",
            content = """{"operation": "add", "a": 5, "b": 3}"""
        )

        // Tool result
        result(
            id = "calculator_tool_id",
            tool = "calculator",
            content = "8"
        )
    }

    // LLM response based on tool result
    assistant("The result of 5 + 3 is 8.")
}

Text message builders

When building a system(), user(), or assistant() message, you can use helper text-building functions for rich text formatting.

val prompt = prompt("text_example") {
    user {
        +"Review the following code snippet:"
        +"fun greet(name: String) = println(\"Hello, \$name!\")"

        // Paragraph break
        br()
        text("Please include in your explanation:")

        // Indent content
        padding("  ") {
            +"1. What the function does."
            +"2. How string interpolation works."
        }
    }
}

You can also use the Markdown and XML builders to add the content in the corresponding format.

val prompt = prompt("markdown_xml_example") {
    // A user message in Markdown format
    user {
        markdown {
            h2("Evaluate the article using the following criteria:")
            bulleted {
                item { +"Clarity and readability" }
                item { +"Accuracy of information" }
                item { +"Usefulness to the reader" }
            }
        }
    }
    // An assistant message in XML format
    assistant {
        xml {
            xmlDeclaration()
            tag("review") {
                tag("clarity") { text("positive") }
                tag("accuracy") { text("neutral") }
                tag("usefulness") { text("positive") }
            }
        }
    }
}

Tip

You can mix the text building functions with the XML and Markdown builders.

Prompt parameters

Prompts can be customized by configuring parameters that control the LLM's behavior.

val prompt = prompt(
    id = "custom_params",
    params = LLMParams(
        temperature = 0.7,
        numberOfChoices = 1,
        toolChoice = LLMParams.ToolChoice.Auto
    )
) {
    system("You are a creative writing assistant.")
    user("Write a song about winter.")
}

The following parameters are supported:

  • temperature: Controls randomness (0.0 = focused/deterministic, 1.0+ = creative/diverse)
  • toolChoice: Tool usage strategy (Auto, Required, Named(toolName))
  • numberOfChoices: Request multiple independent responses
  • schema: Define structured output format (for structured outputs)

For more information, see LLM parameters.

Extending existing prompts

You can extend an existing prompt by calling the prompt() function with the existing prompt as an argument:

val basePrompt = prompt("base") {
    system("You are a helpful assistant.")
    user("Hello!")
    assistant("Hi! How can I help you?")
}

val extendedPrompt = prompt(basePrompt) {
    user("What's the weather like?")
}

This creates a new prompt that includes all messages from basePrompt and the new user message.

Next steps