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
5 changes: 4 additions & 1 deletion core/schemas/bifrost.go
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,9 @@ const (
BifrostContextKeyGovernanceBusinessUnitName BifrostContextKey = "bifrost-governance-business-unit-name" // string (to store the business unit name (set by enterprise governance plugin - DO NOT SET THIS MANUALLY))
BifrostContextKeyGovernanceRoutingRuleID BifrostContextKey = "bifrost-governance-routing-rule-id" // string (to store the routing rule ID (set by bifrost governance plugin - DO NOT SET THIS MANUALLY))
BifrostContextKeyGovernanceRoutingRuleName BifrostContextKey = "bifrost-governance-routing-rule-name" // string (to store the routing rule name (set by bifrost governance plugin - DO NOT SET THIS MANUALLY))
BifrostContextKeySelectedPromptName BifrostContextKey = "bifrost-selected-prompt-name" // string (display name of the selected prompt (set by prompts plugin - DO NOT SET THIS MANUALLY))
BifrostContextKeySelectedPromptVersion BifrostContextKey = "bifrost-selected-prompt-version" // string (numeric version as string, e.g. "3" (set by prompts plugin - DO NOT SET THIS MANUALLY))
BifrostContextKeySelectedPromptID BifrostContextKey = "bifrost-selected-prompt-id" // string (id of the selected prompt (set by prompts plugin - DO NOT SET THIS MANUALLY))
BifrostContextKeyGovernanceIncludeOnlyKeys BifrostContextKey = "bf-governance-include-only-keys" // []string (to store the include-only key IDs for provider config routing (set by bifrost governance plugin - DO NOT SET THIS MANUALLY))
BifrostContextKeyNumberOfRetries BifrostContextKey = "bifrost-number-of-retries" // int (to store the number of retries (set by bifrost - DO NOT SET THIS MANUALLY))
BifrostContextKeyFallbackIndex BifrostContextKey = "bifrost-fallback-index" // int (to store the fallback index (set by bifrost - DO NOT SET THIS MANUALLY)) 0 for primary, 1 for first fallback, etc.
Expand Down Expand Up @@ -222,6 +225,7 @@ const (
BifrostContextKeyHasEmittedMessageDelta BifrostContextKey = "bifrost-has-emitted-message-delta" // bool (tracks whether message_delta was already emitted during streaming - avoids duplicates)
BifrostContextKeySkipDBUpdate BifrostContextKey = "bifrost-skip-db-update" // bool (set by bifrost - DO NOT SET THIS MANUALLY))
BifrostContextKeyGovernancePluginName BifrostContextKey = "governance-plugin-name" // string (name of the governance plugin that processed the request - set by bifrost)
BifrostContextKeyPromptsPluginName BifrostContextKey = "prompts-plugin-name" // string (name of the prompts plugin to use - set by bifrost - DO NOT SET THIS MANUALLY))
BifrostContextKeyIsEnterprise BifrostContextKey = "is-enterprise" // bool (set by bifrost - DO NOT SET THIS MANUALLY))
BifrostContextKeyAvailableProviders BifrostContextKey = "available-providers" // []ModelProvider (set by bifrost - DO NOT SET THIS MANUALLY))
BifrostContextKeyStoreRawRequestResponse BifrostContextKey = "bifrost-store-raw-request-response" // bool (per-request override — read by bifrost.go, never overwritten)
Expand All @@ -235,7 +239,6 @@ const (
BifrostContextKeyHTTPRequestType BifrostContextKey = "bifrost-http-request-type" // RequestType (set by bifrost - DO NOT SET THIS MANUALLY))
BifrostContextKeyPassthroughExtraParams BifrostContextKey = "bifrost-passthrough-extra-params" // bool
BifrostContextKeyRoutingEnginesUsed BifrostContextKey = "bifrost-routing-engines-used" // []string (set by bifrost - DO NOT SET THIS MANUALLY) - list of routing engines used ("routing-rule", "governance", "loadbalancing", etc.)
BifrostContextKeyPromptStreamRequest BifrostContextKey = "bifrost-prompt-stream-request" // bool (set by prompts HTTP plugin when prompt version model_params.stream is true and body omitted stream)
BifrostContextKeyRoutingEngineLogs BifrostContextKey = "bifrost-routing-engine-logs" // []RoutingEngineLogEntry (set by bifrost - DO NOT SET THIS MANUALLY) - list of routing engine log entries
BifrostContextKeyTransportPluginLogs BifrostContextKey = "bifrost-transport-plugin-logs" // []PluginLogEntry (transport-layer plugin logs accumulated during HTTP transport hooks)
BifrostContextKeyTransportPostHookCompleter BifrostContextKey = "bifrost-transport-posthook-completer" // func() (callback to run HTTPTransportPostHook after streaming - set by transport interceptor middleware)
Expand Down
1 change: 1 addition & 0 deletions docs/features/observability/default.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ Bifrost traces comprehensive information for every request, without any changes
- **Input Messages**: Complete conversation history and user prompts
- **Model Parameters**: Temperature, max tokens, tools, and all other parameters
- **Provider Context**: Which provider and model handled the request
- **Prompt Tracking**: When the [Prompts plugin](/features/prompt-repository/prompts-plugin) is active, the log captures the selected prompt name, version number, and ID for full traceability

### **Response Data**
- **Output Messages**: AI responses, tool calls, and function results
Expand Down
7 changes: 6 additions & 1 deletion docs/features/prompt-repository/playground.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,12 @@ The playground uses a simple **three-panel layout**:
|------|---------|
| **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 |
| **Settings (right)** | Configure provider, model, API key, variables, parameters, and deployments |

The settings panel is organized into collapsible sections:

- **Configuration** — Provider, model, API key, variables, and model parameters
- **Deployments** — Prompt deployment strategies and traffic routing (enterprise)

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

Expand Down
44 changes: 29 additions & 15 deletions docs/features/prompt-repository/prompts-plugin.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,13 @@ The **Prompts** plugin connects the [Prompt Repository](/features/prompt-reposit
```mermaid
flowchart TB
Client([Client]) --> Gateway[Bifrost HTTP]
Gateway --> PreHook["HTTP transport pre-hook:<br/>copy bf-prompt-id / bf-prompt-version to context"]
Gateway --> PreHook["HTTP transport pre-hook:<br/>copy x-bf-prompt-id / x-bf-prompt-version to context"]
PreHook --> PreLLM["PreLLM hook:<br/>resolve version, merge params,<br/>prepend template messages"]
PreLLM --> Provider[Provider]
```

1. **Transport (HTTP):** Incoming headers `bf-prompt-id` and `bf-prompt-version` are copied onto the Bifrost context (header name matching is case-insensitive).
2. **Resolve:** The plugin looks up the prompt and the requested version. If **`bf-prompt-version` is omitted**, the prompt’s **latest committed version** is used.
1. **Transport (HTTP):** Incoming headers `x-bf-prompt-id` and `x-bf-prompt-version` are copied onto the Bifrost context (header name matching is case-insensitive).
2. **Resolve:** The plugin looks up the prompt and the requested version. If **`x-bf-prompt-version` is omitted**, the prompt’s **latest committed version** is used.
3. **Parameters:** Version `model` parameters are merged into the request; any field already set on the request wins.
4. **Messages:** Messages from the committed version are **prepended** to `messages` (chat) or `input` (responses). Your request body adds the user turn(s) after the template.

Expand All @@ -47,8 +47,8 @@ If the prompt ID is missing, the plugin does nothing and the request passes thro

| Header | Required | Description |
|--------|----------|-------------|
| `bf-prompt-id` | Yes, to enable injection | UUID of the prompt in the repository. |
| `bf-prompt-version` | No | **Integer version number** (e.g. `3` for v3). If omitted, the **latest** committed version for that prompt is used. |
| `x-bf-prompt-id` | Yes, to enable injection | UUID of the prompt in the repository. |
| `x-bf-prompt-version` | No | **Integer version number** (e.g. `3` for v3). If omitted, the **latest** committed version for that prompt is used. |

Invalid or unknown IDs / versions are logged as warnings; the request is **not** failed by the plugin (it proceeds without template injection).

Expand All @@ -61,7 +61,7 @@ Use the same JSON body as a normal chat request. Only the headers select the tem
```bash
curl -X POST http://localhost:8080/v1/chat/completions \
-H "Content-Type: application/json" \
-H "bf-prompt-id: YOUR-PROMPT-UUID" \
-H "x-bf-prompt-id: YOUR-PROMPT-UUID" \
-H "x-bf-vk: sk-bf-your-virtual-key" \
-d '{
"model": "openai/gpt-5.4",
Expand All @@ -76,13 +76,13 @@ curl -X POST http://localhost:8080/v1/chat/completions \

![Commit Version with Stream enabled in the playground](../../media/prompt-plugin-version-commit.png)

When you commit a version from the playground, **Stream** is saved in that version’s model parameters. The example `curl` above does not set `"stream": true` in the JSON body, but if the committed version was saved with streaming enabled (as in the screenshot), the merged parameters still include `stream: true`, so the request is handled as **streaming** even though the client did not send `stream` explicitly.
When you commit a version from the playground, the model parameters (temperature, max tokens, etc.) are saved with it. These parameters are merged into the outgoing request, with client-supplied values taking precedence.

![LLM log for the same request showing Type: Chat Stream](../../media/prompt-plugin-llm-log.png)
Comment thread
greptile-apps[bot] marked this conversation as resolved.

In **Logs**, that run shows **Type: Chat Stream** and the full conversation: the committed **system** template, your **user** message from the request body, and the assistant reply.
In **Logs**, that run shows the full conversation: the committed **system** template, your **user** message from the request body, and the assistant reply. The log also displays the **Selected Prompt** name and version number for easy traceability.

The provider receives the **stored** messages from the prompt version, checks if the request is streaming or non-streaming, applies the additional model parameters from the request and prepends the messages from the prompt version followed by your user message.
The provider receives the merged model parameters from both the prompt version and the client request, with the messages from the committed version prepended before the client’s messages.

---

Expand All @@ -91,8 +91,8 @@ The provider receives the **stored** messages from the prompt version, checks if
```bash
curl -X POST http://localhost:8080/v1/responses \
-H "Content-Type: application/json" \
-H "bf-prompt-id: YOUR-PROMPT-UUID" \
-H "bf-prompt-version: 4" \
-H "x-bf-prompt-id: YOUR-PROMPT-UUID" \
-H "x-bf-prompt-version: 4" \
-H "x-bf-vk: sk-bf-your-virtual-key" \
-d '{
"model": "openai/gpt-5-nano-2025-08-07",
Expand All @@ -104,7 +104,7 @@ curl -X POST http://localhost:8080/v1/responses \

## Streaming

If the committed version’s **model parameters** include `"stream": true`, the plugin may set streaming on the HTTP transport so behavior matches the saved version. Client-side `stream` flags still interact with the merged parameters as usual.
Streaming is controlled entirely by the client request. If you want streaming, set `"stream": true` in the request body. The plugin merges model parameters from the committed version (request values take precedence), but does **not** override the transport-level streaming mode.

---

Expand All @@ -118,12 +118,26 @@ The plugin keeps an in-memory cache of prompts and versions (loaded with a small

For embedded Bifrost (Go SDK), register the plugin with `prompts.Init` and a **config store** that implements the prompt tables API. The default resolver reads the same logical keys from `BifrostContext`:

- `prompts.PromptIDKey` (`bf-prompt-id`)
- `prompts.PromptVersionKey` (`bf-prompt-version`)
- `prompts.PromptIDKey` (`x-bf-prompt-id`)
- `prompts.PromptVersionKey` (`x-bf-prompt-version`)

Set them on the context you pass to `ChatCompletion` / `Responses` if you are not going through the HTTP transport hooks.

For advanced routing (for example, choosing a prompt from governance metadata), implement `prompts.PromptResolver` in `plugins/prompts/main.go` and use **`prompts.InitWithResolver`**.
For advanced routing (for example, choosing a prompt from governance metadata), implement `prompts.PromptResolver` and use **`prompts.InitWithResolver`**. The interface is:

```go
type PromptResolver interface {
Resolve(ctx *schemas.BifrostContext, req *schemas.BifrostRequest) (promptID string, versionNumber int, err error)
}
```

Return an empty `promptID` to skip injection for a request. Return `versionNumber == 0` to use the prompt's **latest** committed version; any positive integer selects that specific version.

After injection, the plugin sets the following context keys (read by the logging plugin to populate log fields):

- `schemas.BifrostContextKeySelectedPromptID` — UUID of the applied prompt
- `schemas.BifrostContextKeySelectedPromptName` — Display name of the prompt
- `schemas.BifrostContextKeySelectedPromptVersion` — Version number as a string (e.g. `"3"`)

---

Expand Down
47 changes: 47 additions & 0 deletions framework/configstore/migrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,9 @@ func triggerMigrations(ctx context.Context, db *gorm.DB) error {
if err := migrationAddRoutingChainMaxDepthColumn(ctx, db); err != nil {
return err
}
if err := migrationAddPromptVariablesColumns(ctx, db); err != nil {
return err
}
if err := migrationAddModelCapabilityColumns(ctx, db); err != nil {
return err
}
Expand Down Expand Up @@ -5466,6 +5469,50 @@ func migrationAddOpenAIConfigJSONColumn(ctx context.Context, db *gorm.DB) error
return nil
}

// migrationAddPromptVariablesColumns adds variables_json column to prompt_sessions and prompt_versions
func migrationAddPromptVariablesColumns(ctx context.Context, db *gorm.DB) error {
m := migrator.New(db, migrator.DefaultOptions, []*migrator.Migration{{
ID: "add_prompt_variables_columns",
Migrate: func(tx *gorm.DB) error {
tx = tx.WithContext(ctx)
migrator := tx.Migrator()

if !migrator.HasColumn(&tables.TablePromptSession{}, "variables_json") {
if err := migrator.AddColumn(&tables.TablePromptSession{}, "VariablesJSON"); err != nil {
return fmt.Errorf("failed to add variables_json column to prompt_sessions: %w", err)
}
}

if !migrator.HasColumn(&tables.TablePromptVersion{}, "variables_json") {
if err := migrator.AddColumn(&tables.TablePromptVersion{}, "VariablesJSON"); err != nil {
return fmt.Errorf("failed to add variables_json column to prompt_versions: %w", err)
}
}

return nil
},
Rollback: func(tx *gorm.DB) error {
tx = tx.WithContext(ctx)
migrator := tx.Migrator()
if migrator.HasColumn(&tables.TablePromptSession{}, "variables_json") {
if err := migrator.DropColumn(&tables.TablePromptSession{}, "variables_json"); err != nil {
return err
}
}
if migrator.HasColumn(&tables.TablePromptVersion{}, "variables_json") {
if err := migrator.DropColumn(&tables.TablePromptVersion{}, "variables_json"); err != nil {
return err
}
}
return nil
},
}})
if err := m.Migrate(); err != nil {
return fmt.Errorf("error while running add_prompt_variables_columns migration: %s", err.Error())
}
return nil
}

// migrationAddKeyBlacklistedModelsJSONColumn adds blacklisted_models_json to config_keys
// for per-key model deny lists (JSON array of model ids, default []).
func migrationAddKeyBlacklistedModelsJSONColumn(ctx context.Context, db *gorm.DB) error {
Expand Down
22 changes: 22 additions & 0 deletions framework/configstore/tables/promptSessions.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ type TablePromptSession struct {
ModelParams ModelParams `gorm:"-" json:"model_params"`
Provider string `gorm:"type:varchar(100)" json:"provider"`
Model string `gorm:"type:varchar(100)" json:"model"`
VariablesJSON *string `gorm:"type:text;column:variables_json" json:"-"`
Variables PromptVariables `gorm:"-" json:"variables,omitempty"` // {key: value} map for Jinja2 variables
CreatedAt time.Time `gorm:"not null" json:"created_at"`
UpdatedAt time.Time `gorm:"not null" json:"updated_at"`

Expand All @@ -40,6 +42,17 @@ func (s *TablePromptSession) BeforeSave(tx *gorm.DB) error {
}
paramsStr := string(data)
s.ModelParamsJSON = &paramsStr

if s.Variables != nil {
varsData, err := json.Marshal(s.Variables)
if err != nil {
return err
}
varsStr := string(varsData)
s.VariablesJSON = &varsStr
} else {
s.VariablesJSON = nil
}
return nil
}

Expand All @@ -52,6 +65,15 @@ func (s *TablePromptSession) AfterFind(tx *gorm.DB) error {
return err
}
}
if s.VariablesJSON != nil && *s.VariablesJSON != "" {
var vars PromptVariables
if err := json.Unmarshal([]byte(*s.VariablesJSON), &vars); err != nil {
return err
}
s.Variables = vars
} else {
s.Variables = nil
}
return nil
}

Expand Down
Loading
Loading