Skip to content

feat(prompts): Fetch prompts by label#71509

Merged
jurajmajerik merged 7 commits into
masterfrom
prompts/labels-read-path
Jul 17, 2026
Merged

feat(prompts): Fetch prompts by label#71509
jurajmajerik merged 7 commits into
masterfrom
prompts/labels-read-path

Conversation

@jurajmajerik

@jurajmajerik jurajmajerik commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Problem

#70862 added prompt labels (movable pointers to versions) and the endpoints to manage them, but there is no way to fetch a prompt through a label yet. Until that exists, moving a label has no effect on the apps that consume prompts.

Changes

Adds ?label=production to the prompt fetch endpoint. The response is the version the label points to, plus a label field showing what was resolved. Stacked on #70862.

  • ?label= on GET .../llm_prompts/name/{name}/, mutually exclusive with ?version= (400 if both are passed)
  • Label results get their own HyperCache entry ({team}:{name}:label:{label}). We cache the resolved prompt content, so a warm fetch is two Redis reads (the latest entry for metadata, then the label entry) with no Postgres lookup
  • Signals on LLMPromptLabel clear that cache entry whenever a label is created, moved, or deleted
  • New prompt_label property on the $llm_prompt_fetched event
  • The get-prompt MCP tool description now explains the label parameter
  • Fetching without a label or version still returns the latest version, same as before

Design notes:

  • Why signals for cache invalidation: the existing invalidation runs when a prompt version row is saved, and a label move doesn't save any version row — without its own trigger, a moved label would serve the old version until the cache expires (up to a day). A signal on the label model covers every path that changes labels (set, delete, archive's cleanup) in one place.
  • Creating a label also needs invalidation: fetching a missing label caches a short-lived "not found", and the create signal clears it so a new label works right away.

How did you test this code?

New tests in test_llm_prompt.py, all through the public API with the cache in the loop: fetching by label after a move returns the new version (this test fails if the invalidation breaks, and it caught a real gap during development), a label created after a cached 404 works immediately, label+version together returns 400, and fetches 404 after label delete and after prompt archive. Full prompt API + cache suites pass locally (119 tests), mypy clean on changed files.

@github-actions

github-actions Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

🦔 Hogbox preview · ✅ ready

▶ Open the preview

🔑 Login test@posthog.com / 12345678 (demo data)
🧩 Running this PR's backend and frontend, on the PostHog :master base
🔗 Link stable across rebuilds — a re-push swaps the box underneath, the URL stays
🔒 Access tailnet only (PostHog VPN)
🛠️ Admin inspect & debug state in hogland
💤 Idle sleeps after ~30 min idle (snapshot to S3, zero node cost) and wakes on your next visit in ~30s, behind a brief "waking up" screen

commit f519d65 · box box-6fe3e5ff1039 · ready in 777s (push → usable) · build log · rebuilds on every push, torn down on close

@assign-reviewers-posthog
assign-reviewers-posthog Bot requested review from a team July 16, 2026 11:59
@github-actions

github-actions Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

🤖 CI report

⚠️ Bundle size — 🔺 +4.8 KiB (+0.0%)

Uncompressed size of every built .js bundle, compared against the base branch.

Total: 64.80 MiB · 🔺 +4.8 KiB (+0.0%)

File Size Δ vs base
posthog-app/src/scenes/hog-functions/HogFunctionScene.js 47.2 KiB 🟢 -14.9 KiB (-24.0%)
posthog-app/_parent/products/tracing/frontend/TracingScene.js 84.8 KiB 🟢 -13.4 KiB (-13.6%)
exporter/src/exporter/scenes/ExporterDashboardScene.js 275.4 KiB 🔺 +8.3 KiB (+3.1%)
posthog-app/_parent/products/engineering_analytics/frontend/scenes/EngineeringAnalyticsTeamScene.js 8.2 KiB 🔺 +8.2 KiB (new)
render-query/src/render-query/render-query.js 21.32 MiB 🔺 +5.1 KiB (+0.0%)
posthog-app/_parent/products/engineering_analytics/frontend/scenes/EngineeringAnalyticsScene.js 70.2 KiB 🔺 +3.3 KiB (+5.0%)
posthog-app/_parent/products/review_hog/frontend/CodeReviewScene.js 51.3 KiB 🔺 +2.4 KiB (+4.9%)
exporter/src/exporter/scenes/ExporterNotebookScene.js 3.10 MiB 🟢 -1.4 KiB (-0.0%)

