Skip to content

Managed prompts: prompt()/template_prompt() consumption + prompts in variables_push()#2030

Open
dmontagu wants to merge 4 commits into
mainfrom
claude/awesome-faraday-ylmgjj
Open

Managed prompts: prompt()/template_prompt() consumption + prompts in variables_push()#2030
dmontagu wants to merge 4 commits into
mainfrom
claude/awesome-faraday-ylmgjj

Conversation

@dmontagu

@dmontagu dmontagu commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Adds first-class managed prompt support to the SDK: slug-based consumption helpers, and code-first prompt creation via variables_push().

Consumption: logfire.prompt() / logfire.template_prompt()

Prompts in Logfire are managed variables under the hood, distinguished by a prompt__ name prefix. Before this PR you consumed a prompt by manually declaring its backing variable (logfire.var(name='prompt__support_agent', type=str, ...)); these helpers hide the naming convention behind the slug:

# Plain prompt (string value)
support = logfire.prompt('support-agent', default='You are a helpful support agent.')
with support.get(label='production') as resolved:
    system_prompt = resolved.value

# Handlebars-templated prompt with typed inputs
support = logfire.template_prompt('support-agent', default='Helping {{name}}.', inputs_type=MyInputs)
with support.get(MyInputs(name='Alice'), label='production') as resolved:
    system_prompt = resolved.value  # -> 'Helping Alice.'
  • prompt() is var() specialized to prompts: value type fixed to str, slug translated to the backing-variable name. Returns Variable[str].
  • template_prompt() is the same specialization of template_var() (adds inputs_type + template_mismatch_policy). Returns TemplateVariable[str, InputsT].
  • Both delegate entirely to the existing var()/template_var() infrastructure.

Naming / validation

logfire/variables/_prompt.py holds the single slug↔name translation (prompt_variable_name() and its inverse prompt_slug_from_variable_name()). Slugs are validated against the backend's exact rule (^[a-z0-9-]{1,100}$) and raise ValueError loudly at declaration time — a mismatched name (e.g. uppercased) would silently resolve to a different variable and fall back to the code default. Hyphens→underscores is injective over the slug charset, so there is no collision or round-trip loss. An accidental prompt__ prefix warns and strips.

Authoring: variables_push() now creates prompts

logfire.variables_push() pushes everything declared in code — general variables through the variables API as before, and prompt-backed variables through the prompts API (POST /v1/prompts/, requires project:write_variables):

  • Missing prompts are created, publishing a static-string code default as version 1 — the prompt appears on the Prompts page ready for iteration and serves exactly what the code default already said (fresh prompts serve latest). Function defaults create the prompt with no initial version.
  • Existing prompts are never modified by a push — new versions are published on the Prompts page, over MCP, or via the prompts API. This mirrors variables_push's "never touch values" stance.
  • template_prompt() declarations also push their template_inputs_schema (the one prompt field the variables API accepts for prompt rows).

Design choice we're not sure about: prompts are handled inside variables_push() (one entry point pushes everything, routing per-kind under the hood) rather than a dedicated prompts_push(). The upside is you don't have to remember N <thing>_push() calls; the downside is variables_push semantics get less uniform (prompts publish a v1 on create, variables never create versions). Feedback welcome — a split into prompts_push() would be mechanical.

Docs

  • application.md gains a prominent "API Key Required" callout: prompts resolve through the managed-variables API, which needs LOGFIRE_API_KEY (scope project:read_variables) — not the write token. Without it the SDK never contacts Logfire and .get() silently serves the code default. The composition and rollout pages link back to it.
  • New "Create prompts from code" section documenting the push flow.
  • All prompt-management examples use the new helpers.

Scope boundaries

  • The full logfire.prompts_*() management family (get/list/update/settings/labels) from the design doc is still pending; MCP already covers interactive authoring.
  • logfire-api shim excludes these names (like the whole variables family); prompt users need logfire[variables].
  • pydantic-ai ManagedPrompt integration (delegating its naming to prompt_variable_name()) lives in the pydantic-ai repo, not here.

