# Prompt management

> Learn to utilize Opik's prompt library for versioning, reusing, and managing your prompts centrally across projects.

Opik provides a prompt library that you can use to manage your prompts. Storing
prompts in a library allows you to version them, reuse them across projects, and
manage them in a central location.

Using a prompt library does not mean you can't store your prompt in code, we
have designed the prompt library to work seamlessly with your existing prompt
files while providing the benefits of a central prompt library.

Opik supports two types of prompts:

* [**Text Prompts**](#text-prompts): Simple string-based prompts with variable substitution
* [**Chat Prompts**](#chat-prompts): Structured message-based prompts in OpenAI format for conversational AI applications, supporting multimodal content (text, images, videos)

## Text Prompts

Text prompts are simple string-based templates that support variable substitution. They are ideal for single-turn interactions or when you need to generate a single piece of text.

### Managing text prompts stored in code

The recommended way to create and manage text prompts is using the
[`Prompt`](https://www.comet.com/docs/opik/python-sdk-reference/library/Prompt.html)
object. This will allow you to continue versioning your prompts in code while
also getting the benefit of having prompt versions managed in the Opik platform
so you can more easily keep track of your progress.

#### Prompts stored in code variables

<Tabs>
  <Tab value="Python" title="Python">
    ```python
    import opik

    # Prompt text stored in a variable
    PROMPT_TEXT = "Write a summary of the following text: {{text}}"

    # Create a prompt
    prompt = opik.Prompt(
        name="prompt-summary",
        prompt=PROMPT_TEXT,
        metadata={"environment": "production"}
    )

    # Print the prompt text
    print(prompt.prompt)

    # Build the prompt
    print(prompt.format(text="Hello, world!"))
    ```
  </Tab>

  <Tab value="TypeScript" title="TypeScript">
    ```typescript
    import { Opik } from "opik";

    // Prompt text stored in a variable
    const PROMPT_TEXT = "Write a summary of the following text: {{text}}";

    // Create a prompt
    const client = new Opik();
    const prompt = await client.createPrompt({
      name: "prompt-summary",
      prompt: PROMPT_TEXT,
      metadata: { environment: "production" },
    });

    // Print the prompt text
    console.log(prompt.prompt);

    // Build the prompt
    console.log(prompt.format({ text: "Hello, world!" }));
    ```
  </Tab>
</Tabs>

#### Prompts stored in files

Prompts can also be stored in a file and loaded from it:

<Tabs>
  <Tab value="Python" title="Python">
    ```python
    import opik

    # Read the prompt from a file
    with open("prompt.txt", "r") as f:
        prompt_text = f.read()

    prompt = opik.Prompt(name="prompt-summary", prompt=prompt_text)

    # Print the prompt text
    print(prompt.prompt)

    # Build the prompt
    print(prompt.format(text="Hello, world!"))
    ```
  </Tab>

  <Tab value="TypeScript" title="TypeScript">
    ```typescript
    import { Opik } from "opik";
    import { readFileSync } from "fs";

    // Read the prompt from a file
    const promptText = readFileSync("prompt.txt", "utf-8");

    const client = new Opik();
    const prompt = await client.createPrompt({
      name: "prompt-summary",
      prompt: promptText,
    });

    // Print the prompt text
    console.log(prompt.prompt);

    // Build the prompt
    console.log(prompt.format({ text: "Hello, world!" }));
    ```
  </Tab>
</Tabs>

The prompt will now be stored in the library and versioned:

<Frame>
  <img src="https://files.buildwithfern.com/https://opik.docs.buildwithfern.com/docs/opik/2026-01-30T17:33:07.444Z/img/prompt_engineering/prompt_library_versions.png" />
</Frame>

<Tip>
  The [`Prompt`](https://www.comet.com/docs/opik/python-sdk-reference/library/Prompt.html)
  object will create a new prompt in the library if this prompt doesn't already exist,
  otherwise it will return the existing prompt.

  This means you can safely run the above code multiple times without creating
  duplicate prompts.
</Tip>

### Using the low level SDK for text prompts

If you would rather keep text prompts in the Opik platform and manually update / download
them, you can use the low-level Python SDK to manage your prompts.

<Note>
  **When to use client methods vs. classes:**

  * **Use `Prompt()` class** (recommended): For most use cases, this class automatically uses the global Opik configuration set by `opik.configure()`.

  * **Use `client.create_prompt()`**: When you need to use a specific client configuration that differs from the global configuration (e.g., different workspace, host, or API key).
</Note>

#### Creating text prompts

You can create a new prompt in the library using both the SDK and the UI:

<Tabs>
  <Tab value="Python SDK" title="Python SDK">
    ```python
    import opik

    opik.configure()
    client = opik.Opik()

    # Create a new prompt
    prompt = client.create_prompt(name="prompt-summary", prompt="Write a summary of the following text: {{text}}", metadata={"environment": "development"})
    ```
  </Tab>

  <Tab value="TypeScript SDK" title="TypeScript SDK">
    ```typescript
    import { Opik } from "opik";

    const client = new Opik();

    // Create a new prompt
    const prompt = await client.createPrompt({
      name: "prompt-summary",
      prompt: "Write a summary of the following text: {{text}}",
      metadata: { environment: "development" },
    });
    ```
  </Tab>

  <Tab value="Using the UI" title="Using the UI">
    You can create a prompt in the UI by navigating to the Prompt library and clicking `Create new prompt`. This will open a dialog where you can enter the prompt name, the prompt text, and optionally a description:

    <Frame>
      <img src="https://files.buildwithfern.com/https://opik.docs.buildwithfern.com/docs/opik/2026-01-30T17:33:07.444Z/img/prompt_engineering/prompt_library.png" />
    </Frame>

    You can also edit a prompt by clicking on the prompt name in the library and clicking `Edit prompt`.
  </Tab>
</Tabs>

#### Adding prompts to traces and spans

You can associate prompts with your traces and spans using the `opik_context` module. This is useful when you want to track which prompts were used during the execution of your functions:

<Tabs>
  <Tab value="Adding prompts to traces" title="Adding prompts to traces">
    ```python
    import opik
    from opik.opik_context import update_current_trace

    # Create prompts
    system_prompt = opik.Prompt(
        name="system-prompt",
        prompt="You are a helpful assistant that provides accurate and concise answers."
    )

    # Get prompt from the Prompt library
    client = opik.Opik()
    user_prompt = client.get_prompt(name="user-prompt")

    @opik.track
    def process_user_query(question: str) -> str:
        # Add prompts to the current trace
        update_current_trace(
            name="user-query-processing",
            prompts=[system_prompt, user_prompt],
            metadata={"query_type": "general"}
        )
        
        # Your processing logic here
        formatted_prompt = user_prompt.format(question=question)
        # ... rest of your function
        return "Response to: " + question
    ```
  </Tab>

  <Tab value="Adding prompts to spans" title="Adding prompts to spans">
    ```python
    import opik
    from opik.opik_context import update_current_span

    # Create a prompt for a specific operation
    analysis_prompt = opik.Prompt(
        name="text-analysis-prompt",
        prompt="Analyze the sentiment of the following text: {{text}}"
    )

    @opik.track
    def analyze_sentiment(text: str) -> str:
        # Add prompt to the current span
        update_current_span(
            name="sentiment-analysis",
            prompts=[analysis_prompt],
            metadata={"analysis_type": "sentiment"}
        )
        
        # Your analysis logic here
        formatted_prompt = analysis_prompt.format(text=text)
        # ... rest of your function
        return "Positive"  # example result
    ```
  </Tab>

  <Tab value="Combined usage" title="Combined usage">
    ```python
    import opik
    from opik.opik_context import update_current_trace, update_current_span

    # Create different prompts for different purposes
    main_prompt = opik.Prompt(
        name="main-processing-prompt",
        prompt="Process the following data: {{data}}"
    )

    validation_prompt = opik.Prompt(
        name="validation-prompt",
        prompt="Validate this result: {{result}}"
    )

    @opik.track
    def validate_result(result: Dict[str, Any]) -> str:
        # Add validation prompt to span level
        update_current_span(
            name="result-validation",
            prompts=[validation_prompt],
            metadata={"validation_type": "result_check"}
        )

        # ... validation logic

        return "Valid"  # example result

    @opik.track
    def complex_processing(data: str) -> str:
        # Add main prompt to trace level
        update_current_trace(
            name="complex-data-processing",
            prompts=[main_prompt],
            metadata={"processing_type": "complex"}
        )
        
        # Process the data
        result = process_data(data)

        # Validate the result
        validated_result = validate_result(result)

        return validated_result

    complex_processing("My data")
    ```
  </Tab>
</Tabs>

You can view the prompts associated with a trace or span in the Opik UI:

<Frame>
  <img src="https://files.buildwithfern.com/https://opik.docs.buildwithfern.com/docs/opik/2026-01-30T17:33:07.444Z/img/prompt_engineering/prompt_opik_context_update.png" />
</Frame>

Further details on using prompts from the Prompt library are provided in the following sections.

#### Using prompts in supported integrations

Prompts can be used in all supported third-party integrations by attaching them to traces and spans through the [`opik_context` module](/docs/opik/prompt_engineering/prompt_management#adding-prompts-to-traces-and-spans).

For instance, you can use prompts with the `Google ADK` integration, as shown in the [example here](/docs/opik/integrations/adk#prompts-integration).

#### Downloading your text prompts

Once a prompt is created in the library, you can download it in code:

<Tabs>
  <Tab value="Python" title="Python">
    Use the [`Opik.get_prompt`](https://www.comet.com/docs/opik/python-sdk-reference/Opik.html#opik.Opik.get_prompt) method:

    ```python
    import opik

    opik.configure()
    client = opik.Opik()

    # Get a dataset
    dataset = client.get_or_create_dataset("test_dataset")

    # Get the prompt
    prompt = client.get_prompt(name="prompt-summary")

    # Create the prompt message
    prompt.format(text="Hello, world!")
    ```
  </Tab>

  <Tab value="TypeScript" title="TypeScript">
    Use the `getPrompt` method:

    ```typescript
    import { Opik } from "opik";

    const client = new Opik();

    // Get the prompt
    const prompt = await client.getPrompt({ name: "prompt-summary" });

    if (prompt) {
      // Format the prompt
      const formatted = prompt.format({ text: "Hello, world!" });
      console.log(formatted);
    }
    ```
  </Tab>
</Tabs>

If you are not using the SDK, you can download a prompt by using the [REST API](/reference/rest-api/overview).

#### Searching prompts

To discover prompts by name substring and/or filters, use `search_prompts`. Filters are written in Opik Query Language (OQL):

<Tabs>
  <Tab value="Python" title="Python">
    Use [`search_prompts`](https://www.comet.com/docs/opik/python-sdk-reference/Opik.html#opik.Opik.search_prompts):

    ```python
    import opik

    client = opik.Opik()

    # Search by name substring only
    latest_versions = client.search_prompts(
        filter_string='name contains "summary"'
    )

    # Search by name substring and tags filter
    filtered = client.search_prompts(
        filter_string='name contains "summary" AND tags contains "alpha" AND tags contains "beta"',
    )

    # Search for only text prompts
    text_prompts = client.search_prompts(
        filter_string='template_structure = "text"'
    )

    for prompt in filtered:
        print(prompt.name, prompt.commit, prompt.prompt)
    ```
  </Tab>

  <Tab value="TypeScript" title="TypeScript">
    Use `searchPrompts`:

    ```typescript
    import { Opik } from "opik";

    const client = new Opik();

    // Search by name substring only
    const latestVersions = await client.searchPrompts(
      'name contains "summary"'
    );

    // Search by name substring and tags filter
    const filtered = await client.searchPrompts(
      'name contains "summary" AND tags contains "alpha" AND tags contains "beta"'
    );

    // Search for only text prompts
    const textPrompts = await client.searchPrompts(
      'template_structure = "text"'
    );

    for (const prompt of filtered) {
      console.log(prompt.name, prompt.commit, prompt.prompt);
    }
    ```
  </Tab>
</Tabs>

You can filter by `template_structure` to search for only text prompts (`"text"`) or only chat prompts (`"chat"`). Without the filter, `search_prompts` returns both types.

The `filter_string` parameter uses Opik Query Language (OQL) with the format:
`"<COLUMN> <OPERATOR> <VALUE> [AND <COLUMN> <OPERATOR> <VALUE>]*"`

**Supported columns for prompts:**

| Column               | Type   | Operators                                                                   |
| -------------------- | ------ | --------------------------------------------------------------------------- |
| `id`                 | String | `=`, `!=`, `contains`, `not_contains`, `starts_with`, `ends_with`, `>`, `<` |
| `name`               | String | `=`, `!=`, `contains`, `not_contains`, `starts_with`, `ends_with`, `>`, `<` |
| `created_by`         | String | `=`, `!=`, `contains`, `not_contains`, `starts_with`, `ends_with`, `>`, `<` |
| `tags`               | List   | `contains`                                                                  |
| `template_structure` | String | `=`, `!=`                                                                   |

**Examples:**

* `tags contains "production"` - Filter by tag
* `name contains "summary"` - Filter by name substring
* `created_by = "user@example.com"` - Filter by creator
* `tags contains "alpha" AND tags contains "beta"` - Multiple tag filtering
* `template_structure = "text"` - Filter for only text prompts
* `template_structure = "chat"` - Filter for only chat prompts

`search_prompts` returns the **latest** version for each matching prompt.

## Chat Prompts

Chat prompts are structured message-based templates designed for conversational AI applications. They support multiple message roles (system, user, assistant) and multimodal content including text, images, and videos.

### Key Features

* **Structured Messages**: Organize prompts as a list of messages with roles (system, user, assistant)
* **Multimodal Support**: Include images, videos, and text in the same prompt
* **Variable Substitution**: Use Mustache (`{{variable}}`) or Jinja2 syntax
* **Version Control**: Automatic versioning when messages change
* **Template Validation**: Optional validation of template placeholders

### Managing chat prompts stored in code

Similar to text prompts, you can create and manage chat prompts using the
[`ChatPrompt`](https://www.comet.com/docs/opik/python-sdk-reference/library/ChatPrompt.html)
class. This allows you to version your chat prompts in code while benefiting from
centralized management in the Opik platform.

#### Simple chat prompt

<Tabs>
  <Tab value="Python" title="Python">
    ```python
    import opik

    # Define chat messages with variables
    messages = [
        {"role": "system", "content": "You are a helpful assistant specializing in {{domain}}."},
        {"role": "user", "content": "Explain {{topic}} in simple terms."}
    ]

    # Create a chat prompt
    chat_prompt = opik.ChatPrompt(
        name="educational-assistant",
        messages=messages,
        metadata={"category": "education"}
    )

    # Format the prompt with variables
    formatted_messages = chat_prompt.format(
        variables={
            "domain": "physics",
            "topic": "quantum entanglement"
        }
    )

    # Use formatted messages with your LLM
    print(formatted_messages)
    # Output:
    # [
    #     {"role": "system", "content": "You are a helpful assistant specializing in physics."},
    #     {"role": "user", "content": "Explain quantum entanglement in simple terms."}
    # ]
    ```
  </Tab>

  <Tab value="TypeScript" title="TypeScript">
    ```typescript
    import { Opik } from "opik";

    const client = new Opik();

    // Define chat messages with variables
    const messages = [
      {
        role: "system",
        content: "You are a helpful assistant specializing in {{domain}}.",
      },
      { role: "user", content: "Explain {{topic}} in simple terms." },
    ];

    // Create a chat prompt
    const chatPrompt = await client.createChatPrompt({
      name: "educational-assistant",
      messages: messages,
      metadata: { category: "education" },
    });

    // Format the prompt with variables
    const formattedMessages = chatPrompt.format({
      domain: "physics",
      topic: "quantum entanglement",
    });

    // Use formatted messages with your LLM
    console.log(formattedMessages);
    // Output:
    // [
    //   { role: "system", content: "You are a helpful assistant specializing in physics." },
    //   { role: "user", content: "Explain quantum entanglement in simple terms." }
    // ]
    ```
  </Tab>
</Tabs>

#### Using chat prompts with OpenAI

Chat prompts work seamlessly with OpenAI's chat completion API:

<Tabs>
  <Tab value="Python" title="Python">
    ```python
    import opik
    from openai import OpenAI

    # Define chat messages with variables
    messages = [
        {"role": "system", "content": "You are a helpful assistant specializing in {{domain}}."},
        {"role": "user", "content": "Explain {{topic}} in simple terms."}
    ]

    # Create a chat prompt
    chat_prompt = opik.ChatPrompt(
        name="educational-assistant",
        messages=messages,
        metadata={"category": "education"}
    )

    # Format the prompt with variables
    formatted_messages = chat_prompt.format(
        variables={
            "domain": "physics",
            "topic": "quantum entanglement"
        }
    )

    # Use with OpenAI API
    client = OpenAI()
    response = client.chat.completions.create(
        model="gpt-4",
        messages=formatted_messages
    )

    print(response.choices[0].message.content)
    ```
  </Tab>

  <Tab value="TypeScript" title="TypeScript">
    ```typescript
    import { Opik } from "opik";
    import OpenAI from "openai";

    const client = new Opik();
    const openai = new OpenAI();

    // Define chat messages with variables
    const messages = [
      {
        role: "system",
        content: "You are a helpful assistant specializing in {{domain}}.",
      },
      { role: "user", content: "Explain {{topic}} in simple terms." },
    ];

    // Create a chat prompt
    const chatPrompt = await client.createChatPrompt({
      name: "educational-assistant",
      messages: messages,
      metadata: { category: "education" },
    });

    // Format the prompt with variables
    const formattedMessages = chatPrompt.format({
      domain: "physics",
      topic: "quantum entanglement",
    });

    // Use with OpenAI API
    const response = await openai.chat.completions.create({
      model: "gpt-4",
      messages: formattedMessages,
    });

    console.log(response.choices[0].message.content);
    ```
  </Tab>
</Tabs>

#### Multi-turn conversation templates

You can create templates for multi-turn conversations:

<Tabs>
  <Tab value="Python" title="Python">
    ```python
    import opik

    # Define a multi-turn conversation template
    messages = [
        {"role": "system", "content": "You are a customer support agent for {{company}}."},
        {"role": "user", "content": "I have an issue with {{product}}."},
        {"role": "assistant", "content": "I'd be happy to help with your {{product}}. Can you describe the issue?"},
        {"role": "user", "content": "{{issue_description}}"}
    ]

    chat_prompt = opik.ChatPrompt(
        name="customer-support-flow",
        messages=messages
    )

    # Format with specific values
    formatted = chat_prompt.format(
        variables={
            "company": "Acme Corp",
            "product": "Widget Pro",
            "issue_description": "It won't turn on"
        }
    )
    ```
  </Tab>

  <Tab value="TypeScript" title="TypeScript">
    ```typescript
    import { Opik } from "opik";

    const client = new Opik();

    // Define a multi-turn conversation template
    const messages = [
      {
        role: "system",
        content: "You are a customer support agent for {{company}}.",
      },
      { role: "user", content: "I have an issue with {{product}}." },
      {
        role: "assistant",
        content:
          "I'd be happy to help with your {{product}}. Can you describe the issue?",
      },
      { role: "user", content: "{{issue_description}}" },
    ];

    const chatPrompt = await client.createChatPrompt({
      name: "customer-support-flow",
      messages: messages,
    });

    // Format with specific values
    const formatted = chatPrompt.format({
      company: "Acme Corp",
      product: "Widget Pro",
      issue_description: "It won't turn on",
    });
    ```
  </Tab>
</Tabs>

The chat prompt will now be stored in the library and versioned, just like text prompts.

<Tip>
  The [`ChatPrompt`](https://www.comet.com/docs/opik/python-sdk-reference/library/ChatPrompt.html)
  class will create a new chat prompt in the library if it doesn't already exist,
  otherwise it will return the existing prompt.

  This means you can safely run the code multiple times without creating duplicate prompts.
</Tip>

Once created, you can view and manage your chat prompts in the Opik UI:

<Frame>
  <img src="https://files.buildwithfern.com/https://opik.docs.buildwithfern.com/docs/opik/2026-01-30T17:33:07.444Z/img/prompt_engineering/chat_prompt_ui_view.png" />
</Frame>

### Multimodal chat prompts

Chat prompts support multimodal content, allowing you to include images and videos alongside text. This is useful for vision-enabled models.

#### Image analysis

<Tabs>
  <Tab value="Python" title="Python">
    ```python
    import opik

    # Chat prompt with image content
    messages = [
        {"role": "system", "content": "You analyze images and provide detailed descriptions."},
        {
            "role": "user",
            "content": [
                {"type": "text", "text": "What's in this image of {{subject}}?"},
                {
                    "type": "image_url",
                    "image_url": {
                        "url": "{{image_url}}",
                        "detail": "high"
                    }
                }
            ]
        }
    ]

    chat_prompt = opik.ChatPrompt(
        name="image-analyzer",
        messages=messages
    )

    # Format with variables
    formatted = chat_prompt.format(
        variables={
            "subject": "a sunset",
            "image_url": "https://example.com/sunset.jpg"
        },
        supported_modalities={"vision": True}
    )
    ```
  </Tab>

  <Tab value="TypeScript" title="TypeScript">
    ```typescript
    import { Opik } from "opik";

    const client = new Opik();

    // Chat prompt with image content
    const messages = [
      {
        role: "system",
        content: "You analyze images and provide detailed descriptions.",
      },
      {
        role: "user",
        content: [
          { type: "text", text: "What's in this image of {{subject}}?" },
          {
            type: "image_url",
            image_url: {
              url: "{{image_url}}",
              detail: "high",
            },
          },
        ],
      },
    ];

    const chatPrompt = await client.createChatPrompt({
      name: "image-analyzer",
      messages: messages,
    });

    // Format with variables
    const formatted = chatPrompt.format(
      {
        subject: "a sunset",
        image_url: "https://example.com/sunset.jpg",
      },
      { vision: true }
    );
    ```
  </Tab>
</Tabs>

#### Video analysis

Chat prompts can also include video content:

<Tabs>
  <Tab value="Python" title="Python">
    ```python
    import opik

    # Chat prompt with video content
    messages = [
        {"role": "system", "content": "You analyze videos and provide insights."},
        {
            "role": "user",
            "content": [
                {"type": "text", "text": "Analyze this video: {{description}}"},
                {
                    "type": "video_url",
                    "video_url": {
                        "url": "{{video_url}}",
                        "mime_type": "video/mp4"
                    }
                }
            ]
        }
    ]

    chat_prompt = opik.ChatPrompt(
        name="video-analyzer",
        messages=messages
    )

    # Format with variables
    formatted = chat_prompt.format(
        variables={
            "description": "traffic analysis",
            "video_url": "https://example.com/traffic.mp4"
        },
        supported_modalities={"vision": True}
    )
    ```
  </Tab>

  <Tab value="TypeScript" title="TypeScript">
    ```typescript
    import { Opik } from "opik";

    const client = new Opik();

    // Chat prompt with video content
    const messages = [
      {
        role: "system",
        content: "You analyze videos and provide insights.",
      },
      {
        role: "user",
        content: [
          { type: "text", text: "Analyze this video: {{description}}" },
          {
            type: "video_url",
            video_url: {
              url: "{{video_url}}",
              mime_type: "video/mp4",
            },
          },
        ],
      },
    ];

    const chatPrompt = await client.createChatPrompt({
      name: "video-analyzer",
      messages: messages,
    });

    // Format with variables
    const formatted = chatPrompt.format(
      {
        description: "traffic analysis",
        video_url: "https://example.com/traffic.mp4",
      },
      { vision: true, video: true }
    );
    ```
  </Tab>
</Tabs>

#### Mixed content

You can combine multiple types of content in a single message:

<Tabs>
  <Tab value="Python" title="Python">
    ```python
    import opik

    # Chat prompt with multiple images and text
    messages = [
        {
            "role": "user",
            "content": [
                {"type": "text", "text": "Compare these two images:"},
                {
                    "type": "image_url",
                    "image_url": {"url": "{{image1_url}}"}
                },
                {"type": "text", "text": "and"},
                {
                    "type": "image_url",
                    "image_url": {"url": "{{image2_url}}"}
                },
                {"type": "text", "text": "What are the main differences?"}
            ]
        }
    ]

    chat_prompt = opik.ChatPrompt(
        name="image-comparison",
        messages=messages
    )

    formatted = chat_prompt.format(
        variables={
            "image1_url": "https://example.com/before.jpg",
            "image2_url": "https://example.com/after.jpg"
        },
        supported_modalities={"vision": True}
    )
    ```
  </Tab>

  <Tab value="TypeScript" title="TypeScript">
    ```typescript
    import { Opik } from "opik";

    const client = new Opik();

    // Chat prompt with multiple images and text
    const messages = [
      {
        role: "user",
        content: [
          { type: "text", text: "Compare these two images:" },
          {
            type: "image_url",
            image_url: { url: "{{image1_url}}" },
          },
          { type: "text", text: "and" },
          {
            type: "image_url",
            image_url: { url: "{{image2_url}}" },
          },
          { type: "text", text: "What are the main differences?" },
        ],
      },
    ];

    const chatPrompt = await client.createChatPrompt({
      name: "image-comparison",
      messages: messages,
    });

    const formatted = chatPrompt.format(
      {
        image1_url: "https://example.com/before.jpg",
        image2_url: "https://example.com/after.jpg",
      },
      { vision: true }
    );
    ```
  </Tab>
</Tabs>

<Note>
  When formatting multimodal prompts, you can specify `supported_modalities` to control how content is rendered:

  * If a modality is supported (e.g., `{"vision": True}`), the structured content is preserved
  * If a modality is not supported, it's replaced with text placeholders (e.g., `<<<image>>><<</image>>>`)

  This allows you to use the same prompt template with different models that may or may not support certain modalities.
</Note>

### Using the low level SDK for chat prompts

You can also use the low-level Python SDK to create and manage chat prompts directly.

<Note>
  **When to use client methods vs. classes:**

  * **Use `ChatPrompt()` or `Prompt()` classes** (recommended): For most use cases, these classes automatically use the global Opik configuration set by `opik.configure()`.

  * **Use `client.create_chat_prompt()` or `client.create_prompt()`**: When you need to use a specific client configuration that differs from the global configuration (e.g., different workspace, host, or API key).
</Note>

#### Creating chat prompts

<Tabs>
  <Tab value="Python SDK" title="Python SDK">
    Use [`Opik.create_chat_prompt`](https://www.comet.com/docs/opik/python-sdk-reference/Opik.html#opik.Opik.create_chat_prompt) to create a chat prompt:

    ```python
    import opik

    opik.configure()
    client = opik.Opik()

    # Create a new chat prompt
    messages = [
        {"role": "system", "content": "You are a helpful assistant."},
        {"role": "user", "content": "Hello, {{name}}!"}
    ]

    chat_prompt = client.create_chat_prompt(
        name="greeting-prompt",
        messages=messages,
        metadata={"environment": "development"}
    )

    # Format and use the prompt
    formatted = chat_prompt.format(variables={"name": "Alice"})
    print(formatted)
    ```
  </Tab>

  <Tab value="TypeScript SDK" title="TypeScript SDK">
    Use `createChatPrompt` to create a chat prompt:

    ```typescript
    import { Opik } from "opik";

    const client = new Opik();

    // Create a new chat prompt
    const messages = [
      { role: "system", content: "You are a helpful assistant." },
      { role: "user", content: "Hello, {{name}}!" },
    ];

    const chatPrompt = await client.createChatPrompt({
      name: "greeting-prompt",
      messages: messages,
      metadata: { environment: "development" },
    });

    // Format and use the prompt
    const formatted = chatPrompt.format({ name: "Alice" });
    console.log(formatted);
    ```
  </Tab>

  <Tab value="Using the UI" title="Using the UI">
    You can create a chat prompt in the UI by navigating to the Prompt library and clicking `Create new prompt`. Select "Chat prompt" as the prompt type, then enter the prompt name, add your chat messages with different roles (system, user, assistant), and optionally add metadata.

    <Frame>
      <img src="https://files.buildwithfern.com/https://opik.docs.buildwithfern.com/docs/opik/2026-01-30T17:33:07.444Z/img/prompt_engineering/create_chat_prompt.png" />
    </Frame>

    You can also edit a chat prompt by clicking on the prompt name in the library and clicking `Edit prompt`.
  </Tab>
</Tabs>

#### Downloading chat prompts

Once a chat prompt is created in the library, you can download it in code:

<Tabs>
  <Tab value="Python" title="Python">
    Use the [`Opik.get_chat_prompt`](https://www.comet.com/docs/opik/python-sdk-reference/Opik.html#opik.Opik.get_chat_prompt) method:

    ```python
    import opik

    opik.configure()
    client = opik.Opik()

    # Get a chat prompt
    chat_prompt = client.get_chat_prompt(name="greeting-prompt")

    # Format the messages
    formatted_messages = chat_prompt.format(variables={"name": "Bob"})

    # Use with your LLM
    # response = llm.chat(messages=formatted_messages)
    ```
  </Tab>

  <Tab value="TypeScript" title="TypeScript">
    Use the `getChatPrompt` method:

    ```typescript
    import { Opik } from "opik";

    const client = new Opik();

    // Get a chat prompt
    const chatPrompt = await client.getChatPrompt({ name: "greeting-prompt" });

    if (chatPrompt) {
      // Format the messages
      const formattedMessages = chatPrompt.format({ name: "Bob" });

      // Use with your LLM
      // const response = await llm.chat(formattedMessages);
    }
    ```
  </Tab>
</Tabs>

#### Searching chat prompts

You can search for chat prompts specifically by using the `template_structure` filter:

<Tabs>
  <Tab value="Python" title="Python">
    Use [`Opik.search_prompts`](https://www.comet.com/docs/opik/python-sdk-reference/Opik.html#opik.Opik.search_prompts) with the filter:

    ```python
    import opik

    client = opik.Opik()

    # Search for only chat prompts
    chat_prompts = client.search_prompts(
        filter_string='template_structure = "chat" AND name contains "assistant"'
    )

    for prompt in chat_prompts:
        print(f"Chat prompt: {prompt.name}")
        print(f"Messages: {prompt.template}")
    ```
  </Tab>

  <Tab value="TypeScript" title="TypeScript">
    Use `searchPrompts` with the filter:

    ```typescript
    import { Opik } from "opik";

    const client = new Opik();

    // Search for only chat prompts
    const chatPrompts = await client.searchPrompts(
      'template_structure = "chat" AND name contains "assistant"'
    );

    for (const prompt of chatPrompts) {
      console.log(`Chat prompt: ${prompt.name}`);
      console.log(`Messages: ${prompt.messages}`);
    }
    ```
  </Tab>
</Tabs>

To search for text prompts only, use `template_structure = "text"`. Without the filter, `search_prompts` returns both text and chat prompts.

The `filter_string` parameter uses Opik Query Language (OQL) and supports the same columns and operators as text prompts (see [Searching prompts](#searching-prompts) above).

### Template types for chat prompts

Chat prompts support two template types for variable substitution.

#### Mustache (default)

<Tabs>
  <Tab value="Python" title="Python">
    ```python
    import opik
    from opik.api_objects.prompt import PromptType

    messages = [
        {"role": "user", "content": "Hello {{name}}, you live in {{city}}."}
    ]

    chat_prompt = opik.ChatPrompt(
        name="mustache-example",
        messages=messages,
        type=PromptType.MUSTACHE  # Default
    )

    formatted = chat_prompt.format(variables={"name": "Alice", "city": "Paris"})
    # Result: [{"role": "user", "content": "Hello Alice, you live in Paris."}]
    ```
  </Tab>

  <Tab value="TypeScript" title="TypeScript">
    ```typescript
    import { Opik, PromptType } from "opik";

    const client = new Opik();

    const messages = [
      { role: "user", content: "Hello {{name}}, you live in {{city}}." },
    ];

    const chatPrompt = await client.createChatPrompt({
      name: "mustache-example",
      messages: messages,
      type: PromptType.MUSTACHE, // Default
    });

    const formatted = chatPrompt.format({ name: "Alice", city: "Paris" });
    // Result: [{ role: "user", content: "Hello Alice, you live in Paris." }]
    ```
  </Tab>
</Tabs>

#### Jinja2

For more advanced templating with conditionals, loops, and filters:

<Tabs>
  <Tab value="Python" title="Python">
    ```python
    import opik
    from opik.api_objects.prompt import PromptType

    messages = [
        {
            "role": "user",
            "content": """
            {% if is_premium %}
            Hello {{ name }}, welcome to our premium service!
            {% else %}
            Hello {{ name }}, welcome!
            {% endif %}
            """
        }
    ]

    chat_prompt = opik.ChatPrompt(
        name="jinja-example",
        messages=messages,
        type=PromptType.JINJA2
    )

    # With premium user
    formatted = chat_prompt.format(
        variables={"name": "Alice", "is_premium": True}
    )
    # Result includes: "Hello Alice, welcome to our premium service!"

    # With regular user
    formatted = chat_prompt.format(
        variables={"name": "Bob", "is_premium": False}
    )
    # Result includes: "Hello Bob, welcome!"
    ```
  </Tab>

  <Tab value="TypeScript" title="TypeScript">
    ```typescript
    import { Opik, PromptType } from "opik";

    const client = new Opik();

    const messages = [
      {
        role: "user",
        content: `
          {% if is_premium %}
          Hello {{ name }}, welcome to our premium service!
          {% else %}
          Hello {{ name }}, welcome!
          {% endif %}
        `,
      },
    ];

    const chatPrompt = await client.createChatPrompt({
      name: "jinja-example",
      messages: messages,
      type: PromptType.JINJA2,
    });

    // With premium user
    const formatted1 = chatPrompt.format({
      name: "Alice",
      is_premium: true,
    });
    // Result includes: "Hello Alice, welcome to our premium service!"

    // With regular user
    const formatted2 = chatPrompt.format({
      name: "Bob",
      is_premium: false,
    });
    // Result includes: "Hello Bob, welcome!"
    ```
  </Tab>
</Tabs>

<Note>
  Jinja2 templates support advanced features like conditionals, loops, and filters, making them more powerful for complex prompt logic. However, Mustache templates are simpler and more portable.
</Note>

### Chat prompt versioning

Chat prompts are automatically versioned when the messages change. Each version has a unique commit hash.

<Tabs>
  <Tab value="Python" title="Python">
    ```python
    import opik

    # Create initial version
    messages_v1 = [
        {"role": "system", "content": "You are helpful."},
        {"role": "user", "content": "Hi!"}
    ]

    chat_prompt_v1 = opik.ChatPrompt(
        name="assistant-prompt",
        messages=messages_v1
    )

    print(f"Version 1 commit: {chat_prompt_v1.commit}")

    # Create new version with different messages
    messages_v2 = [
        {"role": "system", "content": "You are very helpful."},
        {"role": "user", "content": "Hello there!"},
        {"role": "assistant", "content": "How can I assist you?"}
    ]

    chat_prompt_v2 = opik.ChatPrompt(
        name="assistant-prompt",
        messages=messages_v2
    )

    print(f"Version 2 commit: {chat_prompt_v2.commit}")

    # Get specific version by commit
    client = opik.Opik()
    specific_version = client.get_chat_prompt(
        name="assistant-prompt",
        commit=chat_prompt_v1.commit
    )

    # Get version history
    history = client.get_chat_prompt_history(name="assistant-prompt")
    print(f"Total versions: {len(history)}")
    ```

    You can use [`get_chat_prompt`](https://www.comet.com/docs/opik/python-sdk-reference/Opik.html#opik.Opik.get_chat_prompt) to retrieve a specific version by commit hash, and [`get_chat_prompt_history`](https://www.comet.com/docs/opik/python-sdk-reference/Opik.html#opik.Opik.get_chat_prompt_history) to get all versions.
  </Tab>

  <Tab value="TypeScript" title="TypeScript">
    ```typescript
    import { Opik } from "opik";

    const client = new Opik();

    // Create initial version
    const messagesV1 = [
      { role: "system", content: "You are helpful." },
      { role: "user", content: "Hi!" },
    ];

    const chatPromptV1 = await client.createChatPrompt({
      name: "assistant-prompt",
      messages: messagesV1,
    });

    console.log(`Version 1 commit: ${chatPromptV1.commit}`);

    // Create new version with different messages
    const messagesV2 = [
      { role: "system", content: "You are very helpful." },
      { role: "user", content: "Hello there!" },
      { role: "assistant", content: "How can I assist you?" },
    ];

    const chatPromptV2 = await client.createChatPrompt({
      name: "assistant-prompt",
      messages: messagesV2,
    });

    console.log(`Version 2 commit: ${chatPromptV2.commit}`);

    // Get specific version by commit
    const specificVersion = await client.getChatPrompt({
      name: "assistant-prompt",
      commit: chatPromptV1.commit,
    });

    // Get version history
    const versions = await chatPromptV1.getVersions();
    console.log(`Total versions: ${versions.length}`);
    ```

    You can use `getChatPrompt` with a commit parameter to retrieve a specific version, and the `getVersions()` method to get all versions.
  </Tab>
</Tabs>

<Tip>
  If you create a chat prompt with identical messages to an existing version, Opik will return the existing version instead of creating a duplicate. This helps avoid unnecessary version proliferation.
</Tip>

### Prompt structure immutability

Once you create a prompt with a specific structure (text or chat), that structure cannot be changed. This ensures consistency and prevents accidental mixing of prompt types.

```python
import opik