Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions docs/docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,13 @@
},
"features/telemetry",
"features/semantic-caching",
{
"group": "Prompt Repository",
"icon": "folder",
"pages": [
"features/prompt-repository/playground"
]
},
{
"group": "Plugins",
"icon": "puzzle-piece",
Expand Down
231 changes: 231 additions & 0 deletions docs/features/prompt-repository/playground.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
---
title: "Playground"
description: "Create, test, and version prompts in an interactive playground."
icon: "square-terminal"
---

## Overview

The **Playground** in Bifrost is an interactive workspace for building, testing, and managing prompts. It allows you to experiment with messages, switch models, adjust parameters, and iterate until the output looks right. Once you're satisfied, you can **publish a version** and use it directly in your codebase. Over time, the prompt repository becomes a centralized **CMS for all your prompts**, making it easier to manage versions, collaborate with teammates, and maintain production-ready prompts.

![Prompt Repository Overview](../../media/prompt-repo-overview.png)

## How it Works

The playground is built around three core concepts: **prompts, sessions, and versions**.

### Folders

Folders help organize prompts into logical groups. Teams often structure them by product area, feature, or use case.

- Each folder has a **name** and optional **description**
- Prompts can live inside folders or at the root level
- Deleting a folder removes **all prompts, sessions, and versions inside it**

### Prompts

A **Prompt** is the main unit in the repository.

Think of it as a container that holds the full lifecycle of a prompt, from early experiments to production-ready versions.

Each prompt can have:

- Multiple **sessions** for experimentation
- Multiple **versions** for stable releases

### Sessions (Working Copies)

Sessions are **editable working copies** where you experiment with a prompt.

You can freely:

- Modify messages
- Switch providers or models
- Adjust parameters
- Run the prompt repeatedly

Sessions don't affect committed versions, so you can iterate safely.

If your session has unsaved changes, a **red asterisk appears next to the prompt name** in the top bar.
You can save your progress using:

- **Save Session** button
- `Cmd + S` / `Ctrl + S`

Saved sessions can be **renamed and restored** from the dropdown next to the Save button.

### Versions (Immutable Snapshots)

When you're happy with a prompt, you can **commit it as a version**.

Versions are **immutable snapshots**; once created, they cannot be edited. When the config differs from the last saved version, the **Unpublished Changes** badge appears, and it can be committed to create a new version.

Each version stores:

- The selected **message history** (system, user, assistant)
- **Provider and model configuration**
- **Model parameters** (temperature, max tokens, etc.)
- A **commit message** describing the change

Versions are automatically numbered:

```
v1 → v2 → v3 → ...
```

You can also **restore a previous version** from the dropdown next to the **Commit Version** button.

---

## Workspace Layout

The playground uses a simple **three-panel layout**:

| Panel | Purpose |
|------|---------|
| **Sidebar (left)** | Browse prompts, manage folders, and organize items |
| **Playground (center)** | Build and test your prompt messages |
| **Settings (right)** | Configure provider, model, API key, variables, and parameters |

![Workspace Layout](../../media/prompt-repo-layout.png)

---

## Getting Started

<Steps>

<Step title="Create a folder (optional)">

Click the **"+"** button in the sidebar and select **New Folder**.

Folders help organize prompts by team, feature, or use case.

![Create Folder](../../media/prompt-repo-create-folder.png)

</Step>

<Step title="Create a prompt">

Click **"+"** again and choose **New Prompt**.
Give it a name and optionally assign it to a folder.

![Create Prompt](../../media/prompt-repo-create-prompt.png)

</Step>

<Step title="Build your prompt">

Add messages to your prompt in the Playground:

- **System messages** for instructions
- **User messages** for input
- **Assistant messages** for examples or few-shot responses

Configure the provider, model, and parameters from the settings panel on the right.

![Playground](../../media/prompt-repo-playground.png)

</Step>

<Step title="Run the prompt">

Click **Run** or press `Cmd + enter` / `Ctrl + enter`.

Optionally, if you do not want to execute the prompt and only want to add a message to history, use the **+ Add** button.

</Step>

<Step title="Save and publish a version">

Once you're satisfied with the results:

1. **Save Session** to preserve your work
2. **Commit Version** to create an immutable snapshot

![Commit Version](../../media/prompt-repo-commit.png)

</Step>

</Steps>

## Key Capabilities

### Version Control

Each committed version creates a permanent record of your prompt.

This allows teams to track changes and safely iterate without breaking production prompts.

Key characteristics:

- **Sequential versioning** — v1, v2, v3, ...
- **Commit messages** explaining what changed
- **Immutable history**

### Multi-Provider Testing

You can switch between providers and models directly in the Playground.

Supported providers may include:

- OpenAI
- Anthropic
- AWS Bedrock
- Others configured in your Bifrost instance

You can also choose which API key to use:

- **Auto**: Uses the first available key.
- **Specific key**: Select a particular key.
- **Virtual key**: Uses governance-managed keys.

This makes it easy to compare how different models respond to the same prompt.

### Message Types

The Playground supports several message roles:

- **System**: Defines behavior or instructions.
- **User**: Input to the model.
- **Assistant**: The model's response to the user's input.
- **Tool Calls**: Function calls made by the model.
- **Tool Results**: Mock or real responses from called tools.

These allow you to simulate complex conversations and agent workflows.


### Attachments

For models that support multimodal input, you can attach files directly to user messages.

Supported attachments may include:

- Images
- PDFs
- Other supported file types

Attachments are only enabled when the selected model supports them.

### Drag-and-Drop Organization

Prompts can be reorganized easily using drag and drop in the sidebar.

You can move prompts:

- Between folders
- Back to the root level

## Session Management

Sessions store the state of your prompt experiments.

Each prompt maintains its **own session history**, allowing you to explore different approaches without losing previous work.

With sessions you can:

- Save specific conversation states
- Rename sessions for clarity
- Switch between past experiments

![Sessions](../../media/prompt-repo-sessions.png)
Binary file added docs/media/prompt-repo-commit.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/media/prompt-repo-create-folder.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/media/prompt-repo-create-prompt.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/media/prompt-repo-layout.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/media/prompt-repo-overview.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/media/prompt-repo-playground.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/media/prompt-repo-sessions.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions docs/style.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
.nav-logo {
height: 1.50rem;
}


#sidebar-group > li > button > div{
line-height: normal;
}
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ export function AssistantMessageView({
{usage && (
<Tooltip>
<TooltipTrigger className="p-1 hover:bg-muted focus:bg-muted focus:opacity-100 rounded-sm">
<InfoIcon className="text-muted-foreground hover:text-foreground h-3.5 w-3.5 shrink-0 cursor-pointer opacity-0 transition-opacity group-hover:opacity-100 group-focus-within:opacity-100 " />
<InfoIcon className="text-muted-foreground hover:text-foreground size-3 shrink-0 cursor-pointer opacity-0 transition-opacity group-hover:opacity-100 group-focus-within:opacity-100 " />
</TooltipTrigger>
<TooltipContent side="bottom">
<div className="flex flex-col gap-0.5 text-xs tabular-nums">
Expand All @@ -83,12 +83,12 @@ export function AssistantMessageView({
)}
{!disabled && !isStreaming && (
<button type="button" aria-label="Edit message" data-testid="assistant-msg-edit" onClick={() => setEditMode(true)} className="rounded-sm p-1 opacity-0 transition-opacity group-hover:opacity-100 group-focus-within:opacity-100 hover:bg-muted focus:bg-muted focus:opacity-100">
<PencilIcon className="text-muted-foreground hover:text-foreground h-3 w-3 shrink-0 cursor-pointer" />
<PencilIcon className="text-muted-foreground hover:text-foreground size-3 shrink-0 cursor-pointer" />
</button>
)}
{!disabled && onRemove && (
<button type="button" aria-label="Delete message" data-testid="assistant-msg-delete" onClick={onRemove} className="rounded-sm p-1 opacity-0 transition-opacity group-hover:opacity-100 group-focus-within:opacity-100 hover:bg-muted focus:bg-muted focus:opacity-100">
<XIcon className="text-muted-foreground hover:text-foreground h-3 w-3 shrink-0 cursor-pointer" />
<XIcon className="text-muted-foreground hover:text-foreground size-3 shrink-0 cursor-pointer" />
</button>
)}
</div>
Expand Down
22 changes: 11 additions & 11 deletions ui/components/prompts/components/messagesView/attachmentViews.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,21 @@ export function AttachmentBadge({ attachment, onRemove }: { attachment: MessageC
</>
) : isAudio ? (
<>
<Mic className="text-muted-foreground h-3.5 w-3.5" />
<Mic className="text-muted-foreground size-3" />
<span className="text-muted-foreground max-w-[100px] truncate">{attachment.input_audio?.format?.toUpperCase() || "Audio"}</span>
</>
) : (
<>
<FileIcon className="text-muted-foreground h-3.5 w-3.5" />
<FileIcon className="text-muted-foreground size-3" />
<span className="text-muted-foreground max-w-[120px] truncate">{attachment.file?.filename || "File"}</span>
</>
)}
<button
onClick={onRemove}
className="text-muted-foreground hover:bg-destructive/20 hover:text-destructive ml-0.5 rounded-full p-0.5"
className="text-muted-foreground hover:bg-card hover:text-destructive ml-0.5 rounded-full p-0.5 cursor-pointer"
type="button"
>
<XIcon className="h-3 w-3" />
<XIcon className="size-3" />
</button>
</div>
);
Expand Down Expand Up @@ -56,9 +56,9 @@ export function AttachmentDisplay({
{editable && onRemoveAttachment && (
<button
onClick={() => onRemoveAttachment(i)}
className="bg-background/80 text-muted-foreground hover:bg-destructive/20 hover:text-destructive absolute -top-1.5 -right-1.5 rounded-full border p-0.5 opacity-0 transition-opacity group-hover/att:opacity-100"
className="bg-background/80 text-muted-foreground hover:bg-card hover:text-destructive absolute -top-1.5 -right-1.5 rounded-full border p-0.5 opacity-0 transition-opacity group-hover/att:opacity-100 cursor-pointer"
>
<XIcon className="h-3 w-3" />
<XIcon className="size-3" />
</button>
)}
</div>
Expand All @@ -76,9 +76,9 @@ export function AttachmentDisplay({
{editable && onRemoveAttachment && (
<button
onClick={() => onRemoveAttachment(i)}
className="bg-background/80 text-muted-foreground cursor-pointer hover:bg-destructive/20 hover:text-destructive absolute -top-1.5 -right-1.5 rounded-full border p-0.5 opacity-0 transition-opacity group-hover/att:opacity-100"
className="bg-background/80 text-muted-foreground cursor-pointer hover:bg-card hover:text-destructive absolute -top-1.5 -right-1.5 rounded-full border p-0.5 opacity-0 transition-opacity group-hover/att:opacity-100"
>
<XIcon className="h-3 w-3" />
<XIcon className="size-3" />
</button>
)}
</div>
Expand All @@ -91,15 +91,15 @@ export function AttachmentDisplay({
key={i}
className="group/att bg-muted/30 text-muted-foreground relative flex max-w-full items-center gap-2 rounded-sm border px-3 py-1.5 text-sm"
>
<FileIcon className="h-4 w-4 shrink-0" />
<FileIcon className="size-3 shrink-0" />
<span className="min-w-0 truncate">{att.file?.filename || "File"}</span>
{att.file?.file_type && <span className="shrink-0 text-xs opacity-60">{att.file.file_type}</span>}
{editable && onRemoveAttachment && (
<button
onClick={() => onRemoveAttachment(i)}
className="bg-background/80 text-muted-foreground hover:bg-destructive/20 hover:text-destructive absolute -top-1.5 -right-1.5 rounded-full border p-0.5 opacity-0 transition-opacity group-hover/att:opacity-100"
className="bg-background/80 text-muted-foreground hover:bg-card hover:text-destructive absolute -top-1.5 -right-1.5 rounded-full border p-0.5 opacity-0 transition-opacity group-hover/att:opacity-100"
>
<XIcon className="h-3 w-3" />
<XIcon className="size-3" />
</button>
)}
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ export default function ErrorMessageView({ message, disabled, onRemove }: { mess
<div className="group hover:border-destructive/30 focus-within:border-destructive/30 rounded-sm border border-transparent px-3 py-2 transition-colors">
<div className="mb-1 flex items-center h-5">
<span className="text-destructive flex items-center gap-1 py-0.5 text-xs font-medium uppercase">
<AlertCircle className="h-3 w-3" />
<AlertCircle className="size-3" />
Error
</span>
<div className="ml-auto">
{!disabled && onRemove && (
<button type="button" aria-label="Delete message" data-testid="error-msg-delete" onClick={onRemove} className="rounded-sm p-1 opacity-0 transition-opacity group-hover:opacity-100 group-focus-within:opacity-100 hover:bg-muted focus:bg-muted focus:opacity-100">
<XIcon className="text-muted-foreground hover:text-foreground h-3 w-3 shrink-0 cursor-pointer" />
<XIcon className="text-muted-foreground hover:text-foreground size-3 shrink-0 cursor-pointer" />
</button>
)}
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export default function MessageRoleSwitcher({
)}
>
{role}
<ChevronDown className="h-3 w-3 opacity-0 transition-opacity group-hover:opacity-100 group-focus-within:opacity-100" />
<ChevronDown className="size-3 opacity-0 transition-opacity group-hover:opacity-100 group-focus-within:opacity-100" />
</button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export function MessagesView() {
const isLastMessageStreaming = isStreaming && lastMessage?.type === MessageType.CompletionResult;

return (
<div className="space-y-1 p-4">
<div className="space-y-1 px-1 py-4">
{messages.map((msg, index) => {
const isStreamingMsg = isLastMessageStreaming && index === messages.length - 1;
const canRemove = index > 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,12 +99,12 @@ export function SystemMessageView({
<div className="ml-auto flex items-center gap-0.5 h-5">
{!disabled && (
<button type="button" aria-label="Edit message" data-testid="system-msg-edit" onClick={() => setEditMode(true)} className="rounded-sm p-1 opacity-0 transition-opacity group-hover:opacity-100 group-focus-within:opacity-100 hover:bg-muted focus:bg-muted focus:opacity-100">
<PencilIcon className="text-muted-foreground hover:text-foreground h-3 w-3 shrink-0 cursor-pointer" />
<PencilIcon className="text-muted-foreground hover:text-foreground size-3 shrink-0 cursor-pointer" />
</button>
)}
{!disabled && onRemove && (
<button type="button" aria-label="Delete message" data-testid="system-msg-delete" onClick={onRemove} className="rounded-sm p-1 opacity-0 transition-opacity group-hover:opacity-100 group-focus-within:opacity-100 hover:bg-muted focus:bg-muted focus:opacity-100">
<XIcon className="text-muted-foreground hover:text-foreground h-3 w-3 shrink-0 cursor-pointer" />
<XIcon className="text-muted-foreground hover:text-foreground size-3 shrink-0 cursor-pointer" />
</button>
)}
</div>
Expand Down
Loading