Text and chat prompts

The Prompt Library supports two prompt structures:

  • Text prompts — Simple string templates with variable substitution. Good for one-shot generations.
  • Chat prompts — Structured message lists in OpenAI format with system, user, and assistant roles. Good for multi-turn or system+user agents, and supports multimodal content (text, images, videos).

The prompt structure is fixed at creation time. A prompt created with create_prompt (text) cannot later be turned into a chat prompt, and vice versa — attempting it raises PromptTemplateStructureMismatch.

Text prompts

A text prompt is a single string template with {{variable}} substitution. They are ideal for single-turn interactions or when you need to generate a single piece of text.

Creating a text prompt

1import opik
2
3client = opik.Opik()
4
5prompt = client.create_prompt(
6 name="prompt-summary",
7 prompt="Write a summary of the following text: {{text}}",
8 metadata={"environment": "development"},
9 project_name="my-agent",
10)
11
12# Render the template
13print(prompt.format(text="Hello, world!"))

Each call with new content creates a new version, which is visible in the library:

Fetching a text prompt

1import opik
2
3client = opik.Opik()
4
5prompt = client.get_prompt(name="prompt-summary", project_name="my-agent")
6
7# Render the template
8print(prompt.format(text="Hello, world!"))

If you are not using the SDK, you can also fetch a prompt through the REST API.

Chat prompts

A chat prompt is a structured list of messages with roles (system, user, assistant), in the same shape that OpenAI-compatible chat completion APIs accept. They are the right choice when your agent has a multi-turn message structure.

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

Creating a chat prompt

1import opik
2
3client = opik.Opik()
4
5messages = [
6 {"role": "system", "content": "You are a helpful assistant specializing in {{domain}}."},
7 {"role": "user", "content": "Explain {{topic}} in simple terms."},
8]
9
10chat_prompt = client.create_chat_prompt(
11 name="educational-assistant",
12 messages=messages,
13 metadata={"category": "education"},
14 project_name="my-agent",
15)
16
17# Render the messages with variables
18formatted_messages = chat_prompt.format(
19 variables={
20 "domain": "physics",
21 "topic": "quantum entanglement",
22 }
23)
24
25print(formatted_messages)
26# [
27# {"role": "system", "content": "You are a helpful assistant specializing in physics."},
28# {"role": "user", "content": "Explain quantum entanglement in simple terms."},
29# ]

Once saved, a chat prompt has its own view in the Prompt Library:

Using chat prompts with the OpenAI API

The output of chat_prompt.format() is already in the shape the OpenAI chat completion API expects:

1import opik
2from openai import OpenAI
3
4client = opik.Opik()
5openai_client = OpenAI()
6
7chat_prompt = client.get_chat_prompt(
8 name="educational-assistant",
9 project_name="my-agent",
10)
11
12formatted_messages = chat_prompt.format(
13 variables={"domain": "physics", "topic": "quantum entanglement"},
14)
15
16response = openai_client.chat.completions.create(
17 model="gpt-4o-mini",
18 messages=formatted_messages,
19)
20
21print(response.choices[0].message.content)

Multi-turn templates

Chat prompts can capture a multi-turn flow, with assistant turns inline and variables anywhere in the conversation:

1import opik
2
3client = opik.Opik()
4
5messages = [
6 {"role": "system", "content": "You are a customer support agent for {{company}}."},
7 {"role": "user", "content": "I have an issue with {{product}}."},
8 {"role": "assistant", "content": "I'd be happy to help with your {{product}}. Can you describe the issue?"},
9 {"role": "user", "content": "{{issue_description}}"},
10]
11
12chat_prompt = client.create_chat_prompt(
13 name="customer-support-flow",
14 messages=messages,
15 project_name="my-agent",
16)
17
18formatted = chat_prompt.format(
19 variables={
20 "company": "Acme Corp",
21 "product": "Widget Pro",
22 "issue_description": "It won't turn on",
23 }
24)

Multimodal content

Chat prompts can include images and videos alongside text — useful for vision-enabled models.

Image analysis

1import opik
2
3client = opik.Opik()
4
5messages = [
6 {"role": "system", "content": "You analyze images and provide detailed descriptions."},
7 {
8 "role": "user",
9 "content": [
10 {"type": "text", "text": "What's in this image of {{subject}}?"},
11 {
12 "type": "image_url",
13 "image_url": {
14 "url": "{{image_url}}",
15 "detail": "high",
16 },
17 },
18 ],
19 },
20]
21
22chat_prompt = client.create_chat_prompt(
23 name="image-analyzer",
24 messages=messages,
25 project_name="my-agent",
26)
27
28formatted = chat_prompt.format(
29 variables={
30 "subject": "a sunset",
31 "image_url": "https://example.com/sunset.jpg",
32 },
33 supported_modalities={"vision": True},
34)

Video analysis