Testing

  • tests/test_prompts.py: naming (incl. inverse), resolution, templating, validation, and the push flows (create with v1, existing untouched, template inputs schema, function default, dry run) — driven through LocalVariableProvider, which implements the same create_prompt primitive as the remote provider.
  • Verified end-to-end against a local platform stack running the /v1/prompts/ branch (pydantic/platform#23832): push creates the prompt via the API, the Prompts page shows it, and prompt().get() resolves the pushed version.
  • pyright, ruff, docs-example tests all green.

claude and others added 3 commits June 16, 2026 15:47
Declaring prompt-backed managed variables required hand-writing the backend's
prompt__<name> convention at every call site (and re-deriving it in the
pydantic-ai ManagedPrompt capability). These helpers specialize var()/template_var()
to str and own that name translation in one place, so a prompt declared in code
resolves to the same backing variable the backend and UI manage. The shared
normalizer is exported as logfire.variables.prompt_variable_name for reuse.

https://claude.ai/code/session_01Nm4UP61HFEJEZrMu18bGpQ
logfire.variables_push() now pushes everything declared in code: general
variables through the variables API as before, and prompt-backed variables
(from logfire.prompt() / logfire.template_prompt()) through the prompts API.
Missing prompts are created with the static-string code default published as
version 1 — so they appear on the Prompts page ready for iteration and serve
the same content the code default already produced — and existing prompts are
never modified by a push. template_prompt() declarations also push their
template_inputs_schema.

Supporting changes:
- prompt_slug_from_variable_name() inverse helper in logfire/variables/_prompt.py
- create_prompt() provider primitive with local (in-memory, platform-shaped) and
  remote (POST /v1/prompts/ + versions + inputs-schema PUT) implementations
- prompt rows excluded from the diff's server-only section (they have their own
  sections and their own page)
- docs: "API Key Required" callout (resolution needs LOGFIRE_API_KEY, not the
  write token — otherwise .get() silently serves the code default), a "Create
  prompts from code" section, and pointers from the composition/rollout pages
- prompt_variable_name() docstring no longer claims pydantic-ai already uses it

Verified end-to-end against a local platform stack running the /v1/prompts/
branch: push creates the prompts, the UI lists them, .get() serves the pushed
version, and a second push is a clean no-op.

Claude-Session: https://claude.ai/code/session_01ApEni3KiYfaiGTwZN7K58R
@dmontagu dmontagu changed the title Add prompt() and template_prompt() convenience methods Managed prompts: prompt()/template_prompt() consumption + prompts in variables_push() Jul 1, 2026
Adds tests for the slug-inverse ValueError, the base provider's warn-only
create_prompt default, the local duplicate-slug conflict, and the remote
provider's create_prompt (full request shapes, minimal body, 409 conflict,
and HTTP-error wrapping) via requests-mock.

Claude-Session: https://claude.ai/code/session_01ApEni3KiYfaiGTwZN7K58R
@dmontagu dmontagu marked this pull request as ready for review July 2, 2026 03:56
@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR introduces public logfire.prompt() and logfire.template_prompt() APIs for managed prompt variables, backed by a new prompt-slug naming convention (prompt__ prefix) with validation and conversion utilities. VariableProvider gains a create_prompt() method, implemented for local and remote providers, and push_variables() now creates missing prompts separately from regular variable diffs. Extensive tests cover naming, resolution, push behavior, and provider creation. Documentation across prompt-management pages is updated to reference the new APIs instead of var()/template_var().

Changes

Cohort / File(s) Summary
Naming utilities (logfire/variables/_prompt.py, logfire/variables/__init__.py) Added PROMPT_VARIABLE_PREFIX, prompt_variable_name(), prompt_slug_from_variable_name(); exported publicly.
Public API (logfire/__init__.py, logfire/_internal/main.py) Added Logfire.prompt(), Logfire.template_prompt(), module-level exports, updated variables_push() docs.
Provider support (logfire/variables/abstract.py, local.py, remote.py) Added create_prompt() base/local/remote implementations; updated push_variables() to handle prompt-backed variables separately.
Tests (tests/test_prompts.py, test_logfire_api.py, type_checking.py) Added comprehensive test coverage for naming, resolution, push, and creation; updated API surface exclusions and type-checking assertions.
Docs (docs/reference/advanced/prompt-management/*.md) Updated examples and prose to use prompt()/template_prompt() instead of var()/template_var().

Sequence Diagram(s)

sequenceDiagram
  participant App
  participant VariablesPush as variables_push()
  participant Provider as VariableProvider
  participant Remote as RemoteVariableProvider
  App->>VariablesPush: variables_push()
  VariablesPush->>Provider: split prompt vs regular variables
  Provider->>Provider: diff regular variables
  Provider->>Provider: detect missing prompts
  Provider->>Remote: create_prompt(slug, name, template)
  Remote->>Remote: POST /v1/prompts/
  Remote->>Remote: POST /v1/prompts/{slug}/versions/
  Remote->>Remote: PUT /v1/variables/{name}/
  Remote-->>Provider: refresh(force=True)
Loading

Related Issues: None referenced.

Related PRs: None referenced.

Suggested labels: enhancement, documentation, prompts, variables

Suggested reviewers: None determinable from provided summaries.

Poem
A rabbit hopped through prompts anew,
prompt() and template_prompt() in view,
Slugs turned to vars with hyphens shed,
Push created what was missing instead,
Docs rewritten, tests hopping too! 🐰

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: managed prompt consumption helpers and prompt creation in variables_push().
Description check ✅ Passed The description is directly related to the changeset and explains the new prompt helpers, push behavior, docs, and tests.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/awesome-faraday-ylmgjj

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/reference/advanced/prompt-management/application.md`:
- Around line 88-95: The version-1 guarantee in the prompt push description is
too broad and should be narrowed to match the implementation contract. Update
the wording in the prompt-management docs around logfire.variables_push() so it
states that only prompts with static-string defaults are created and published
as version 1, while callable or dynamic defaults are described separately or
left unspecified. Use the prompt push description in application.md to locate
the text.

In `@docs/reference/advanced/prompt-management/templates.md`:
- Around line 12-15: The wording around logfire.prompt() is misleading because
it implies a fully raw template, but prompt() still resolves @{...}@ composition
references before returning the result. Update the documentation near prompt()
and template_prompt() to state that prompt() leaves only the remaining {{...}}
placeholders for the application to render, while template_prompt() handles
runtime variable substitution before sending to the model client.

In `@logfire/variables/abstract.py`:
- Around line 1697-1706: The derived prompt display name in the missing-prompts
creation flow uses capitalized only the first word, so update the name
generation in the loop inside the prompt creation logic to use a title-cased
version of slug.replace('-', ' ') instead. Keep the existing slug derivation and
create_prompt call intact, but ensure the generated name for multi-word slugs is
consistently title-cased when auto-creating prompts.

In `@logfire/variables/remote.py`:
- Around line 522-577: The create_prompt flow can leave a partially created
prompt behind if the version or schema write fails after the initial POST
succeeds, causing later push_variables() runs to skip that slug as already
existing. Update create_prompt to either use an atomic create-with-version path
if available or add recovery logic that detects an incomplete prompt and
finishes publishing the template and template_inputs_schema on retry. Keep the
behavior centered around create_prompt, VariableAlreadyExistsError, and the
prompt/version/schema API calls so the slug can be resumed instead of abandoned.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 9f5e5f7b-49bd-4691-965e-3689b8006d23

📥 Commits

Reviewing files that changed from the base of the PR and between ef5c776 and 5fbba0d.

📒 Files selected for processing (16)
  • docs/reference/advanced/prompt-management/application.md
  • docs/reference/advanced/prompt-management/composition-walkthrough.md
  • docs/reference/advanced/prompt-management/promotion-and-rollouts.md
  • docs/reference/advanced/prompt-management/template-reference.md
  • docs/reference/advanced/prompt-management/templates.md
  • docs/reference/advanced/prompt-management/ui.md
  • logfire/__init__.py
  • logfire/_internal/main.py
  • logfire/variables/__init__.py
  • logfire/variables/_prompt.py
  • logfire/variables/abstract.py
  • logfire/variables/local.py
  • logfire/variables/remote.py
  • tests/test_logfire_api.py
  • tests/test_prompts.py
  • tests/type_checking.py
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • pydantic/pydantic (auto-detected)
  • pydantic/pydantic-ai (auto-detected)

Comment on lines +88 to +95
If a prompt only exists in your code so far, you don't have to recreate it by
hand in the UI. `logfire.variables_push()` pushes everything declared in code —
managed variables *and* prompts. For each prompt that doesn't exist in the
project yet, it creates the prompt and publishes your code `default` as version
1, so it appears on the Prompts page ready for iteration and serves exactly what
your code default already said. Prompts that already exist are never modified by
a push — publish new versions on the Prompts page, over MCP, or via the prompts
API.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Scope the version-1 guarantee to static-string defaults.

This reads like any prompt default is pushed as version 1. The contract shown in the implementation docstring only guarantees that for static-string defaults, so callable/dynamic defaults need a narrower description.

Suggested wording
- For each prompt that doesn't exist yet, it creates the prompt and publishes your code default as version 1, so it appears on the Prompts page ready for iteration and serves exactly what your code default already said.
+ For each prompt that doesn't exist yet, it creates the prompt and publishes its static-string code default as version 1, so it appears on the Prompts page ready for iteration and serves exactly what that default already said.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
If a prompt only exists in your code so far, you don't have to recreate it by
hand in the UI. `logfire.variables_push()` pushes everything declared in code —
managed variables *and* prompts. For each prompt that doesn't exist in the
project yet, it creates the prompt and publishes your code `default` as version
1, so it appears on the Prompts page ready for iteration and serves exactly what
your code default already said. Prompts that already exist are never modified by
a push — publish new versions on the Prompts page, over MCP, or via the prompts
API.
If a prompt only exists in your code so far, you don't have to recreate it by
hand in the UI. `logfire.variables_push()` pushes everything declared in code —
managed variables *and* prompts. For each prompt that doesn't exist in the
project yet, it creates the prompt and publishes its static-string code default as version
1, so it appears on the Prompts page ready for iteration and serves exactly what
that default already said. Prompts that already exist are never modified by
a push — publish new versions on the Prompts page, over MCP, or via the prompts
API.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/reference/advanced/prompt-management/application.md` around lines 88 -
95, The version-1 guarantee in the prompt push description is too broad and
should be narrowed to match the implementation contract. Update the wording in
the prompt-management docs around logfire.variables_push() so it states that
only prompts with static-string defaults are created and published as version 1,
while callable or dynamic defaults are described separately or left unspecified.
Use the prompt push description in application.md to locate the text.

Comment on lines +12 to 15
`logfire.template_prompt()` to substitute runtime variables before sending the
rendered prompt to your model client. If you fetch the raw template with
`logfire.var()`, your application must render the remaining `{{...}}`
`logfire.prompt()`, your application must render the remaining `{{...}}`
placeholders itself. See [Use Prompts in Your Application](./application.md).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Avoid calling logfire.prompt() the raw template.

prompt() still expands @{...}@ composition refs first; only the remaining {{...}} placeholders are left for your app. "Raw template" makes it sound like composition refs stay untouched.

Suggested wording
- If you fetch the raw template with `logfire.prompt()`, your application must render the remaining `{{...}}` placeholders itself.
+ If you fetch the post-composition template with `logfire.prompt()`, your application must render the remaining `{{...}}` placeholders itself.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
`logfire.template_prompt()` to substitute runtime variables before sending the
rendered prompt to your model client. If you fetch the raw template with
`logfire.var()`, your application must render the remaining `{{...}}`
`logfire.prompt()`, your application must render the remaining `{{...}}`
placeholders itself. See [Use Prompts in Your Application](./application.md).
`logfire.template_prompt()` to substitute runtime variables before sending the
rendered prompt to your model client. If you fetch the post-composition template with
`logfire.prompt()`, your application must render the remaining `{{...}}`
placeholders itself. See [Use Prompts in Your Application](./application.md).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/reference/advanced/prompt-management/templates.md` around lines 12 - 15,
The wording around logfire.prompt() is misleading because it implies a fully raw
template, but prompt() still resolves @{...}@ composition references before
returning the result. Update the documentation near prompt() and
template_prompt() to state that prompt() leaves only the remaining {{...}}
placeholders for the application to render, while template_prompt() handles
runtime variable substitution before sending to the model client.

Comment on lines +1697 to +1706
for prompt_variable in missing_prompts:
slug = prompt_slug_from_variable_name(prompt_variable.name)
self.create_prompt(
slug=slug,
name=slug.replace('-', ' ').capitalize(),
description=prompt_variable.description,
template=prompt_variable.default if isinstance(prompt_variable.default, str) else None,
template_inputs_schema=get_template_inputs_schema(prompt_variable),
)
print(f' {ANSI_GREEN}Created prompt: {slug}{ANSI_RESET}')

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Use .title() for the derived prompt name.

slug.replace('-', ' ').capitalize() only capitalizes the first word — support-agent becomes "Support agent" instead of "Support agent" → should be "Support Agent". Multi-word slugs will show inconsistently-cased names on the Prompts page. This path isn't covered by test_remote_create_prompt_full/_minimal since those call create_prompt() directly with an explicit name=.

🐛 Proposed fix
-                    name=slug.replace('-', ' ').capitalize(),
+                    name=slug.replace('-', ' ').title(),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for prompt_variable in missing_prompts:
slug = prompt_slug_from_variable_name(prompt_variable.name)
self.create_prompt(
slug=slug,
name=slug.replace('-', ' ').capitalize(),
description=prompt_variable.description,
template=prompt_variable.default if isinstance(prompt_variable.default, str) else None,
template_inputs_schema=get_template_inputs_schema(prompt_variable),
)
print(f' {ANSI_GREEN}Created prompt: {slug}{ANSI_RESET}')
for prompt_variable in missing_prompts:
slug = prompt_slug_from_variable_name(prompt_variable.name)
self.create_prompt(
slug=slug,
name=slug.replace('-', ' ').title(),
description=prompt_variable.description,
template=prompt_variable.default if isinstance(prompt_variable.default, str) else None,
template_inputs_schema=get_template_inputs_schema(prompt_variable),
)
print(f' {ANSI_GREEN}Created prompt: {slug}{ANSI_RESET}')
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@logfire/variables/abstract.py` around lines 1697 - 1706, The derived prompt
display name in the missing-prompts creation flow uses capitalized only the
first word, so update the name generation in the loop inside the prompt creation
logic to use a title-cased version of slug.replace('-', ' ') instead. Keep the
existing slug derivation and create_prompt call intact, but ensure the generated
name for multi-word slugs is consistently title-cased when auto-creating
prompts.

Comment on lines +522 to +577
def create_prompt(
self,
*,
slug: str,
name: str,
description: str | None = None,
template: str | None = None,
template_inputs_schema: dict[str, Any] | None = None,
) -> None:
"""Create a prompt via the remote prompts API.

Creates the prompt with `POST /v1/prompts/` (requires an API key with the
`project:write_variables` scope), publishes `template` as version 1 when given,
and sets `template_inputs_schema` on the backing variable (the one prompt field
the variables API accepts).

Args:
slug: The prompt slug, e.g. `support-agent`.
name: Human-readable prompt name shown on the Prompts page.
description: Optional prompt description.
template: When set, published as the prompt's first version.
template_inputs_schema: JSON Schema for `{{field}}` template inputs.

Raises:
VariableAlreadyExistsError: If a prompt with this slug already exists.
VariableWriteError: If any API request fails.
"""
body: dict[str, Any] = {'name': name, 'slug': slug}
if description is not None:
body['description'] = description
try:
with self._session_lock:
response = self._session.post(urljoin(self._base_url, '/v1/prompts/'), json=body, timeout=self._timeout)
if response.status_code == 409:
raise VariableAlreadyExistsError(f"Prompt '{slug}' already exists")
UnexpectedResponse.raise_for_status(response)
if template is not None:
response = self._session.post(
urljoin(self._base_url, f'/v1/prompts/{slug}/versions/'),
json={'template': template},
timeout=self._timeout,
)
UnexpectedResponse.raise_for_status(response)
if template_inputs_schema is not None:
response = self._session.put(
urljoin(self._base_url, f'/v1/variables/{prompt_variable_name(slug)}/'),
json={'template_inputs_schema': template_inputs_schema},
timeout=self._timeout,
)
UnexpectedResponse.raise_for_status(response)
except (UnexpectedResponse, RequestException) as e:
raise VariableWriteError(f'Failed to create prompt: {e}') from e

# Refresh cache after successful write
self.refresh(force=True)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🌐 Web query:

Does the Logfire prompts API support creating a prompt with an initial version and template_inputs_schema in a single request?

💡 Result:

Yes, the Pydantic Logfire system supports defining the template_inputs_schema and managing prompt versions through the integration of Managed Variables and the Logfire SDK [1][2]. In Logfire, prompts are fundamentally backed by managed variables [3][4]. When you define a template variable in your application code using logfire.template_var, you provide an inputs_type (a Pydantic model) that describes the expected inputs [1][5]. This definition automatically generates the template_inputs_schema [1][2]. When you push this variable to the Logfire backend using logfire.variables_push or logfire.variables_push_config, the system syncs both the variable's configuration and its generated template_inputs_schema [1][2]. Consequently, while the API interactions are managed through variable operations rather than a separate "Prompt API" endpoint that takes both as raw input parameters in a single call, the workflow achieves this unification: you define the schema and initial state in your code, and the SDK handles the synchronization of both into the Logfire environment in a single conceptual operation [1][5][2].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '--- remote.py around create_prompt ---\n'
sed -n '500,620p' logfire/variables/remote.py

printf '\n--- search for create_prompt usages/tests ---\n'
rg -n "create_prompt\\(|VariableAlreadyExistsError|Failed to create prompt|template_inputs_schema|prompt_variable_name\\(" logfire tests

printf '\n--- push_variables implementation ---\n'
sed -n '1,260p' logfire/variables/abstract.py

Repository: pydantic/logfire

Length of output: 245


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '--- file list for variables area ---\n'
git ls-files logfire/variables tests | sed -n '1,200p'

printf '\n--- prompt-management docs references in repo ---\n'
rg -n "prompt(s)? API|template_inputs_schema|prompt version|managed variable|variables_push_config|variables_push" docs logfire tests README* pyproject.toml

Repository: pydantic/logfire

Length of output: 245


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- remote.py around create_prompt ---'
sed -n '500,620p' logfire/variables/remote.py

echo
echo '--- search for create_prompt usages/tests ---'
rg -n "create_prompt\\(|VariableAlreadyExistsError|Failed to create prompt|template_inputs_schema|prompt_variable_name\\(" logfire tests

echo
echo '--- push_variables implementation ---'
sed -n '1,260p' logfire/variables/abstract.py

Repository: pydantic/logfire

Length of output: 29093


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- file list for variables area ---'
git ls-files logfire/variables tests | sed -n '1,200p'

echo
echo '--- prompt-management docs references in repo ---'
rg -n "prompt(s)? API|template_inputs_schema|prompt version|managed variable|variables_push_config|variables_push" docs logfire tests README* pyproject.toml

Repository: pydantic/logfire

Length of output: 35261


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- abstract.py prompt push path ---'
sed -n '1670,1725p' logfire/variables/abstract.py

echo
echo '--- local.py create_prompt ---'
sed -n '120,210p' logfire/variables/local.py

echo
echo '--- prompt tests around create_prompt and failures ---'
sed -n '200,420p' tests/test_prompts.py

Repository: pydantic/logfire

Length of output: 15001


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- prompt diff / missing prompt handling ---'
sed -n '1540,1715p' logfire/variables/abstract.py

echo
echo '--- search for prompt_exists / already exist handling in push path ---'
rg -n "missing_prompts|already exist and are left untouched|prompt(s)? already exist|create_prompt\\(" logfire/variables/abstract.py tests/test_push_variables.py tests/test_prompts.py

echo
echo '--- tests for retry/resume semantics around prompts ---'
sed -n '420,520p' tests/test_prompts.py
sed -n '1240,1295p' tests/test_push_variables.py

Repository: pydantic/logfire

Length of output: 12942


Make prompt creation resumable or atomic

If the version or schema write fails after the initial prompt POST succeeds, the next push_variables() run treats that slug as existing and leaves it incomplete. Consider an atomic create-with-version endpoint or a recovery path that can finish a partially created prompt instead of skipping it.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@logfire/variables/remote.py` around lines 522 - 577, The create_prompt flow
can leave a partially created prompt behind if the version or schema write fails
after the initial POST succeeds, causing later push_variables() runs to skip
that slug as already existing. Update create_prompt to either use an atomic
create-with-version path if available or add recovery logic that detects an
incomplete prompt and finishes publishing the template and
template_inputs_schema on retry. Keep the behavior centered around
create_prompt, VariableAlreadyExistsError, and the prompt/version/schema API
calls so the slug can be resumed instead of abandoned.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

4 issues found across 16 files

Confidence score: 2/5

  • In logfire/__init__.py, the logfire-api shim is missing Logfire.prompt / Logfire.template_prompt and the module-level aliases, which can break existing integrations that rely on those public entry points; add the missing methods and DEFAULT_LOGFIRE_INSTANCE aliases before merging.
  • In logfire/variables/remote.py, prompt creation is non-atomic, so a failed version/schema step can leave a prompt partially created and cause later push_variables() runs to skip needed setup; make the create/version/schema flow transactional (or add rollback/retry/idempotent recovery) before merging.
  • The docs in docs/reference/advanced/prompt-management/application.md and docs/reference/advanced/prompt-management/templates.md currently overstate behavior (default publishing and “raw template” wording), which can mislead users even if runtime code works; align the wording with current implementation in this PR or in a tightly-following docs patch.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="logfire/__init__.py">

<violation number="1" location="logfire/__init__.py:105">
P1: `logfire-api` shim is missing `prompt` and `template_prompt` methods on the `Logfire` class, plus the module-level `prompt` / `template_prompt` aliases from `DEFAULT_LOGFIRE_INSTANCE`</violation>
</file>

<file name="logfire/variables/remote.py">

<violation number="1" location="logfire/variables/remote.py:558">
P2: Prompt creation is not atomic: if the version POST or schema PUT fails after the initial prompt POST succeeds, subsequent `push_variables()` runs will see the prompt as already existing (since it's now in the server config) and skip it — leaving it permanently without its v1 version or `template_inputs_schema`. Consider either catching partial failures and attempting cleanup/retry, or documenting that the user must manually complete the prompt on the Prompts page if a push partially fails.</violation>
</file>

<file name="docs/reference/advanced/prompt-management/templates.md">

<violation number="1" location="docs/reference/advanced/prompt-management/templates.md:14">
P3: `logfire.prompt()` still expands `@{...}@` composition references during `.get()`, so calling its output the "raw template" is misleading — it suggests composition refs remain unexpanded. Consider "post-composition template" or simply "the template" to accurately describe what `.get()` returns.</violation>
</file>

<file name="docs/reference/advanced/prompt-management/application.md">

<violation number="1" location="docs/reference/advanced/prompt-management/application.md:91">
P3: This paragraph implies any code `default` is published as version 1, but the implementation only publishes static-string defaults — callable/function defaults create the prompt without an initial version. Consider clarifying that only static-string defaults are published as v1 to avoid confusion when users define dynamic defaults.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread logfire/__init__.py
# Variables
var = DEFAULT_LOGFIRE_INSTANCE.var
template_var = DEFAULT_LOGFIRE_INSTANCE.template_var
prompt = DEFAULT_LOGFIRE_INSTANCE.prompt

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: logfire-api shim is missing prompt and template_prompt methods on the Logfire class, plus the module-level prompt / template_prompt aliases from DEFAULT_LOGFIRE_INSTANCE

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At logfire/__init__.py, line 105:

<comment>`logfire-api` shim is missing `prompt` and `template_prompt` methods on the `Logfire` class, plus the module-level `prompt` / `template_prompt` aliases from `DEFAULT_LOGFIRE_INSTANCE`</comment>

<file context>
@@ -102,6 +102,8 @@
 # Variables
 var = DEFAULT_LOGFIRE_INSTANCE.var
 template_var = DEFAULT_LOGFIRE_INSTANCE.template_var
+prompt = DEFAULT_LOGFIRE_INSTANCE.prompt
+template_prompt = DEFAULT_LOGFIRE_INSTANCE.template_prompt
 variables_clear = DEFAULT_LOGFIRE_INSTANCE.variables_clear
</file context>

if response.status_code == 409:
raise VariableAlreadyExistsError(f"Prompt '{slug}' already exists")
UnexpectedResponse.raise_for_status(response)
if template is not None:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Prompt creation is not atomic: if the version POST or schema PUT fails after the initial prompt POST succeeds, subsequent push_variables() runs will see the prompt as already existing (since it's now in the server config) and skip it — leaving it permanently without its v1 version or template_inputs_schema. Consider either catching partial failures and attempting cleanup/retry, or documenting that the user must manually complete the prompt on the Prompts page if a push partially fails.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At logfire/variables/remote.py, line 558:

<comment>Prompt creation is not atomic: if the version POST or schema PUT fails after the initial prompt POST succeeds, subsequent `push_variables()` runs will see the prompt as already existing (since it's now in the server config) and skip it — leaving it permanently without its v1 version or `template_inputs_schema`. Consider either catching partial failures and attempting cleanup/retry, or documenting that the user must manually complete the prompt on the Prompts page if a push partially fails.</comment>

<file context>
@@ -518,6 +519,62 @@ def update_variable(self, name: str, config: VariableConfig) -> VariableConfig:
+                if response.status_code == 409:
+                    raise VariableAlreadyExistsError(f"Prompt '{slug}' already exists")
+                UnexpectedResponse.raise_for_status(response)
+                if template is not None:
+                    response = self._session.post(
+                        urljoin(self._base_url, f'/v1/prompts/{slug}/versions/'),
</file context>

`logfire.template_prompt()` to substitute runtime variables before sending the
rendered prompt to your model client. If you fetch the raw template with
`logfire.var()`, your application must render the remaining `{{...}}`
`logfire.prompt()`, your application must render the remaining `{{...}}`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: logfire.prompt() still expands @{...}@ composition references during .get(), so calling its output the "raw template" is misleading — it suggests composition refs remain unexpanded. Consider "post-composition template" or simply "the template" to accurately describe what .get() returns.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At docs/reference/advanced/prompt-management/templates.md, line 14:

<comment>`logfire.prompt()` still expands `@{...}@` composition references during `.get()`, so calling its output the "raw template" is misleading — it suggests composition refs remain unexpanded. Consider "post-composition template" or simply "the template" to accurately describe what `.get()` returns.</comment>

<file context>
@@ -9,9 +9,9 @@ A prompt template is a string. At render time, Logfire walks that string with a
+    `logfire.template_prompt()` to substitute runtime variables before sending the
     rendered prompt to your model client. If you fetch the raw template with
-    `logfire.var()`, your application must render the remaining `{{...}}`
+    `logfire.prompt()`, your application must render the remaining `{{...}}`
     placeholders itself. See [Use Prompts in Your Application](./application.md).
 
</file context>

If a prompt only exists in your code so far, you don't have to recreate it by
hand in the UI. `logfire.variables_push()` pushes everything declared in code —
managed variables *and* prompts. For each prompt that doesn't exist in the
project yet, it creates the prompt and publishes your code `default` as version

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: This paragraph implies any code default is published as version 1, but the implementation only publishes static-string defaults — callable/function defaults create the prompt without an initial version. Consider clarifying that only static-string defaults are published as v1 to avoid confusion when users define dynamic defaults.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At docs/reference/advanced/prompt-management/application.md, line 91:

<comment>This paragraph implies any code `default` is published as version 1, but the implementation only publishes static-string defaults — callable/function defaults create the prompt without an initial version. Consider clarifying that only static-string defaults are published as v1 to avoid confusion when users define dynamic defaults.</comment>

<file context>
@@ -79,10 +83,39 @@ those references during `.get()`. See the
+If a prompt only exists in your code so far, you don't have to recreate it by
+hand in the UI. `logfire.variables_push()` pushes everything declared in code —
+managed variables *and* prompts. For each prompt that doesn't exist in the
+project yet, it creates the prompt and publishes your code `default` as version
+1, so it appears on the Prompts page ready for iteration and serves exactly what
+your code default already said. Prompts that already exist are never modified by
</file context>
Suggested change
project yet, it creates the prompt and publishes your code `default` as version
project yet, it creates the prompt and publishes your static-string code `default` as version

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.

2 participants