-
Notifications
You must be signed in to change notification settings - Fork 42
Guide prompt api #851
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Guide prompt api #851
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,270 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| --- | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| title: "Automated Prompt Replication" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| description: "Bulk-copy Portkey prompts and point them at a new model using the Admin API—no manual duplication in the UI." | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| --- | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Use this workflow when many prompts target one model (for example Claude 3.7) and you want **replicas** that keep the same template and settings but run on another model (for example Claude 3.5 Sonnet on Bedrock). The [List prompts](/api-reference/admin-api/control-plane/prompts/list-prompts), [Retrieve prompt](/api-reference/admin-api/control-plane/prompts/retrieve-prompt), and [Create prompt](/api-reference/admin-api/control-plane/prompts/create-prompt) endpoints drive the migration. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Info> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| **Auth:** Use an [Admin API key](/api-reference/admin-api/introduction) or a **Workspace API key** with prompt permissions. Send `x-portkey-api-key` on every request. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </Info> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ## When to use this | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - Migrate dozens of prompts to a new default model after a provider or catalog change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - Keep originals untouched by creating **named replicas** (for example append `-replica`) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - Automate what would otherwise be repeated copy-paste in Prompt Studio | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ## How it works | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 1. **List** all prompts and collect their IDs. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 2. **Retrieve** each prompt’s full definition (template `string`, `parameters`, `virtual_key`, metadata, etc.). | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 3. **Create** a new prompt per ID with the same body fields and a **new `model`** value. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Replace the example model string (`anthropic.claude-3-5-sonnet`) with the exact model identifier your workspace uses. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| --- | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ## Step 1: List prompts and collect IDs | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Tabs> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Tab title="Python"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ```python | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import requests | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| BASE = "https://api.portkey.ai/v1" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| headers = {"x-portkey-api-key": "YOUR_API_KEY"} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| r = requests.get(f"{BASE}/prompts", headers=headers) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| r.raise_for_status() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| prompt_ids = [item["id"] for item in r.json()["data"]] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| print(prompt_ids) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ``` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </Tab> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Tab title="Node.js"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ```javascript | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| async function main() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const BASE = 'https://api.portkey.ai/v1'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const headers = { 'x-portkey-api-key': process.env.PORTKEY_API_KEY ?? 'YOUR_API_KEY' }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const res = await fetch(`${BASE}/prompts`, { headers }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!res.ok) throw new Error(`List prompts failed: ${res.status}`); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { data } = await res.json(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const promptIds = data.map((row) => row.id); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.log(promptIds); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| main().catch(console.error); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ``` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </Tab> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </Tabs> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Note> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| **Python (steps 2–3):** Run the snippets in order in the same session so `BASE`, `headers`, and `prompt_ids` / `prompt_data` stay in scope. **Node.js:** Examples use native `fetch` (Node.js 18+). | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </Note> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| --- | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ## Step 2: Fetch one prompt’s full configuration | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Use this shape to see which fields the API returns before building the create payload (names vary slightly by version; always log once and adjust keys if needed). | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Tabs> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Tab title="Python"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ```python | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| prompt_id = prompt_ids[0] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| url = f"{BASE}/prompts/{prompt_id}" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| prompt_data = requests.get(url, headers=headers).json() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| print(prompt_data) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ``` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </Tab> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <Tab title="Node.js"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ```javascript | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| async function main() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const BASE = 'https://api.portkey.ai/v1'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const headers = { 'x-portkey-api-key': process.env.PORTKEY_API_KEY ?? 'YOUR_API_KEY' }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const listRes = await fetch(`${BASE}/prompts`, { headers }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { data } = await listRes.json(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const promptId = data[0].id; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const promptData = await fetch(`${BASE}/prompts/${promptId}`, { headers }).then((r) => | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| r.json() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+90
to
+95
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { data } = await listRes.json(); | |
| const promptId = data[0].id; | |
| const promptData = await fetch(`${BASE}/prompts/${promptId}`, { headers }).then((r) => | |
| r.json() | |
| ); | |
| if (!listRes.ok) { | |
| const body = await listRes.text(); | |
| throw new Error(`List prompts failed: ${listRes.status} ${body}`); | |
| } | |
| const { data } = await listRes.json(); | |
| const promptId = data[0].id; | |
| const promptRes = await fetch(`${BASE}/prompts/${promptId}`, { headers }); | |
| if (!promptRes.ok) { | |
| const body = await promptRes.text(); | |
| throw new Error(`Retrieve prompt failed: ${promptRes.status} ${body}`); | |
| } | |
| const promptData = await promptRes.json(); |
Copilot
AI
Apr 8, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the Node.js Step 2 snippet, the retrieve call parses JSON without validating r.ok. If the prompt fetch fails, the example will still log an error payload as if it were a prompt. Consider checking ok and throwing a descriptive error before r.json().
| const { data } = await listRes.json(); | |
| const promptId = data[0].id; | |
| const promptData = await fetch(`${BASE}/prompts/${promptId}`, { headers }).then((r) => | |
| r.json() | |
| ); | |
| if (!listRes.ok) throw new Error(`List prompts failed: ${listRes.status}`); | |
| const { data } = await listRes.json(); | |
| const promptId = data[0].id; | |
| const promptRes = await fetch(`${BASE}/prompts/${promptId}`, { headers }); | |
| if (!promptRes.ok) throw new Error(`Retrieve prompt failed: ${promptRes.status}`); | |
| const promptData = await promptRes.json(); |
Copilot
AI
Apr 8, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the Node.js Step 3 snippet, the list/retrieve calls (listRes, prompt fetch) are not checked for ok before reading .json(), which can lead to confusing failures when auth/pagination errors occur. Align this snippet with the Step 1 pattern by validating ok for each fetch before using the response body.
| const { data } = await listRes.json(); | |
| const promptData = await fetch(`${BASE}/prompts/${data[0].id}`, { headers }).then((r) => | |
| r.json() | |
| ); | |
| if (!listRes.ok) { | |
| throw new Error(`List prompts failed: ${listRes.status} ${await listRes.text()}`); | |
| } | |
| const { data } = await listRes.json(); | |
| const promptRes = await fetch(`${BASE}/prompts/${data[0].id}`, { headers }); | |
| if (!promptRes.ok) { | |
| throw new Error(`Retrieve prompt failed: ${promptRes.status} ${await promptRes.text()}`); | |
| } | |
| const promptData = await promptRes.json(); |
Copilot
AI
Apr 8, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the Node.js Step 3 snippet, the create request parses JSON without checking r.ok, so errors can be logged as if creation succeeded. Consider checking ok and including the response body in the thrown error before logging the created prompt.
| const created = await fetch(`${BASE}/prompts`, { | |
| method: 'POST', | |
| headers: { ...headers, 'Content-Type': 'application/json' }, | |
| body: JSON.stringify(payload), | |
| }).then((r) => r.json()); | |
| const createRes = await fetch(`${BASE}/prompts`, { | |
| method: 'POST', | |
| headers: { ...headers, 'Content-Type': 'application/json' }, | |
| body: JSON.stringify(payload), | |
| }); | |
| const created = await createRes.json(); | |
| if (!createRes.ok) { | |
| throw new Error( | |
| `Failed to create prompt (${createRes.status}): ${JSON.stringify(created)}` | |
| ); | |
| } |
Copilot
AI
Apr 8, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the full-loop Python example, each prompt retrieve uses .json() directly without raise_for_status(). If any prompt ID is invalid or permissions differ across collections, the loop may fail later with unclear errors. Consider calling raise_for_status() on the retrieve response before building the payload.
| data = requests.get(f"{BASE}/prompts/{prompt_id}", headers=headers).json() | |
| retrieve_res = requests.get(f"{BASE}/prompts/{prompt_id}", headers=headers) | |
| retrieve_res.raise_for_status() | |
| data = retrieve_res.json() |
Copilot
AI
Apr 8, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the full-loop Node.js example, the per-prompt retrieve call parses JSON without checking r.ok. If any prompt fetch fails mid-loop, the code may attempt to create a replica from an error payload. Consider checking ok (and surfacing status/body) before using the retrieved data.
| const data = await fetch(`${BASE}/prompts/${promptId}`, { headers }).then((r) => | |
| r.json() | |
| ); | |
| const retrieveRes = await fetch(`${BASE}/prompts/${promptId}`, { headers }); | |
| if (!retrieveRes.ok) { | |
| const body = await retrieveRes.text(); | |
| throw new Error(`Retrieve failed for ${promptId}: ${retrieveRes.status} ${body}`); | |
| } | |
| const data = await retrieveRes.json(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the Python Step 2 snippet, the GET response is parsed with
.json()without checking for HTTP errors first. If the request fails (401/404/etc.), users may get a confusing JSON parse / KeyError later. Consider callingraise_for_status()(and optionally using a short timeout) before reading.json()to make failures explicit.