1import opik
2
3client = opik.Opik()
4
5messages = [
6 {"role": "system", "content": "You analyze videos and provide insights."},
7 {
8 "role": "user",
9 "content": [
10 {"type": "text", "text": "Analyze this video: {{description}}"},
11 {
12 "type": "video_url",
13 "video_url": {
14 "url": "{{video_url}}",
15 "mime_type": "video/mp4",
16 },
17 },
18 ],
19 },
20]
21
22chat_prompt = client.create_chat_prompt(
23 name="video-analyzer",
24 messages=messages,
25 project_name="my-agent",
26)
27
28formatted = chat_prompt.format(
29 variables={
30 "description": "traffic analysis",
31 "video_url": "https://example.com/traffic.mp4",
32 },
33 supported_modalities={"vision": True},
34)

Mixed content

You can combine multiple content blocks in a single message — e.g. two images with text around them:

1import opik
2
3client = opik.Opik()
4
5messages = [
6 {
7 "role": "user",
8 "content": [
9 {"type": "text", "text": "Compare these two images:"},
10 {"type": "image_url", "image_url": {"url": "{{image1_url}}"}},
11 {"type": "text", "text": "and"},
12 {"type": "image_url", "image_url": {"url": "{{image2_url}}"}},
13 {"type": "text", "text": "What are the main differences?"},
14 ],
15 },
16]
17
18chat_prompt = client.create_chat_prompt(
19 name="image-comparison",
20 messages=messages,
21 project_name="my-agent",
22)
23
24formatted = chat_prompt.format(
25 variables={
26 "image1_url": "https://example.com/before.jpg",
27 "image2_url": "https://example.com/after.jpg",
28 },
29 supported_modalities={"vision": True},
30)

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 lets you reuse the same template with different models that may or may not support certain modalities.

Template engines

Both text and chat prompts support two template engines for variable substitution:

Mustache (default)

Mustache is simple, portable, and covers the common case of substituting variables into a template.

1import opik
2from opik.api_objects.prompt import PromptType
3
4client = opik.Opik()
5
6chat_prompt = client.create_chat_prompt(
7 name="mustache-example",
8 messages=[
9 {"role": "user", "content": "Hello {{name}}, you live in {{city}}."},
10 ],
11 type=PromptType.MUSTACHE, # Default
12 project_name="my-agent",
13)
14
15formatted = chat_prompt.format(variables={"name": "Alice", "city": "Paris"})
16# [{"role": "user", "content": "Hello Alice, you live in Paris."}]

Jinja2

Jinja2 supports conditionals, loops, and filters, which makes it a better fit when your prompt needs to branch on input values.

1import opik
2from opik.api_objects.prompt import PromptType
3
4client = opik.Opik()
5
6chat_prompt = client.create_chat_prompt(
7 name="jinja-example",
8 messages=[
9 {
10 "role": "user",
11 "content": """
12 {% if is_premium %}
13 Hello {{ name }}, welcome to our premium service!
14 {% else %}
15 Hello {{ name }}, welcome!
16 {% endif %}
17 """,
18 },
19 ],
20 type=PromptType.JINJA2,
21 project_name="my-agent",
22)
23
24# Premium user
25chat_prompt.format(variables={"name": "Alice", "is_premium": True})
26# Regular user
27chat_prompt.format(variables={"name": "Bob", "is_premium": False})

Jinja2 is more powerful for branching prompt logic; Mustache is simpler and more portable across other tools that consume the same template. Pick the one that fits your prompt.

Searching prompts

To discover prompts by name substring or filter expression, use search_prompts / searchPrompts. Filters use Opik Query Language (OQL), the same syntax used elsewhere in Opik:

1import opik
2
3client = opik.Opik()
4
5# Search by name substring
6summaries = client.search_prompts(
7 filter_string='name contains "summary"'
8)
9
10# Combine name + tags
11filtered = client.search_prompts(
12 filter_string='name contains "summary" AND tags contains "alpha" AND tags contains "beta"',
13)
14
15# Only text prompts
16text_prompts = client.search_prompts(
17 filter_string='template_structure = "text"'
18)
19
20# Only chat prompts
21chat_prompts = client.search_prompts(
22 filter_string='template_structure = "chat" AND name contains "assistant"'
23)
24
25for prompt in filtered:
26 print(prompt.name, prompt.version, prompt.prompt)

search_prompts returns the latest version for each matching prompt. To explore the full version history of a single prompt, see Version control.

Filter syntax

The filter_string parameter takes one or more <column> <operator> <value> clauses joined with AND:

ColumnTypeOperators
idString=, !=, contains, not_contains, starts_with, ends_with, >, <
nameString=, !=, contains, not_contains, starts_with, ends_with, >, <
created_byString=, !=, contains, not_contains, starts_with, ends_with, >, <
tagsListcontains
template_structureString=, != (values: "text", "chat")

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 (AND)
  • template_structure = "text" — Only text prompts
  • template_structure = "chat" — Only chat prompts