Skip to content

feat(prompts): a standard way to pin a model and inference parameters on a prompt - #17784

Draft
benlangfeld wants to merge 2 commits into
langfuse:mainfrom
benlangfeld:claude/langfuse-prompt-model-params-22c46f
Draft

benlangfeld wants to merge 2 commits into
langfuse:mainfrom
benlangfeld:claude/langfuse-prompt-model-params-22c46f

Conversation

@benlangfeld

Copy link
Copy Markdown

What does this PR do?

A prompt's config is a free-form JSON object, and three surfaces had grown their own conventions on top of it. Tools already had a shared, tested parser (parsePromptToolConfig). The model and its parameters did not: provider and model were read by a private schema inside a React hook, and temperature, top_p, max_tokens, maxReasoningTokens and providerOptions were parsed out of the config and then thrown away. Pinning a temperature on a prompt had no effect on an experiment run.

This gives that facet the same home the tools facet has, and wires it through every surface that should honour it.

The standard

parsePromptModelConfig in @langfuse/shared, with the same none | valid | invalid contract as the tools parser. It owns provider, model, temperature, top_p, max_tokens, maxReasoningTokens and providerOptions, coerces numbers written as strings, ignores keys it does not own, and rejects the whole config if any one recognised key is unreadable rather than applying half of it.

Deliberately read-side only. Validating config on write would reject prompts carrying keys Langfuse does not know about, and the free-form object is a published contract.

What now uses it

  • Experiments apply the pinned parameters, not just the model. The subtle part: the adapter-change effect rewrites the tunables to that adapter's defaults whenever provider changes, so applying a prompt's parameters only on selection would silently lose them as soon as the prompt's provider differed from the current one. The overlay is re-applied there too, clamped to the adapter's temperature ceiling and respecting Anthropic's temperature/top_p exclusivity. An unreadable config warns next to the prompt picker, mirroring the existing tools warning.
  • "Open in playground" carried nothing from config, even though the generation branch a few lines below did exactly this mapping. It now carries the model, its parameters and the prompt's tools.
  • The prompt form gets a Model section above the JSON editor, using the same ModelParameters picker as the playground and experiments, opt-in via a checkbox. It derives from the config string on every render and writes back through the handlers — no mirrored state — so hand-editing the JSON and using the picker stay in agreement. It touches only the seven keys it owns; tools and your own metadata survive unpinning.
  • "Save as prompt" from the playground seeds those keys.

The provider/model list derivation moved to getLlmConnectionOptions so the form and the hook share it rather than forking. Its two orderings differ and are load-bearing (the combined picker lists default models first; a provider's own model list lists custom models first) — tests pin both.

Fern documents the reserved keys on config and lastConfig, and web/public/generated/api/openapi.yml is regenerated. Types stay unknown, so generated clients are unaffected.

Type of change

  • New feature (non-breaking change which adds functionality)
  • This change requires a documentation update

Mandatory Tasks

  • Make sure you have self-reviewed the code. A decent size PR without self-review might be rejected.

Verification

  • Tests 20 passed (20) — new shared parser suite
  • Tests 221 passed (221) — shared src/server/llm and src/features/prompts
  • Test Files 466 passed | 1 skipped (467), Tests 4565 passed | 51 skipped (4616) — full web client suite, including 11 new llmConnectionOptions tests and 9 new PromptModelConfigSection tests
  • Tasks: 8 successful, 8 total with Cached: 0 cached, 8 total — repo lint, executed rather than replayed from cache
  • All matched files use Prettier code style!
  • pnpm exec knip: clean; web and worker typecheck: clean
  • pnpm run openapi:check regenerated the served spec; it is committed here

What a reviewer should doubt

  • No page was loaded. The new prompt-form section's behaviour is covered by nine component tests, but nobody has looked at it. Docker was unavailable locally, so there was no stack to drive. Worth a look on the preview at /project/<id>/prompts/new, with and without LLM connections configured.
  • The write-back in PromptModelConfigSection keeps a ref to the last written config because the combined picker calls updateModelParamValue twice synchronously (provider, then model) and the second call would otherwise clobber the first. There is a test for it, but the pattern deserves a second opinion.
  • Re-applying pinned parameters on adapter change means a user's manual temperature edit is reverted to the prompt's value when they switch provider. That reads as correct to me — the prompt is the baseline — but it is a judgement call.
  • A config pinning only parameters and no model will not pre-fill the playground jump, because PlaygroundCache.modelParams requires a provider and model. Widening that type looked riskier than the gap is worth.
  • Two things left alone on purpose: tools are read into the playground but not written back on save, and maxReasoningTokens still never reaches mapLegacyLLMCompletionParams (pre-existing).

🤖 Generated with Claude Code

benlangfeld and others added 2 commits September 22, 2026 10:30
… on a prompt

A prompt's `config` is a free-form JSON object, and three surfaces had grown
their own conventions on top of it. Tools had a shared, tested parser; the
model and its parameters did not - `provider` and `model` were read by a
private schema inside a React hook, and `temperature`, `top_p`, `max_tokens`,
`maxReasoningTokens` and `providerOptions` were parsed out of the config and
then discarded. Pinning a temperature on a prompt had no effect on an
experiment run.

Give that facet the same home the tools facet has: `parsePromptModelConfig` in
shared, with a `none | valid | invalid` contract. Reading only - validating
`config` on write would reject prompts carrying keys Langfuse does not know,
and the free-form object is a published contract.

Experiments now apply the pinned parameters, not just the model. Switching
provider resets the tunables to that adapter's defaults, so the overlay is
re-applied there too, clamped to the adapter's temperature ceiling and
respecting Anthropic's temperature/top_p exclusivity. An unreadable config
warns next to the prompt picker rather than running with half of it.

"Open in playground" carried nothing from `config`; it now carries the model,
its parameters and the prompt's tools. Saving a playground window as a prompt
writes the model parameters back.

The prompt form gets a Model section above the JSON editor, using the same
picker as the playground and experiments. It derives from the config string
and writes back through the handlers, so hand-editing the JSON and using the
picker stay in agreement, and it touches only the keys it owns.

The provider and model list derivation moves to `getLlmConnectionOptions` so
the form and the hook share it rather than forking. Its two orderings differ
and are load-bearing; tests pin them.

Fern documents the reserved keys on `config` and `lastConfig`. Types stay
`unknown`, so generated clients are unaffected.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
main moved the playground's cross-feature imports behind a barrel
(`@/src/features/playground`), which collided with the new `PlaygroundCache`
import in the prompt form.

Take the barrel, and route the rest of this branch's prompts -> playground
imports through it too: `PlaygroundCache`, `getDefaultAdapterParams`,
`getLlmConnectionOptions` and `resolveConnectionProvider` join the feature's
public surface.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant