Managed prompts: prompt()/template_prompt() consumption + prompts in variables_push()#2030
Managed prompts: prompt()/template_prompt() consumption + prompts in variables_push()#2030dmontagu wants to merge 4 commits into
Conversation
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
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
📝 WalkthroughWalkthroughThis PR introduces public Changes
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)
Related Issues: None referenced. Related PRs: None referenced. Suggested labels: enhancement, documentation, prompts, variables Suggested reviewers: None determinable from provided summaries. Poem 🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
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
📒 Files selected for processing (16)
docs/reference/advanced/prompt-management/application.mddocs/reference/advanced/prompt-management/composition-walkthrough.mddocs/reference/advanced/prompt-management/promotion-and-rollouts.mddocs/reference/advanced/prompt-management/template-reference.mddocs/reference/advanced/prompt-management/templates.mddocs/reference/advanced/prompt-management/ui.mdlogfire/__init__.pylogfire/_internal/main.pylogfire/variables/__init__.pylogfire/variables/_prompt.pylogfire/variables/abstract.pylogfire/variables/local.pylogfire/variables/remote.pytests/test_logfire_api.pytests/test_prompts.pytests/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)
| 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. |
There was a problem hiding this comment.
🎯 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.
| 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.
| `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). |
There was a problem hiding this comment.
🎯 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.
| `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.
| 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}') |
There was a problem hiding this comment.
🎯 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.
| 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.
| 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) | ||
|
|
There was a problem hiding this comment.
🗄️ 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:
- 1: https://pydantic.dev/docs/logfire/manage/managed-variables/templates-and-composition/
- 2: Composition and templating with native handlebars #1954
- 3: https://pydantic.dev/docs/logfire/prompt-management/application/
- 4: https://pydantic.dev/docs/logfire/prompt-management/concepts/
- 5: https://pydantic.dev/docs/logfire/manage/managed-variables/
🏁 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.pyRepository: 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.tomlRepository: 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.pyRepository: 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.tomlRepository: 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.pyRepository: 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.pyRepository: 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.
There was a problem hiding this comment.
4 issues found across 16 files
Confidence score: 2/5
- In
logfire/__init__.py, thelogfire-apishim is missingLogfire.prompt/Logfire.template_promptand the module-level aliases, which can break existing integrations that rely on those public entry points; add the missing methods andDEFAULT_LOGFIRE_INSTANCEaliases 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 laterpush_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.mdanddocs/reference/advanced/prompt-management/templates.mdcurrently 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
| # Variables | ||
| var = DEFAULT_LOGFIRE_INSTANCE.var | ||
| template_var = DEFAULT_LOGFIRE_INSTANCE.template_var | ||
| prompt = DEFAULT_LOGFIRE_INSTANCE.prompt |
There was a problem hiding this comment.
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: |
There was a problem hiding this comment.
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 `{{...}}` |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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>
| 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 |
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:prompt()isvar()specialized to prompts: value type fixed tostr, slug translated to the backing-variable name. ReturnsVariable[str].template_prompt()is the same specialization oftemplate_var()(addsinputs_type+template_mismatch_policy). ReturnsTemplateVariable[str, InputsT].var()/template_var()infrastructure.Naming / validation
logfire/variables/_prompt.pyholds the single slug↔name translation (prompt_variable_name()and its inverseprompt_slug_from_variable_name()). Slugs are validated against the backend's exact rule (^[a-z0-9-]{1,100}$) and raiseValueErrorloudly 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 accidentalprompt__prefix warns and strips.Authoring:
variables_push()now creates promptslogfire.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/, requiresproject:write_variables):defaultas version 1 — the prompt appears on the Prompts page ready for iteration and serves exactly what the code default already said (fresh prompts servelatest). Function defaults create the prompt with no initial version.variables_push's "never touch values" stance.template_prompt()declarations also push theirtemplate_inputs_schema(the one prompt field the variables API accepts for prompt rows).Docs
application.mdgains a prominent "API Key Required" callout: prompts resolve through the managed-variables API, which needsLOGFIRE_API_KEY(scopeproject: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.Scope boundaries
logfire.prompts_*()management family (get/list/update/settings/labels) from the design doc is still pending; MCP already covers interactive authoring.logfire-apishim excludes these names (like the whole variables family); prompt users needlogfire[variables].ManagedPromptintegration (delegating its naming toprompt_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 throughLocalVariableProvider, which implements the samecreate_promptprimitive as the remote provider./v1/prompts/branch (pydantic/platform#23832): push creates the prompt via the API, the Prompts page shows it, andprompt().get()resolves the pushed version.pyright,ruff, docs-example tests all green.