diff --git a/integration-test/langfuse-integration-node.spec.ts b/integration-test/langfuse-integration-node.spec.ts index f8103931..d65c0597 100644 --- a/integration-test/langfuse-integration-node.spec.ts +++ b/integration-test/langfuse-integration-node.spec.ts @@ -432,6 +432,26 @@ describe("Langfuse Node.js", () => { version: expect.any(Number), }); }); + + it("update a prompt", async () => { + const promptName = "test-prompt" + Math.random().toString(36); + await langfuse.createPrompt({ + name: promptName, + prompt: "This is a prompt with a {{variable}}", + isActive: true, + labels: ["john"], + }); + + const updatedPrompt = await langfuse.updatePrompt({ + name: promptName, + version: 1, + newLabels: ["john", "doe"], + }); + + const prompt = await langfuse.getPrompt(promptName); + expect(prompt.labels).toEqual(expect.arrayContaining(["john", "doe"])); + expect(updatedPrompt.labels).toEqual(expect.arrayContaining(["john", "doe"])); + }); }); it("link prompt to generation", async () => { diff --git a/langfuse-core/openapi-spec/openapi-server.yaml b/langfuse-core/openapi-spec/openapi-server.yaml index 9e40794a..bb6c7fad 100644 --- a/langfuse-core/openapi-spec/openapi-server.yaml +++ b/langfuse-core/openapi-spec/openapi-server.yaml @@ -723,6 +723,9 @@ paths: Notes: + - Introduction to data model: + https://langfuse.com/docs/tracing-data-model + - Batch sizes are limited to 3.5 MB in total. You need to adjust the number of events per batch accordingly. @@ -1423,6 +1426,76 @@ paths: schema: {} security: - BasicAuth: [] + /api/public/v2/prompts/{promptName}/version/{version}: + patch: + description: Update labels for a specific prompt version + operationId: promptVersion_update + tags: + - PromptVersion + parameters: + - name: promptName + in: path + description: The name of the prompt + required: true + schema: + type: string + - name: version + in: path + description: Version of the prompt to update + required: true + schema: + type: integer + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/Prompt' + '400': + description: '' + content: + application/json: + schema: {} + '401': + description: '' + content: + application/json: + schema: {} + '403': + description: '' + content: + application/json: + schema: {} + '404': + description: '' + content: + application/json: + schema: {} + '405': + description: '' + content: + application/json: + schema: {} + security: + - BasicAuth: [] + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + newLabels: + type: array + items: + type: string + description: >- + New labels for the prompt version. Labels are unique across + versions. The "latest" label is reserved and managed by + Langfuse. + required: + - newLabels /api/public/v2/prompts/{promptName}: get: description: Get a prompt @@ -3183,11 +3256,12 @@ components: to exact match, use `(?i)^modelname$` startDate: type: string - format: date + format: date-time nullable: true description: Apply only to generations which are newer than this ISO date. unit: $ref: '#/components/schemas/ModelUsageUnit' + nullable: true description: Unit used by this model. inputPrice: type: number @@ -3223,7 +3297,6 @@ components: - id - modelName - matchPattern - - unit - isLangfuseManaged ModelUsageUnit: title: ModelUsageUnit @@ -4276,6 +4349,7 @@ components: description: Apply only to generations which are newer than this ISO date. unit: $ref: '#/components/schemas/ModelUsageUnit' + nullable: true description: Unit used by this model. inputPrice: type: number @@ -4308,7 +4382,6 @@ components: required: - modelName - matchPattern - - unit Observations: title: Observations type: object diff --git a/langfuse-core/src/index.ts b/langfuse-core/src/index.ts index 53a7dea0..d1ddd6c0 100644 --- a/langfuse-core/src/index.ts +++ b/langfuse-core/src/index.ts @@ -56,6 +56,7 @@ import { type UpdateLangfuseGenerationBody, type UpdateLangfuseSpanBody, type GetMediaResponse, + UpdatePromptBody, } from "./types"; import { LangfuseMedia, type LangfuseMediaResolveMediaReferencesParams } from "./media/LangfuseMedia"; import { @@ -527,6 +528,15 @@ abstract class LangfuseCoreStateless { ); } + async updatePromptStateless( + body: UpdatePromptBody & { name: string; version: number } + ): Promise { + return this.fetchAndLogErrors( + `${this.baseUrl}/api/public/v2/prompts/${encodeURIComponent(body.name)}/versions/${encodeURIComponent(body.version)}`, + this._getFetchOptions({ method: "PATCH", body: JSON.stringify(body) }) + ); + } + async getPromptStateless( name: string, version?: number, @@ -1382,6 +1392,12 @@ export abstract class LangfuseCore extends LangfuseCoreStateless { return new TextPromptClient(promptResponse); } + async updatePrompt(body: { name: string; version: number; newLabels: string[] }): Promise { + const newPrompt = await this.updatePromptStateless(body); + this._promptCache.invalidate(body.name); + return newPrompt; + } + async getPrompt( name: string, version?: number, diff --git a/langfuse-core/src/openapi/server.ts b/langfuse-core/src/openapi/server.ts index 2fbc6996..18a90926 100644 --- a/langfuse-core/src/openapi/server.ts +++ b/langfuse-core/src/openapi/server.ts @@ -190,6 +190,7 @@ export interface paths { * * Notes: * + * - Introduction to data model: https://langfuse.com/docs/tracing-data-model * - Batch sizes are limited to 3.5 MB in total. You need to adjust the number of events per batch accordingly. * - The API does not return a 4xx status code for input errors. Instead, it responds with a 207 status code, which includes a list of the encountered errors. */ post: operations["ingestion_batch"]; @@ -338,6 +339,23 @@ export interface paths { patch?: never; trace?: never; }; + "/api/public/v2/prompts/{promptName}/version/{version}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + /** @description Update labels for a specific prompt version */ + patch: operations["promptVersion_update"]; + trace?: never; + }; "/api/public/v2/prompts/{promptName}": { parameters: { query?: never; @@ -949,12 +967,12 @@ export interface components { /** @description Regex pattern which matches this model definition to generation.model. Useful in case of fine-tuned models. If you want to exact match, use `(?i)^modelname$` */ matchPattern: string; /** - * Format: date + * Format: date-time * @description Apply only to generations which are newer than this ISO date. */ startDate?: string | null; /** @description Unit used by this model. */ - unit: components["schemas"]["ModelUsageUnit"]; + unit?: components["schemas"]["ModelUsageUnit"]; /** * Format: double * @description Price (USD) per input unit @@ -1477,7 +1495,7 @@ export interface components { */ startDate?: string | null; /** @description Unit used by this model. */ - unit: components["schemas"]["ModelUsageUnit"]; + unit?: components["schemas"]["ModelUsageUnit"]; /** * Format: double * @description Price (USD) per input unit @@ -3317,6 +3335,77 @@ export interface operations { }; }; }; + promptVersion_update: { + parameters: { + query?: never; + header?: never; + path: { + /** @description The name of the prompt */ + promptName: string; + /** @description Version of the prompt to update */ + version: number; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + /** @description New labels for the prompt version. Labels are unique across versions. The "latest" label is reserved and managed by Langfuse. */ + newLabels: string[]; + }; + }; + }; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Prompt"]; + }; + }; + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + 401: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + 404: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + 405: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + }; + }; prompts_get: { parameters: { query?: { diff --git a/langfuse-core/src/prompts/promptCache.ts b/langfuse-core/src/prompts/promptCache.ts index 69cc2f37..729a40a7 100644 --- a/langfuse-core/src/prompts/promptCache.ts +++ b/langfuse-core/src/prompts/promptCache.ts @@ -50,4 +50,12 @@ export class LangfusePromptCache { public isRefreshing(key: string): boolean { return this._refreshingKeys.has(key); } + + public invalidate(promptName: string): void { + for (const key of this._cache.keys()) { + if (key.startsWith(promptName)) { + this._cache.delete(key); + } + } + } } diff --git a/langfuse-core/src/types.ts b/langfuse-core/src/types.ts index 9545145e..a56b3e34 100644 --- a/langfuse-core/src/types.ts +++ b/langfuse-core/src/types.ts @@ -161,6 +161,9 @@ export type GetLangfuseDatasetRunsResponse = FixTypes< export type CreateLangfusePromptBody = FixTypes< paths["/api/public/v2/prompts"]["post"]["requestBody"]["content"]["application/json"] >; +export type UpdatePromptBody = FixTypes< + paths["/api/public/v2/prompts/{promptName}/version/{version}"]["patch"]["requestBody"]["content"]["application/json"] +>; export type CreateLangfusePromptResponse = paths["/api/public/v2/prompts"]["post"]["responses"]["200"]["content"]["application/json"];