Posted automatically by build-bundle-size-report · uncompressed bytes from dist-report

Eager graph — within budget

How much code each root ships on the eager path — downloaded and parsed before the surface is interactive. Measured from the esbuild output chunks (post-tree-shake, static imports only); lazy import() / React.lazy chunks are not counted.

Root Eager (shipped) Δ vs base Budget
entry (logged-out pages, app bootstrap)
src/index.tsx
1.23 MiB · 22 files 🔺 +125 B (+0.0%) ███░░░░░░░ 28.6% of 4.29 MiB
authenticated shell (every logged-in page)
src/scenes/AuthenticatedShell.tsx
8.15 MiB · 2,987 files 🔺 +6.7 KiB (+0.1%) █████████░ 88.1% of 9.25 MiB

🟢 node_modules/monaco-editor/ stays out of src/index.tsx
🟢 src/lib/components/ActivityLog/describers stays out of src/index.tsx
🟢 [object Object] stays out of src/index.tsx
🟢 [object Object] stays out of src/index.tsx
🟢 node_modules/monaco-editor/ stays out of src/scenes/AuthenticatedShell.tsx
🟢 src/lib/components/ActivityLog/describers stays out of src/scenes/AuthenticatedShell.tsx
🟢 [object Object] stays out of src/scenes/AuthenticatedShell.tsx
🟢 [object Object] stays out of src/scenes/AuthenticatedShell.tsx

Largest files eagerly shipped from src/index.tsx
Size File
126.8 KiB ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js
24.6 KiB ../node_modules/.pnpm/buffer@6.0.3/node_modules/buffer/index.js
6.3 KiB ../node_modules/.pnpm/react@18.3.1/node_modules/react/cjs/react.production.min.js
4.5 KiB ../node_modules/.pnpm/@jspm+core@2.1.0/node_modules/@jspm/core/nodelibs/browser/process.js
3.9 KiB ../node_modules/.pnpm/scheduler@0.23.2/node_modules/scheduler/cjs/scheduler.production.min.js
1.4 KiB ../node_modules/.pnpm/base64-js@1.5.1/node_modules/base64-js/index.js
1.3 KiB src/RootErrorBoundary.tsx
912 B ../node_modules/.pnpm/ieee754@1.2.1/node_modules/ieee754/index.js
789 B src/scenes/ChunkLoadErrorBoundary.tsx
762 B src/index.tsx
Largest files eagerly shipped from src/scenes/AuthenticatedShell.tsx
Size File
281.3 KiB ../node_modules/.pnpm/posthog-js@1.404.0/node_modules/posthog-js/dist/rrweb.js
267.7 KiB ../node_modules/.pnpm/@posthog+icons@0.38.0_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/icons/dist/posthog-icons.es.js
235.5 KiB src/taxonomy/core-filter-definitions-by-group.json
223.2 KiB ../node_modules/.pnpm/posthog-js@1.404.0/node_modules/posthog-js/dist/module.js
164.0 KiB src/queries/validators.js
154.3 KiB ../node_modules/.pnpm/re2js@0.4.1/node_modules/re2js/build/index.esm.js
126.8 KiB ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js
105.8 KiB src/lib/api.ts
93.3 KiB ../node_modules/.pnpm/prosemirror-view@1.40.1/node_modules/prosemirror-view/dist/index.js
93.3 KiB ../packages/quill/packages/quill/dist/index.js

