# `Nous.PromptTemplate`
[🔗](https://github.com/nyo16/nous/blob/v0.17.1/lib/nous/prompt_template.ex#L1)

Safe prompt templates for building messages.

Templates use a tightly-constrained `<%= @var %>` substitution syntax;
the template body is **not** passed through `EEx.eval_string/2`, so
template content from LLM output, tool results, or other untrusted
sources cannot trigger arbitrary Elixir evaluation. Templates that
contain `<%` markers other than the simple variable form are rejected
by `from_template/2`.

## Simple Usage

    template = PromptTemplate.from_template(
      "You are a <%= @role %> assistant that speaks <%= @language %>.",
      role: :system
    )

    message = PromptTemplate.to_message(template, %{role: "helpful", language: "Spanish"})
    # => %Message{role: :system, content: "You are a helpful assistant that speaks Spanish."}

## Building Message Lists

    messages = PromptTemplate.to_messages([
      PromptTemplate.from_template("You are <%= @persona %>", role: :system),
      PromptTemplate.from_template("Tell me about <%= @topic %>", role: :user)
    ], %{persona: "a historian", topic: "ancient Rome"})

    # Use with Agent.run
    Agent.run(agent, messages: messages)

## Default Values

    template = PromptTemplate.from_template(
      "Search for <%= @query %> with limit <%= @limit %>",
      inputs: %{limit: 10}
    )

    # Only need to provide query, limit has a default
    formatted = PromptTemplate.format(template, %{query: "elixir"})
    # => "Search for elixir with limit 10"

## Conditional Content

This module deliberately does not support `<%= if ... do %>` blocks or
arbitrary Elixir expressions. Build conditional structure by composing
multiple smaller templates with `compose/2` or `to_messages/2`.

## String Templates

For simple string templates without Message conversion:

    text = PromptTemplate.format_string(
      "Hello, <%= @name %>!",
      %{name: "World"}
    )
    # => "Hello, World!"

# `t`

```elixir
@type t() :: %Nous.PromptTemplate{
  inputs: map(),
  role: :system | :user | :assistant,
  text: String.t()
}
```

# `assistant`

```elixir
@spec assistant(
  String.t(),
  keyword()
) :: t()
```

Create an assistant message template.

Shorthand for `from_template(text, role: :assistant)`.

## Example

    template = PromptTemplate.assistant("I understand you want <%= @action %>")

# `build_messages`

```elixir
@spec build_messages([{atom(), String.t()}], map()) :: [Nous.Message.t()]
```

Create messages from a list of role/content tuples with shared bindings.

Convenient for building message lists inline.

## Example

    messages = PromptTemplate.build_messages([
      {:system, "You are a <%= @role %> assistant"},
      {:user, "Hello, my name is <%= @name %>"}
    ], %{role: "helpful", name: "Alice"})

# `compose`

```elixir
@spec compose([t()], String.t()) :: t()
```

Compose multiple templates into a single template.

Joins templates with the specified separator.

## Example

    intro = system("You are a helpful assistant.")
    rules = system("Follow these rules: <%= @rules %>")

    combined = compose([intro, rules], "\n\n")
    # Creates a single :system template with both texts joined

# `extract_variables`

```elixir
@spec extract_variables(String.t()) :: [atom() | String.t()]
```

Extract variable names from a template string.

## Example

    PromptTemplate.extract_variables("Hello <%= @name %>!")
    # => [:name]

Variables that match an existing atom are returned as atoms (matching what
callers pass in `bindings`). Unknown variables are returned as strings to
avoid uncontrolled atom-table growth from attacker-controlled template bodies.

# `format`

```elixir
@spec format(t(), map()) :: String.t()
```

Format template with variable substitution.

Merges provided bindings with default inputs (bindings take precedence).

## Example

    template = from_template("Hello, <%= @name %>!", inputs: %{name: "World"})

    format(template, %{})        # => "Hello, World!"
    format(template, %{name: "Elixir"})  # => "Hello, Elixir!"

# `format_string`

```elixir
@spec format_string(String.t(), map()) :: String.t()
```

Format a string template directly without creating a PromptTemplate struct.

## Example

    PromptTemplate.format_string("Hello, <%= @name %>!", %{name: "World"})
    # => "Hello, World!"

# `from_template`

```elixir
@spec from_template(
  String.t(),
  keyword()
) :: t()
```

Create a template from a string with `<%= @var %>` placeholders.

## Options

- `:role` - Message role (`:system`, `:user`, `:assistant`). Default: `:user`
- `:inputs` - Default variable values. Default: `%{}`

## Example

    template = PromptTemplate.from_template(
      "You are a <%= @role %> assistant.",
      role: :system,
      inputs: %{role: "helpful"}
    )

# `system`

```elixir
@spec system(
  String.t(),
  keyword()
) :: t()
```

Create a system message template.

Shorthand for `from_template(text, role: :system)`.

## Example

    template = PromptTemplate.system("You are a <%= @persona %>")

# `to_message`

```elixir
@spec to_message(t(), map()) :: Nous.Message.t()
```

Convert template to Message with variable substitution.

## Example

    template = from_template("You are a <%= @role %> assistant", role: :system)
    message = to_message(template, %{role: "helpful"})
    # => %Message{role: :system, content: "You are a helpful assistant"}

# `to_messages`

```elixir
@spec to_messages([t() | Nous.Message.t()], map()) :: [Nous.Message.t()]
```

Convert list of templates and/or messages to messages.

Items can be:
- `%PromptTemplate{}` - Will be formatted with bindings
- `%Message{}` - Passed through unchanged

## Example

    messages = to_messages([
      PromptTemplate.system("You are <%= @persona %>"),
      Message.user("Hello"),
      PromptTemplate.user("Tell me about <%= @topic %>")
    ], %{persona: "helpful", topic: "Elixir"})

# `user`

```elixir
@spec user(
  String.t(),
  keyword()
) :: t()
```

Create a user message template.

Shorthand for `from_template(text, role: :user)`.

## Example

    template = PromptTemplate.user("Tell me about <%= @topic %>")

# `validate_bindings`

```elixir
@spec validate_bindings(t(), map()) :: {:ok, map()} | {:error, [atom()]}
```

Check if all required variables are present in bindings.

Returns `{:ok, bindings}` if all variables are present,
or `{:error, missing_vars}` if some are missing.

## Example

    template = from_template("Hello <%= @name %>, age <%= @age %>")

    validate_bindings(template, %{name: "Alice", age: 30})
    # => {:ok, %{name: "Alice", age: 30}}

    validate_bindings(template, %{name: "Alice"})
    # => {:error, [:age]}

# `variables`

```elixir
@spec variables(t()) :: [atom()]
```

Extract all variable names from a template.

Useful for validation or documentation.

## Example

    template = from_template("Hello <%= @name %>, you are <%= @age %> years old")
    variables(template)
    # => [:name, :age]

---

*Consult [api-reference.md](api-reference.md) for complete listing*