Posted automatically by check-eager-graph · sizes are eager output bytes (shipped, post-tree-shake) from the esbuild metafile · part of #32479

Dist folder size — 🔺 +10.86 MiB (+0.8%)

Total size of the built frontend/dist folder (all assets), compared against the base branch.

Total: 1333.77 MiB · 🔺 +10.86 MiB (+0.8%)

ℹ️ MCP UI apps size — 31 app(s), 16603.1 KB JS

Built size of each MCP UI app (main.js + styles.css).

App JS CSS
debug 599.5 KB 187.4 KB
action 457.8 KB 187.4 KB
action-list 564.3 KB 187.4 KB
cohort 456.8 KB 187.4 KB
cohort-list 563.3 KB 187.4 KB
email-template 456.6 KB 187.4 KB
error-details 472.4 KB 187.4 KB
error-issue 457.5 KB 187.4 KB
error-issue-list 564.2 KB 187.4 KB
experiment 561.5 KB 187.4 KB
experiment-list 565.1 KB 187.4 KB
experiment-results 563.2 KB 187.4 KB
feature-flag 567.1 KB 187.4 KB
feature-flag-list 570.9 KB 187.4 KB
feature-flag-testing 461.0 KB 187.4 KB
insight-actors 562.1 KB 187.4 KB
invite-email-preview 456.0 KB 187.4 KB
llm-costs 559.5 KB 187.4 KB
session-recording 458.6 KB 187.4 KB
session-summary 463.9 KB 187.4 KB
survey 458.4 KB 187.4 KB
survey-global-stats 562.2 KB 187.4 KB
survey-list 565.0 KB 187.4 KB
survey-stats 562.2 KB 187.4 KB
trace-span 457.2 KB 187.4 KB
trace-span-list 564.2 KB 187.4 KB
workflow 457.1 KB 187.4 KB
workflow-list 563.7 KB 187.4 KB
query-results 745.0 KB 187.4 KB
render-ui 825.6 KB 187.4 KB
visual-review-snapshots 461.6 KB 187.4 KB
⚠️ Playwright — 2 failed

🎭 Playwright report · View test results →

2 failed tests:

  • view results, pause, resume, and ship (chromium)
  • golden creation flow: click-to-create → filter → Enable anyway → flips to enabled (chromium)

These issues are not necessarily caused by your changes.
Annoyed by this section? Help fix flakies and failures and it will go green!

⚠️ Backend coverage — 81.0% of changed backend lines covered — 22 uncovered

🧪 Backend test coverage

Patch coverage — changed backend lines (products + core): ████████████████░░░░ 81.0% (94 / 116)

File Patch Uncovered changed lines
products/ai_observability/backend/models/llm_prompt.py 23.1% 172–174, 176–177, 179–182, 184
posthog/storage/llm_prompt_cache.py 67.6% 171–174, 176–183

🤖 Agents: add a test covering the lines above, or note why under "How did you test this code?". Machine-readable gap list: the patch-coverage artifact on this run (gh run download 29569480349 -n patch-coverage), or the coverage-data block at the end of this comment.

Per-product line coverage (touched products)
Product Coverage Lines
platform_features ██░░░░░░░░░░░░░░░░░░ 12.1% 7 / 58
batch_exports ████████░░░░░░░░░░░░ 39.7% 8,416 / 21,225
demo ███████████░░░░░░░░░ 56.2% 1,497 / 2,663
warehouse_sources_queue ████████████░░░░░░░░ 59.2% 148 / 250
tasks █████████████░░░░░░░ 67.1% 25,926 / 38,648
data_tools ██████████████░░░░░░ 70.0% 63 / 90
ai_gateway ███████████████░░░░░ 75.0% 9 / 12
signals ████████████████░░░░ 79.1% 19,037 / 24,073
data_modeling ████████████████░░░░ 80.0% 4,834 / 6,045
cdp ████████████████░░░░ 80.7% 3,118 / 3,864
wizard ████████████████░░░░ 82.5% 772 / 936
notebooks █████████████████░░░ 84.8% 6,903 / 8,141
cohorts █████████████████░░░ 86.2% 4,065 / 4,717
agent_platform █████████████████░░░ 86.4% 3,807 / 4,405
actions █████████████████░░░ 86.6% 717 / 828
product_tours █████████████████░░░ 87.5% 1,266 / 1,447
exports ██████████████████░░ 88.4% 6,904 / 7,813
visual_review ██████████████████░░ 88.5% 5,565 / 6,287
business_knowledge ██████████████████░░ 88.5% 4,400 / 4,969
conversations ██████████████████░░ 89.0% 16,164 / 18,167
mcp_analytics ██████████████████░░ 89.2% 2,514 / 2,819
dashboards ██████████████████░░ 89.3% 5,841 / 6,540
engineering_analytics ██████████████████░░ 89.3% 5,407 / 6,053
error_tracking ██████████████████░░ 89.6% 9,731 / 10,862
alerts ██████████████████░░ 90.0% 3,655 / 4,063
streamlit_apps ██████████████████░░ 90.4% 2,499 / 2,764
stamphog ██████████████████░░ 90.4% 3,503 / 3,874
slack_app ██████████████████░░ 90.6% 9,511 / 10,503
links ██████████████████░░ 90.6% 183 / 202
marketing_analytics ██████████████████░░ 90.8% 11,514 / 12,684
product_analytics ██████████████████░░ 91.3% 5,757 / 6,304
mcp_store ██████████████████░░ 91.8% 3,685 / 4,012
managed_migrations ██████████████████░░ 91.9% 908 / 988
data_warehouse ██████████████████░░ 92.1% 18,220 / 19,780
notifications ███████████████████░ 92.7% 1,031 / 1,112
web_analytics ███████████████████░ 92.7% 13,535 / 14,597
workflows ███████████████████░ 92.8% 5,477 / 5,904
ai_observability ███████████████████░ 92.8% 14,916 / 16,077
surveys ███████████████████░ 92.9% 5,689 / 6,122
posthog_ai ███████████████████░ 93.2% 1,322 / 1,418
tracing ███████████████████░ 93.3% 2,447 / 2,624
approvals ███████████████████░ 93.3% 3,395 / 3,640
reminders ███████████████████░ 93.4% 468 / 501
early_access_features ███████████████████░ 93.8% 848 / 904
legal_documents ███████████████████░ 94.1% 1,568 / 1,667
endpoints ███████████████████░ 94.1% 8,606 / 9,143
revenue_analytics ███████████████████░ 94.5% 3,598 / 3,809
skills ███████████████████░ 94.5% 2,881 / 3,049
messaging ███████████████████░ 94.5% 2,530 / 2,677
review_hog ███████████████████░ 94.6% 6,788 / 7,173
logs ███████████████████░ 95.3% 9,572 / 10,044
experiments ███████████████████░ 95.7% 24,421 / 25,531
replay_vision ███████████████████░ 95.7% 13,483 / 14,084
growth ███████████████████░ 95.8% 2,837 / 2,960
annotations ███████████████████░ 96.2% 732 / 761
feature_flags ███████████████████░ 96.3% 16,172 / 16,794
warehouse_sources ███████████████████░ 96.4% 262,618 / 272,387
user_interviews ███████████████████░ 96.4% 2,242 / 2,325
access_control ███████████████████░ 96.8% 849 / 877
data_catalog ███████████████████░ 97.2% 2,117 / 2,179
customer_analytics ███████████████████░ 97.2% 7,480 / 7,698
analytics_platform ████████████████████ 98.0% 2,102 / 2,145
metrics ████████████████████ 98.2% 2,404 / 2,448
pulse ████████████████████ 98.4% 2,012 / 2,044
live_debugger ████████████████████ 99.2% 613 / 618
field_notes ████████████████████ 99.4% 158 / 159

Report-only. Patch coverage = changed backend lines covered vs origin/master. Sorted lowest first.
Known gaps: lines covered only by Temporal tests show as uncovered; core line numbers may drift if master changed the same file.

⚠️ MCP snapshots — 1 updated (1 modified, 0 added, 0 deleted)

Snapshots: MCP unit test snapshots updated

Changes: 1 snapshots (1 modified, 0 added, 0 deleted)

What this means:

  • Snapshots have been automatically updated to match current output

Next steps:

  • Review the changes to ensure they're intentional
  • If unexpected, investigate what caused the output to change

Review snapshot changes →

Django migration risk — no migrations to analyze

No Django migrations need risk analysis.

@greptile-apps

greptile-apps Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Reviews (1): Last reviewed commit: "feat(aio): fetch prompts by label" | Re-trigger Greptile

Comment thread posthog/storage/llm_prompt_cache.py Outdated
@trunk-io

trunk-io Bot commented Jul 16, 2026

Copy link
Copy Markdown

Static BadgeStatic BadgeStatic BadgeStatic Badge

View Full Report ↗︎Docs

@github-actions
github-actions Bot requested a deployment to preview-pr-71509 July 16, 2026 12:17 In progress
@github-actions
github-actions Bot requested a deployment to preview-pr-71509 July 16, 2026 12:24 In progress
@github-actions
github-actions Bot requested a deployment to preview-pr-71509 July 16, 2026 12:25 In progress
Base automatically changed from prompts/labels-write-path to master July 16, 2026 12:41
@jurajmajerik jurajmajerik changed the title feat(aio): Fetch prompts by label feat(prompts): Fetch prompts by label Jul 16, 2026
jurajmajerik and others added 5 commits July 16, 2026 15:19
Adds ?label= to the prompt fetch endpoint, resolving through HyperCache
with a per-label cache entry. Label moves, creates, and deletes clear
the entry via model signals — a pointer change never touches LLMPrompt
rows, so the existing invalidation could not cover it. The fetch event
gains prompt_label so 'what was production serving at time X' is
answerable from the event stream.
A cache fill racing a label-move invalidation can store an already-stale
resolution the generation marker cannot detect. Label entries now use a
60s TTL (own HyperCache instance over the same keys) and stay out of S3,
so any stale fill self-heals within a minute; signals remain the fast path.
Fetching writes miss sentinels under caller-controlled cache keys, so
impossible label names are rejected before any cache touch. Also makes
the label 404 message cover the missing-prompt case.

if label is not None:
label_key = prompt_label_cache_key(team.id, prompt_name, label)
labeled_prompt = llm_prompts_label_hypercache.get_from_cache(label_key)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should we make this fetch skip the object storage?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yep, will do in a follow up!



class LLMPromptGetByNameQuerySerializer(LLMPromptFetchQuerySerializer):
label = serializers.CharField( # type: ignore[assignment]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We should make this aware of the constraints from validate_prompt_label_name_value

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Added: ec2d816

@jurajmajerik
jurajmajerik enabled auto-merge (squash) July 17, 2026 09:15
@tests-posthog
tests-posthog Bot disabled auto-merge July 17, 2026 09:18
@jurajmajerik
jurajmajerik enabled auto-merge (squash) July 17, 2026 09:25
@jurajmajerik
jurajmajerik merged commit cb7a53a into master Jul 17, 2026
257 checks passed
@jurajmajerik
jurajmajerik deleted the prompts/labels-read-path branch July 17, 2026 09:54
@deployment-status-posthog

deployment-status-posthog Bot commented Jul 17, 2026

Copy link
Copy Markdown

Deploy status

Environment Status Deployed At Workflow
dev ✅ Deployed 2026-07-17 10:25 UTC Run
prod-us ✅ Deployed 2026-07-17 10:38 UTC Run
prod-eu ✅ Deployed 2026-07-17 10:41 UTC Run

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