/** @jest-environment node */ import { prisma } from "@langfuse/shared/src/db"; import { disconnectQueues, makeAPICall } from "@/src/__tests__/test-utils"; import { v4 as uuidv4, v4 } from "uuid"; import { PromptSchema, type ValidatedPrompt, type ChatMessage, type Prompt, PromptType, } from "@langfuse/shared"; import { parsePromptDependencyTags } from "@langfuse/shared"; import { generateId, nanoid } from "ai"; import { type PromptsMetaResponse } from "@/src/features/prompts/server/actions/getPromptsMeta"; import { createOrgProjectAndApiKey, getObservationById, MAX_PROMPT_NESTING_DEPTH, ChatMessageType, } from "@langfuse/shared/src/server"; import { randomUUID } from "node:crypto"; import waitForExpect from "wait-for-expect"; const projectId = "7a88fb47-b4e2-43b8-a06c-a5ce950dc53a"; const baseURI = "/api/public/v2/prompts"; type CreatePromptInDBParams = { promptId?: string; name: string; prompt: string; labels: string[]; version: number; config: any; projectId: string; createdBy: string; type?: PromptType; tags?: string[]; createdAt?: Date; updatedAt?: Date; }; const createPromptInDB = async (params: CreatePromptInDBParams) => { return await prisma.prompt.create({ data: { id: params.promptId ?? uuidv4(), name: params.name, prompt: params.prompt, labels: params.labels, version: params.version, config: params.config, project: { connect: { id: params.projectId }, }, createdBy: params.createdBy, type: params.type, tags: params.tags, createdAt: params.createdAt, updatedAt: params.updatedAt, }, }); }; const setupTriggerAndAction = async (projectId: string) => { const trigger = await prisma.trigger.create({ data: { id: v4(), projectId: projectId, eventSource: "prompt", eventActions: ["updated"], filter: [], status: "ACTIVE", }, }); trigger.id; // Create webhook action const action = await prisma.action.create({ data: { id: v4(), projectId: projectId, type: "WEBHOOK", config: { type: "WEBHOOK", url: "https://example.com/prompt-labels-webhook", headers: { "Content-Type": "application/json" }, apiVersion: { prompt: "v1" }, }, }, }); action.id; // Link trigger to action await prisma.automation.create({ data: { projectId: projectId, triggerId: trigger.id, actionId: action.id, name: "Prompt Labels Automation", }, }); return { actionId: action.id, triggerId: trigger.id, }; }; const testPromptEquality = ( promptParams: CreatePromptInDBParams, prompt: Prompt, ) => { if (promptParams.promptId) { expect(prompt.id).toBe(promptParams.promptId); } expect(prompt.name).toBe(promptParams.name); expect(prompt.prompt).toBe(promptParams.prompt); expect(prompt.type).toBe(promptParams.type ? promptParams.type : "text"); expect(prompt.version).toBe(promptParams.version); expect(prompt.labels).toEqual(promptParams.labels); expect(prompt.createdBy).toBe(promptParams.createdBy); expect(prompt.config).toEqual(promptParams.config); expect(prompt.tags).toEqual([]); expect(prompt.resolutionGraph).toBeNull(); }; describe("/api/public/v2/prompts API Endpoint", () => { afterAll(async () => { await disconnectQueues(); }); describe("when fetching a prompt", () => { it("should return a 401 if key is invalid", async () => { const projectId = uuidv4(); const response = await makeAPICall( "GET", baseURI, undefined, `Bearer ${projectId}`, ); expect(response.status).toBe(401); const body = response.body; expect(body).toHaveProperty("error"); // @ts-expect-error expect(body.error).toContain("Unauthorized"); }); it("should fetch a prompt", async () => { const { projectId, auth } = await createOrgProjectAndApiKey(); const promptId = uuidv4(); const promptName = "promptName" + nanoid(); const createPromptParams: CreatePromptInDBParams = { promptId: promptId, name: promptName, prompt: "prompt", labels: ["production"], version: 1, config: { temperature: 0.1, }, projectId, createdBy: "user-1", }; await createPromptInDB(createPromptParams); const fetchedPrompt = await makeAPICall( "GET", `${baseURI}/${encodeURIComponent(promptName)}`, undefined, auth, ); expect(fetchedPrompt.status).toBe(200); if (!isPrompt(fetchedPrompt.body)) { throw new Error("Expected body to be a prompt"); } testPromptEquality(createPromptParams, fetchedPrompt.body); }); it("should fetch a prompt with special characters", async () => { const { projectId, auth } = await createOrgProjectAndApiKey(); const promptName = "promptName?!+ =@#;" + nanoid(); const createPromptParams: CreatePromptInDBParams = { name: promptName, prompt: "prompt", labels: ["production"], version: 1, config: { temperature: 0.1, }, projectId, createdBy: "user-1", }; await createPromptInDB(createPromptParams); const fetchedPrompt = await makeAPICall( "GET", `${baseURI}/${encodeURIComponent(promptName)}`, undefined, auth, ); expect(fetchedPrompt.status).toBe(200); if (!isPrompt(fetchedPrompt.body)) { throw new Error("Expected body to be a prompt"); } testPromptEquality(createPromptParams, fetchedPrompt.body); }); it("should fetch a prompt by version even if not production", async () => { const { projectId, auth } = await createOrgProjectAndApiKey(); const promptName = "nonProductionPrompt" + nanoid(); const createPromptParams: CreatePromptInDBParams = { name: promptName, prompt: "prompt", labels: [], version: 1, config: { temperature: 0.1, }, projectId, createdBy: "user-1", }; await createPromptInDB(createPromptParams); const fetchedDefaultPrompt = await makeAPICall( "GET", `${baseURI}/${encodeURIComponent(promptName)}`, undefined, auth, ); expect(fetchedDefaultPrompt.status).toBe(404); const fetchedPrompt = await makeAPICall( "GET", `${baseURI}/${encodeURIComponent(promptName)}?version=1`, undefined, auth, ); if (!isPrompt(fetchedPrompt.body)) { throw new Error("Expected body to be a prompt"); } testPromptEquality(createPromptParams, fetchedPrompt.body); }); it("should fetch a prompt by label even if not production", async () => { const { projectId, auth } = await createOrgProjectAndApiKey(); const promptName = "nonProductionPrompt_" + nanoid(); const createPromptParams: CreatePromptInDBParams = { name: promptName, prompt: "prompt", labels: ["dev"], version: 1, config: { temperature: 0.1, }, projectId, createdBy: "user-1", }; await createPromptInDB(createPromptParams); const fetchedDefaultPrompt = await makeAPICall( "GET", `${baseURI}/${encodeURIComponent(promptName)}`, undefined, auth, ); expect(fetchedDefaultPrompt.status).toBe(404); const fetchedPrompt = await makeAPICall( "GET", `${baseURI}/${encodeURIComponent(promptName)}?label=dev`, undefined, auth, ); if (!isPrompt(fetchedPrompt.body)) { throw new Error("Expected body to be a prompt"); } testPromptEquality(createPromptParams, fetchedPrompt.body); }); (it("should fetch the latest prompt if label is latest", async () => { const { projectId, auth } = await createOrgProjectAndApiKey(); const promptName = "latestPrompt_" + nanoid(); const productionPromptParams: CreatePromptInDBParams = { name: promptName, prompt: "prompt", labels: ["production"], version: 1, config: { temperature: 0.1, }, projectId, createdBy: "user-1", }; const latestPromptParams: CreatePromptInDBParams = { name: promptName, prompt: "prompt", labels: ["latest"], version: 2, config: { temperature: 0.1, }, projectId, createdBy: "user-1", }; await createPromptInDB(latestPromptParams); await createPromptInDB(productionPromptParams); const fetchedPrompt = await makeAPICall( "GET", `${baseURI}/${encodeURIComponent(promptName)}?label=latest`, undefined, auth, ); if (!isPrompt(fetchedPrompt.body)) { throw new Error("Expected body to be a prompt"); } testPromptEquality(latestPromptParams, fetchedPrompt.body); const fetchedDefaultPrompt = await makeAPICall( "GET", `${baseURI}/${encodeURIComponent(promptName)}`, undefined, auth, ); expect(fetchedDefaultPrompt.status).toBe(200); if (!isPrompt(fetchedDefaultPrompt.body)) { throw new Error("Expected body to be a prompt"); } testPromptEquality(productionPromptParams, fetchedDefaultPrompt.body); }), it("should fetch the production prompt if no version or label set", async () => { const { projectId, auth } = await createOrgProjectAndApiKey(); const promptName = "prompt_" + nanoid(); const nonProductionPromptParams: CreatePromptInDBParams = { name: promptName, prompt: "prompt", labels: ["staging"], version: 1, config: { temperature: 0.1, }, projectId, createdBy: "user-1", }; const productionPromptParams: CreatePromptInDBParams = { name: promptName, prompt: "prompt", labels: ["production"], version: 2, config: { temperature: 0.1, }, projectId, createdBy: "user-1", }; await createPromptInDB(productionPromptParams); await createPromptInDB(nonProductionPromptParams); const fetchedPrompt = await makeAPICall( "GET", `${baseURI}/${encodeURIComponent(promptName)}`, undefined, auth, ); if (!isPrompt(fetchedPrompt.body)) { throw new Error("Expected body to be a prompt"); } testPromptEquality(productionPromptParams, fetchedPrompt.body); })); it("should return a 404 if prompt does not exist", async () => { const fetchedPrompt = await makeAPICall( "GET", `${baseURI}/${encodeURIComponent("random_prompt")}`, undefined, ); expect(fetchedPrompt.status).toBe(404); expect(fetchedPrompt.body).toHaveProperty("error"); // @ts-expect-error expect(fetchedPrompt.body.error).toContain("NotFound"); }); it("should relate generation to prompt", async () => { const { projectId, auth } = await createOrgProjectAndApiKey(); const promptName = "prompt-name" + nanoid(); const traceId = v4(); const generationId = v4(); const promptId = uuidv4(); await prisma.prompt.create({ data: { id: promptId, name: promptName, prompt: "prompt", labels: ["production"], version: 1, project: { connect: { id: projectId }, }, createdBy: "user-1", }, }); const response = await makeAPICall( "POST", "/api/public/ingestion", { metadata: { sdk_verion: "1.0.0", sdk_name: "python", }, batch: [ { id: v4(), type: "trace-create", timestamp: new Date().toISOString(), body: { id: traceId, name: "trace-name", }, }, { id: v4(), type: "generation-create", timestamp: new Date().toISOString(), body: { id: generationId, traceId: traceId, type: "GENERATION", name: "generation-name", promptName, promptVersion: 1, }, }, ], }, auth, ); expect(response.status).toBe(207); // Delay to allow for async processing await waitForExpect(async () => { const dbGeneration = await getObservationById({ id: generationId, projectId, }); expect(dbGeneration?.id).toBe(generationId); expect(dbGeneration?.promptId).toBe(promptId); }, 8000); }, 10000); }); describe("when creating a prompt", () => { it("should create and fetch a chat prompt", async () => { const { auth, projectId } = await createOrgProjectAndApiKey(); const { actionId, triggerId } = await setupTriggerAndAction(projectId); const promptName = "prompt-name" + nanoid(); const chatMessages = [ { role: "system", content: "You are a bot" }, { role: "user", content: "What's up?" }, ]; const response = await makeAPICall( "POST", baseURI, { name: promptName, prompt: chatMessages, type: "chat", labels: ["production"], commitMessage: "chore: setup initial prompt", }, auth, ); expect(response.status).toBe(201); const { body: fetchedPrompt } = await makeAPICall( "GET", `${baseURI}/${promptName}`, undefined, auth, ); const validatedPrompt = validatePrompt(fetchedPrompt); expect(validatedPrompt.name).toBe(promptName); expect(validatedPrompt.prompt).toEqual(chatMessages); expect(validatedPrompt.type).toBe("chat"); expect(validatedPrompt.version).toBe(1); expect(validatedPrompt.labels).toEqual(["production", "latest"]); expect(validatedPrompt.createdBy).toBe("API"); expect(validatedPrompt.config).toEqual({}); expect(validatedPrompt.commitMessage).toBe("chore: setup initial prompt"); await waitForExpect(async () => { // check that the action execution is created const actionExecution = await prisma.automationExecution.findFirst({ where: { projectId, triggerId, actionId, }, }); expect(actionExecution).not.toBeNull(); expect(actionExecution?.status).toBe("PENDING"); expect(actionExecution?.sourceId).toBe(validatedPrompt.id); }); }, 10000); it("should create and fetch a chat prompt with message placeholders", async () => { const { auth } = await createOrgProjectAndApiKey(); const promptName = `prompt-name-message-placeholders${generateId()}`; const commitMessage = "feat: add message placeholders support"; const chatMessages = [ { role: "system", content: "You are a helpful assistant with conversation context.", }, { type: ChatMessageType.Placeholder, name: "conversation_history", }, { role: "user", content: "{{user_question}}" }, ]; const response = await makeAPICall( "POST", baseURI, { name: promptName, prompt: chatMessages, type: "chat", labels: ["production"], commitMessage: commitMessage, }, auth, ); expect(response.status).toBe(201); const { body: fetchedPrompt } = await makeAPICall( "GET", `${baseURI}/${promptName}`, undefined, auth, ); const validatedPrompt = validatePrompt(fetchedPrompt); expect(validatedPrompt.name).toBe(promptName); expect(validatedPrompt.prompt).toEqual(chatMessages); expect(validatedPrompt.type).toBe("chat"); expect(validatedPrompt.version).toBe(1); expect(validatedPrompt.labels).toEqual(["production", "latest"]); expect(validatedPrompt.createdBy).toBe("API"); expect(validatedPrompt.config).toEqual({}); expect(validatedPrompt.commitMessage).toBe(commitMessage); // Verify the placeholder message structure is preserved const messages = validatedPrompt.prompt as ChatMessage[]; const placeholderMessage = messages[1] as { type: ChatMessageType.Placeholder; name: string; }; expect(placeholderMessage.type).toBe(ChatMessageType.Placeholder); expect(placeholderMessage.name).toBe("conversation_history"); }); it("should fail if chat prompt has string prompt", async () => { const { auth } = await createOrgProjectAndApiKey(); const promptName = "prompt-name"; const response = await makeAPICall( "POST", baseURI, { name: promptName, prompt: "prompt", type: "chat", labels: ["production"], }, auth, ); expect(response.status).toBe(400); const { body, status } = await makeAPICall( "GET", `/api/public/prompts?name=${promptName}`, undefined, auth, ); expect(status).toBe(404); expect(body).toEqual({ error: "LangfuseNotFoundError", message: "Prompt not found", }); }); it("should fail if chat prompt has incorrect messages format", async () => { const { auth } = await createOrgProjectAndApiKey(); const promptName = "prompt-name" + nanoid(); const incorrectChatMessages = [ { role: "system", content: "You are a bot" }, { role: "user", message: "What's up?" }, ]; const response = await makeAPICall( "POST", baseURI, { name: promptName, prompt: incorrectChatMessages, type: "chat", labels: ["production"], }, auth, ); expect(response.status).toBe(400); const { body, status } = await makeAPICall( "GET", `${baseURI}/${promptName}`, undefined, auth, ); expect(status).toBe(404); // @ts-expect-error expect(body.error).toBe("LangfuseNotFoundError"); }); it("should fail if text prompt has message format", async () => { const { auth } = await createOrgProjectAndApiKey(); const promptName = "prompt-name" + nanoid(); const response = await makeAPICall( "POST", baseURI, { name: promptName, prompt: [{ role: "system", content: "You are a bot" }], type: "text", labels: ["production"], }, auth, ); expect(response.status).toBe(400); const { body, status } = await makeAPICall( "GET", `${baseURI}/${promptName}`, undefined, auth, ); expect(status).toBe(404); // @ts-expect-error expect(body.error).toBe("LangfuseNotFoundError"); }); it("should fail if previous versions have different prompt type", async () => { const { auth } = await createOrgProjectAndApiKey(); // Create a chat prompt const promptName = "prompt-name" + nanoid(); const chatMessages = [ { role: "system", content: "You are a bot" }, { role: "user", content: "What's up?" }, ]; const postResponse1 = await makeAPICall( "POST", baseURI, { name: promptName, prompt: chatMessages, labels: ["production"], type: "chat", }, auth, ); expect(postResponse1.status).toBe(201); // Try creating a text prompt with the same name const postResponse2 = await makeAPICall( "POST", baseURI, { name: promptName, prompt: "prompt", type: "text", labels: ["production"], version: 2, }, auth, ); expect(postResponse2.status).toBe(400); // @ts-expect-error expect(postResponse2.body.error).toBe("InvalidRequestError"); // Check if the prompt is still the chat prompt const getResponse1 = await makeAPICall( "GET", `${baseURI}/${promptName}`, undefined, auth, ); expect(getResponse1.status).toBe(200); const validatedPrompt = validatePrompt(getResponse1.body); expect(validatedPrompt.name).toBe(promptName); expect(validatedPrompt.prompt).toEqual(chatMessages); expect(validatedPrompt.type).toBe("chat"); expect(validatedPrompt.version).toBe(1); expect(validatedPrompt.labels).toEqual(["production", "latest"]); expect(validatedPrompt.createdBy).toBe("API"); expect(validatedPrompt.config).toEqual({}); // Check that the text prompt has not been created const getResponse2 = await makeAPICall( "GET", `${baseURI}/${promptName}?version=2`, undefined, auth, ); expect(getResponse2.status).toBe(404); // @ts-expect-error expect(getResponse2.body.error).toBe("LangfuseNotFoundError"); }); it("should correctly handle overwriting labels", async () => { const { auth } = await createOrgProjectAndApiKey(); const promptName = "prompt-name" + nanoid(); // First prompt has multiple labels const prompt1 = await makeAPICall( "POST", baseURI, { name: promptName, prompt: "prompt1", labels: ["production", "staging", "development"], version: 1, config: { temperature: 0.1, }, createdBy: "user-1", }, auth, ); // Second prompt overwrites production and staging label const prompt2 = await makeAPICall( "POST", baseURI, { name: promptName, prompt: "prompt2", labels: ["production", "production", "staging"], // Should be deduped version: 2, config: { temperature: 0.2, }, createdBy: "user-1", }, auth, ); // Third prompt overwrites staging label const prompt3 = await makeAPICall( "POST", baseURI, { name: promptName, prompt: "prompt3", labels: ["staging"], isActive: false, version: 3, config: { temperature: 0.3, }, createdBy: "user-1", }, auth, ); // Expect the second prompt to be fetched as default production prompt const fetchedProductionPrompt = await makeAPICall( "GET", `${baseURI}/${promptName}`, undefined, auth, ); expect(fetchedProductionPrompt.status).toBe(200); if (!isPrompt(fetchedProductionPrompt.body)) { throw new Error("Expected body to be a prompt"); } // @ts-expect-error expect(fetchedProductionPrompt.body.id).toBe(prompt2.body.id); expect(fetchedProductionPrompt.body.labels).toEqual(["production"]); // Only production label should be present // Expect the first prompt to have only development label const fetchedFirstPrompt = await makeAPICall( "GET", `${baseURI}/${promptName}?version=1`, undefined, auth, ); expect(fetchedFirstPrompt.status).toBe(200); if (!isPrompt(fetchedFirstPrompt.body)) { throw new Error("Expected body to be a prompt"); } // @ts-expect-error expect(fetchedFirstPrompt.body.id).toBe(prompt1.body.id); expect(fetchedFirstPrompt.body.labels).toEqual(["development"]); // Expect the third prompt to have only staging label const fetchedThirdPrompt = await makeAPICall( "GET", `${baseURI}/${promptName}?version=3`, undefined, auth, ); expect(fetchedThirdPrompt.status).toBe(200); if (!isPrompt(fetchedThirdPrompt.body)) { throw new Error("Expected body to be a prompt"); } // @ts-expect-error expect(fetchedThirdPrompt.body.id).toBe(prompt3.body.id); expect(fetchedThirdPrompt.body.labels).toEqual(["staging", "latest"]); }); it("should create empty object if no config is provided", async () => { const { auth } = await createOrgProjectAndApiKey(); const promptName = "prompt-name" + nanoid(); await makeAPICall( "POST", baseURI, { name: promptName, prompt: "prompt", labels: ["production"], }, auth, ); const fetchedPrompt = await makeAPICall( "GET", `${baseURI}/${promptName}`, undefined, auth, ); expect(fetchedPrompt.status).toBe(200); if (!isPrompt(fetchedPrompt.body)) { throw new Error("Expected body to be a prompt"); } expect(fetchedPrompt.body.name).toBe(promptName); expect(fetchedPrompt.body.prompt).toBe("prompt"); expect(fetchedPrompt.body.type).toBe("text"); expect(fetchedPrompt.body.version).toBe(1); expect(fetchedPrompt.body.labels).toEqual(["production", "latest"]); expect(fetchedPrompt.body.createdBy).toBe("API"); expect(fetchedPrompt.body.config).toEqual({}); }); describe("prompt name validation", () => { const testInvalidName = async ( name: string, expectedError: string, auth?: string, ) => { const response = await makeAPICall( "POST", baseURI, { name, prompt: "test prompt", type: "text", }, auth, ); expect(response.status).toBe(400); expect(response.body.message).toBe("Invalid request data"); expect(JSON.stringify(response.body.error)).toContain( `"message":"${expectedError}"`, ); const hasExpectedMessage = JSON.stringify(response.body.error).includes( `"message":"${expectedError}"`, ); expect(hasExpectedMessage).toBe(true); }; const testValidName = async (name: string, auth?: string) => { const response = await makeAPICall( "POST", baseURI, { name, prompt: "test prompt", type: "text", }, auth, ); expect(response.status).toBe(201); await prisma.prompt.deleteMany({ where: { name, projectId }, }); }; it("should reject invalid prompt names", async () => { const { auth } = await createOrgProjectAndApiKey(); // Test invalid patterns await testInvalidName( "/invalid-name", "Name cannot start with a slash", auth, ); await testInvalidName( "invalid-name/", "Name cannot end with a slash", auth, ); await testInvalidName( "invalid//name", "Name cannot contain consecutive slashes", auth, ); await testInvalidName( "invalid|name", "Prompt name cannot contain '|' character", auth, ); await testInvalidName("new", "Prompt name cannot be 'new'", auth); await testInvalidName("", "Text cannot be empty", auth); await testInvalidName( "Test
", "Text cannot contain HTML tags", auth, ); }); it("should accept valid prompt names", async () => { const { auth } = await createOrgProjectAndApiKey(); const validNames = [ "simple-name", "name_with_underscores", "name.with.dots", "UPPERCASE", "folder/subfolder/name", "name-with-123-numbers", "_starting_with_underscore", "ending_with_underscore_", "multiple___underscores", "multiple---hyphens", "multiple...dots", "name with spaces", "multiple spaces", "angled[brac]es]", ]; for (const name of validNames) { await testValidName(name, auth); } }); }); it("should update tags across versions", async () => { const { auth } = await createOrgProjectAndApiKey(); const promptName = "prompt-name" + nanoid(); const createPromptVersion = async (tags?: string[]) => { await makeAPICall( "POST", baseURI, { name: promptName, prompt: "This is a test prompt", type: PromptType.Text, ...(tags !== undefined && { tags: tags }), }, auth, ); }; const fetchPromptVersion = async (version: number) => { const fetchedPrompt = await makeAPICall( "GET", `${baseURI}/${promptName}?version=${version}`, undefined, auth, ); expect(fetchedPrompt.status).toBe(200); if (!isPrompt(fetchedPrompt.body)) { throw new Error("Expected body to be a prompt"); } return fetchedPrompt.body; }; // Create version 1 with ["tag"] await createPromptVersion(["tag"]); let fetchedPrompt1 = await fetchPromptVersion(1); expect(fetchedPrompt1.tags).toEqual(["tag"]); expect(fetchedPrompt1.version).toBe(1); // Create version 2 with no tags provided (should use tags from version 1) await createPromptVersion(); let fetchedPrompt2 = await fetchPromptVersion(2); expect(fetchedPrompt2.tags).toEqual(["tag"]); expect(fetchedPrompt2.version).toBe(2); // Create version 3 with ["tag1", "tag2", "tag3"] (should update tags across versions) await createPromptVersion(["tag1", "tag2", "tag3"]); fetchedPrompt1 = await fetchPromptVersion(1); fetchedPrompt2 = await fetchPromptVersion(2); let fetchedPrompt3 = await fetchPromptVersion(3); expect(fetchedPrompt1.tags).toEqual(["tag1", "tag2", "tag3"]); expect(fetchedPrompt1.version).toBe(1); expect(fetchedPrompt2.tags).toEqual(["tag1", "tag2", "tag3"]); expect(fetchedPrompt2.version).toBe(2); expect(fetchedPrompt3.tags).toEqual(["tag1", "tag2", "tag3"]); expect(fetchedPrompt3.version).toBe(3); // remove tags await createPromptVersion([]); fetchedPrompt1 = await fetchPromptVersion(1); fetchedPrompt2 = await fetchPromptVersion(2); fetchedPrompt3 = await fetchPromptVersion(3); let fetchedPrompt4 = await fetchPromptVersion(4); expect(fetchedPrompt1.tags).toEqual([]); expect(fetchedPrompt1.version).toBe(1); expect(fetchedPrompt2.tags).toEqual([]); expect(fetchedPrompt2.version).toBe(2); expect(fetchedPrompt3.tags).toEqual([]); expect(fetchedPrompt3.version).toBe(3); expect(fetchedPrompt4.tags).toEqual([]); expect(fetchedPrompt4.version).toBe(4); }); it("should create and fetch a test prompt with slashes in the name", async () => { const { auth } = await createOrgProjectAndApiKey(); const promptName = "this/is/a/prompt/with/a/slash" + nanoid(); const response = await makeAPICall( "POST", baseURI, { name: promptName, prompt: "This is a prompt in a folder structure", type: "text", labels: ["production"], commitMessage: "chore: setup folder structure prompt", }, auth, ); expect(response.status).toBe(201); const { body: fetchedPrompt } = await makeAPICall( "GET", `${baseURI}/${encodeURIComponent(promptName)}`, undefined, auth, ); const validatedPrompt = validatePrompt(fetchedPrompt); // expect(fetchedPrompt.status).toBe(200); // if (!isPrompt(fetchedPrompt.body)) { // throw new Error("Expected body to be a prompt"); // } // Verify the name with slashes is preserved expect(validatedPrompt.name).toBe(promptName); expect(validatedPrompt.name).toContain("/"); expect(validatedPrompt.prompt).toBe( "This is a prompt in a folder structure", ); }); it("should prevent creating a prompt with both a variable and placeholder with the same name", async () => { const { auth } = await createOrgProjectAndApiKey(); const promptName = "prompt-same-name-conflict-" + nanoid(); // Try to create a prompt where the same name is used as both a variable and a placeholder const response = await makeAPICall( "POST", baseURI, { name: promptName, prompt: [ { role: "system", content: "Hello {{userName}}" }, { type: ChatMessageType.Placeholder, name: "userName" }, { role: "user", content: "How are you?" }, ], type: "chat", }, auth, ); // This should fail with a 400 error expect(response.status).toBe(400); expect(response.body).toHaveProperty("error"); expect(response.body).toHaveProperty("message"); // @ts-expect-error expect(response.body.message).toContain( "variables and placeholders must be unique", ); // @ts-expect-error expect(response.body.message).toContain("userName"); }); it("should allow creating a new version of a prompt with placeholder names that conflict a variable name in a previous version", async () => { const { auth } = await createOrgProjectAndApiKey(); const promptName = "prompt-with-variable-conflict-" + nanoid(); // First, create a chat prompt with a message variable const v1Response = await makeAPICall( "POST", baseURI, { name: promptName, prompt: [ { role: "system", content: "You are a helpful {{conversationHistory}}", }, { role: "user", content: "Continue our conversation" }, ], type: "chat", labels: ["production"], }, auth, ); expect(v1Response.status).toBe(201); // Try to create a new version with a text variable that has the same name as the placeholder const v2Response = await makeAPICall( "POST", baseURI, { name: promptName, prompt: [ { role: "system", content: "You are a helpful assistant with context: {{newHistory}}", }, { type: "placeholder", name: "conversationHistory" }, { role: "user", content: "Continue our conversation" }, ], type: "chat", }, auth, ); // This should succeed, we allow cross-version name reuse expect(v2Response.status).toBe(201); expect(v2Response.body).toHaveProperty("id"); expect(v2Response.body).toHaveProperty("version"); // @ts-expect-error - Response body type is flexible for testing expect(v2Response.body.version).toBe(2); }); }); describe("when fetching a prompt list", () => { const otherProjectPromptName = "prompt-5"; let otherProjectId: string; let projectId: string; let auth: string; beforeEach(async () => { // Create a prompt in a different project ({ projectId: projectId, auth: auth } = await createOrgProjectAndApiKey()); ({ projectId: otherProjectId } = await createOrgProjectAndApiKey()); await createPromptInDB({ name: otherProjectPromptName, prompt: "prompt-5", labels: ["production"], version: 1, config: {}, projectId: otherProjectId, createdBy: "user-test", }); // Create prompts in the current project await Promise.all( getMockPrompts(projectId, otherProjectId).map(createPromptInDB), ); }); it("should only return prompts from the current project", async () => { // Add a prompt from a different project const response = await makeAPICall("GET", `${baseURI}`, undefined, auth); expect(response.status).toBe(200); const body = response.body as unknown as PromptsMetaResponse; expect(body.data).toHaveLength(3); expect( body.data.some((promptMeta) => promptMeta.name === "prompt-1"), ).toBe(true); expect( body.data.some( (promptMeta) => promptMeta.name === otherProjectPromptName, ), ).toBe(false); }); it("should fetch a prompt meta list", async () => { const response = await makeAPICall("GET", `${baseURI}`, undefined, auth); expect(response.status).toBe(200); const body = response.body as unknown as PromptsMetaResponse; expect(body.data).toHaveLength(3); expect( body.data.some( (promptMeta) => promptMeta.name === otherProjectPromptName, ), ).toBe(false); const [promptMeta1, promptMeta2, promptMeta3] = body.data; // Validate prompt-1 meta expect(promptMeta1.name).toBe("prompt-1"); expect(promptMeta1.versions).toEqual([1, 2, 4]); expect(promptMeta1.labels).toEqual(["production", "version2"]); expect(promptMeta1.tags).toEqual([]); expect(promptMeta1.lastUpdatedAt).toBeDefined(); // Validate prompt-2 meta expect(promptMeta2.name).toBe("prompt-2"); expect(promptMeta2.versions).toEqual([1, 2, 3]); expect(promptMeta2.labels).toEqual(["dev", "production", "staging"]); expect(promptMeta2.tags).toEqual([]); expect(promptMeta2.lastUpdatedAt).toBeDefined(); // Validate prompt-3 meta expect(promptMeta3.name).toBe("prompt-3"); expect(promptMeta3.versions).toEqual([1]); expect(promptMeta3.labels).toEqual(["production"]); expect(promptMeta3.tags).toEqual(["tag-1"]); expect(promptMeta3.lastUpdatedAt).toBeDefined(); // Validate pagination expect(body.meta.page).toBe(1); expect(body.meta.limit).toBe(10); expect(body.meta.totalPages).toBe(1); expect(body.meta.totalItems).toBe(3); // Validate pagination backwards compatibility // https://github.com/langfuse/langfuse/issues/2068 expect(body.pagination?.page).toBe(1); expect(body.pagination?.limit).toBe(10); expect(body.pagination?.totalPages).toBe(1); expect(body.pagination?.totalItems).toBe(3); }); it("should fetch a prompt meta list with name filter", async () => { const response = await makeAPICall( "GET", `${baseURI}?name=prompt-1`, undefined, auth, ); expect(response.status).toBe(200); const body = response.body as unknown as PromptsMetaResponse; expect(body.data).toHaveLength(1); expect(body.data[0].name).toBe("prompt-1"); expect(body.data[0].versions).toEqual([1, 2, 4]); expect(body.data[0].labels).toEqual(["production", "version2"]); expect(body.data[0].tags).toEqual([]); // Validate pagination expect(body.meta.page).toBe(1); expect(body.meta.limit).toBe(10); expect(body.meta.totalPages).toBe(1); expect(body.meta.totalItems).toBe(1); // Test with a different name const response2 = await makeAPICall( "GET", `${baseURI}?name=prompt-2`, undefined, auth, ); expect(response2.status).toBe(200); const body2 = response2.body as unknown as PromptsMetaResponse; expect(body2.data).toHaveLength(1); expect(body2.data[0].name).toBe("prompt-2"); expect(body2.data[0].versions).toEqual([1, 2, 3]); expect(body2.data[0].labels).toEqual(["dev", "production", "staging"]); expect(body2.data[0].tags).toEqual([]); // Validate pagination expect(body2.meta.page).toBe(1); expect(body2.meta.limit).toBe(10); expect(body2.meta.totalPages).toBe(1); expect(body2.meta.totalItems).toBe(1); // Return 200 with empty list if name does not exist const response3 = await makeAPICall( "GET", `${baseURI}?name=non-existent`, undefined, auth, ); expect(response3.status).toBe(200); // @ts-expect-error expect(response3.body.data).toEqual([]); }); it("should fetch a prompt meta list with tag filter", async () => { const response = await makeAPICall( "GET", `${baseURI}?tag=tag-1`, undefined, auth, ); expect(response.status).toBe(200); const body = response.body as unknown as PromptsMetaResponse; expect(body.data).toHaveLength(1); expect(body.data[0].name).toBe("prompt-3"); expect(body.data[0].versions).toEqual([1]); expect(body.data[0].labels).toEqual(["production"]); expect(body.data[0].tags).toEqual(["tag-1"]); // Validate pagination expect(body.meta.page).toBe(1); expect(body.meta.limit).toBe(10); expect(body.meta.totalPages).toBe(1); expect(body.meta.totalItems).toBe(1); // Return 200 with empty list if tag does not exist const response3 = await makeAPICall( "GET", `${baseURI}?tag=non-existent`, undefined, auth, ); expect(response3.status).toBe(200); // @ts-expect-error expect(response3.body.data).toEqual([]); }); it("should fetch a prompt meta list with label filter", async () => { const response = await makeAPICall( "GET", `${baseURI}?label=production`, undefined, auth, ); expect(response.status).toBe(200); const body = response.body as unknown as PromptsMetaResponse; expect(body.data).toHaveLength(3); expect( body.data.some((promptMeta) => promptMeta.name === "prompt-1"), ).toBe(true); expect( body.data.some((promptMeta) => promptMeta.name === "prompt-2"), ).toBe(true); expect( body.data.some((promptMeta) => promptMeta.name === "prompt-3"), ).toBe(true); // Validate pagination expect(body.meta.page).toBe(1); expect(body.meta.limit).toBe(10); expect(body.meta.totalPages).toBe(1); expect(body.meta.totalItems).toBe(3); // Test with a different label const response2 = await makeAPICall( "GET", `${baseURI}?label=dev`, undefined, auth, ); expect(response2.status).toBe(200); const body2 = response2.body as unknown as PromptsMetaResponse; expect(body2.data).toHaveLength(1); expect(body2.data[0].name).toBe("prompt-2"); expect(body2.data[0].versions).toEqual([3]); // Only version 3 should be present as it is the only one with dev label expect(body2.data[0].labels).toEqual(["dev"]); // Only dev label should be present expect(body2.data[0].tags).toEqual([]); // Validate pagination expect(body2.meta.page).toBe(1); expect(body2.meta.limit).toBe(10); expect(body2.meta.totalPages).toBe(1); expect(body2.meta.totalItems).toBe(1); // Return 200 with empty list if label does not exist const response3 = await makeAPICall( "GET", `${baseURI}?label=non-existent`, undefined, auth, ); expect(response3.status).toBe(200); // @ts-expect-error expect(response3.body.data).toEqual([]); }); }); it("should fetch a prompt meta list with pagination", async () => { const { auth, projectId } = await createOrgProjectAndApiKey(); const { projectId: otherProjectId } = await createOrgProjectAndApiKey(); await Promise.all( getMockPrompts(projectId, otherProjectId).map(createPromptInDB), ); const response = await makeAPICall( "GET", `${baseURI}?page=1&limit=1`, undefined, auth, ); expect(response.status).toBe(200); const body = response.body as unknown as PromptsMetaResponse; expect(body.data).toHaveLength(1); expect(body.meta.page).toBe(1); expect(body.meta.limit).toBe(1); expect(body.meta.totalPages).toBe(3); expect(body.meta.totalItems).toBe(3); }); it("should fetch lastConfig correctly for a prompt with multiple versions", async () => { const { auth, projectId } = await createOrgProjectAndApiKey(); const { projectId: otherProjectId } = await createOrgProjectAndApiKey(); await Promise.all( getMockPrompts(projectId, otherProjectId).map(createPromptInDB), ); // no filters const response = await makeAPICall("GET", `${baseURI}`, undefined, auth); expect(response.status).toBe(200); const body = response.body as unknown as PromptsMetaResponse; expect(body.data).toHaveLength(3); expect(body.data.some((promptMeta) => promptMeta.name === "prompt-1")).toBe( true, ); expect(body.data.some((promptMeta) => promptMeta.name === "prompt-2")).toBe( true, ); expect(body.data.some((promptMeta) => promptMeta.name === "prompt-3")).toBe( true, ); const prompt1 = body.data.find( (promptMeta) => promptMeta.name === "prompt-1", ); expect(prompt1).toBeDefined(); expect(prompt1?.lastConfig).toEqual({ version: 4 }); const prompt2 = body.data.find( (promptMeta) => promptMeta.name === "prompt-2", ); expect(prompt2).toBeDefined(); expect(prompt2?.lastConfig).toEqual({}); // validate with label filter const response2 = await makeAPICall( "GET", `${baseURI}?label=version2`, undefined, auth, ); expect(response2.status).toBe(200); const body2 = response2.body as unknown as PromptsMetaResponse; expect(body2.data).toHaveLength(1); expect(body2.data[0].name).toBe("prompt-1"); expect(body2.data[0].lastConfig).toEqual({ version: 2 }); // validate with version filter const response3 = await makeAPICall( "GET", `${baseURI}?version=1`, undefined, auth, ); expect(response3.status).toBe(200); const body3 = response3.body as unknown as PromptsMetaResponse; expect(body3.data).toHaveLength(3); const prompt1v1 = body3.data.find( (promptMeta) => promptMeta.name === "prompt-1", ); expect(prompt1v1?.lastConfig).toEqual({ version: 1 }); }); it("should respect the fromUpdatedAt and toUpdatedAt filters on GET /prompts", async () => { const { auth, projectId } = await createOrgProjectAndApiKey(); const { projectId: otherProjectId } = await createOrgProjectAndApiKey(); await Promise.all( getMockPrompts(projectId, otherProjectId).map(createPromptInDB), ); // to and from const from = new Date("2024-01-02T00:00:00.000Z"); const to = new Date("2024-01-04T00:00:00.000Z"); const response = await makeAPICall( "GET", `${baseURI}?fromUpdatedAt=${from.toISOString()}&toUpdatedAt=${to.toISOString()}`, undefined, auth, ); expect(response.status).toBe(200); const body = response.body as unknown as PromptsMetaResponse; expect(body.data).toHaveLength(1); expect(body.data[0].name).toBe("prompt-1"); expect(body.data[0].lastUpdatedAt).toBe("2024-01-02T00:00:00.000Z"); expect(body.data[0].versions.length).toBe(1); expect(body.meta.totalItems).toBe(1); // only from const response2 = await makeAPICall( "GET", `${baseURI}?fromUpdatedAt=${from.toISOString()}`, undefined, auth, ); expect(response2.status).toBe(200); const body2 = response2.body as unknown as PromptsMetaResponse; expect(body2.data).toHaveLength(1); expect(body2.data[0].name).toBe("prompt-1"); expect(body2.data[0].lastUpdatedAt).toBe("2024-01-04T00:00:00.000Z"); expect(body2.data[0].versions.length).toBe(2); expect(body2.meta.totalItems).toBe(1); // only to const response3 = await makeAPICall( "GET", `${baseURI}?toUpdatedAt=${to.toISOString()}`, undefined, auth, ); expect(response3.status).toBe(200); const body3 = response3.body as unknown as PromptsMetaResponse; expect(body3.data).toHaveLength(3); expect(body3.data[0].name).toBe("prompt-1"); expect(body3.data[0].lastUpdatedAt).toBe("2024-01-02T00:00:00.000Z"); expect(body3.data[0].versions.length).toBe(2); expect(body3.data[1].name).toBe("prompt-2"); expect(body3.data[1].lastUpdatedAt).toBe("2000-03-01T00:00:00.000Z"); expect(body3.data[1].versions.length).toBe(3); expect(body3.data[2].name).toBe("prompt-3"); expect(body3.data[2].lastUpdatedAt).toBe("2000-01-01T00:00:00.000Z"); expect(body3.data[2].versions.length).toBe(1); expect(body3.meta.totalItems).toBe(3); }); }); describe("PATCH api/public/v2/prompts/[promptName]/versions/[version]", () => { let triggerId: string; let actionId: string; afterAll(async () => { await disconnectQueues(); }); it("should update the labels of a prompt", async () => { const { projectId: newProjectId, auth: newAuth } = await createOrgProjectAndApiKey(); const { actionId: newActionId, triggerId: newTriggerId } = await setupTriggerAndAction(newProjectId); const originalPrompt = await prisma.prompt.create({ data: { name: "prompt-1", projectId: newProjectId, version: 1, labels: ["production"], createdBy: "user-test", prompt: "prompt-1", }, }); const response = await makeAPICall( "PATCH", `${baseURI}/prompt-1/versions/1`, { newLabels: ["new-label"], }, newAuth, ); expect(response.status).toBe(200); const updatedPrompt = await prisma.prompt.findUnique({ where: { id: originalPrompt.id, }, }); expect(updatedPrompt?.labels).toContain("production"); expect(updatedPrompt?.labels).toContain("new-label"); expect(updatedPrompt?.labels).toHaveLength(2); // check that the action execution is created await waitForExpect(async () => { const actionExecution = await prisma.automationExecution.findFirst({ where: { projectId: newProjectId, triggerId: newTriggerId, actionId: newActionId, }, }); expect(actionExecution).not.toBeNull(); expect(actionExecution?.status).toBe("PENDING"); expect(actionExecution?.sourceId).toBe(originalPrompt.id); }); }); it("should remove label from previous version when adding to new version", async () => { const { projectId: newProjectId, auth: newAuth } = await createOrgProjectAndApiKey(); const { actionId: newActionId, triggerId: newTriggerId } = await setupTriggerAndAction(newProjectId); actionId = newActionId; triggerId = newTriggerId; // Create version 1 with "production" label await prisma.prompt.create({ data: { name: "prompt-1", projectId: newProjectId, version: 1, labels: ["production"], createdBy: "user-test", prompt: "prompt-1", }, }); // Create version 2 initially without the label await prisma.prompt.create({ data: { name: "prompt-1", projectId: newProjectId, version: 2, labels: [], createdBy: "user-test", prompt: "prompt-1", }, }); // Add "production" label to version 2 const response = await makeAPICall( "PATCH", `${baseURI}/prompt-1/versions/2`, { newLabels: ["production"], }, newAuth, ); expect(response.status).toBe(200); const responseBody = response.body as unknown as Prompt; expect(responseBody.labels).toEqual(["production"]); // Check version 2 got the label const promptV2 = await prisma.prompt.findFirst({ where: { projectId: newProjectId, name: "prompt-1", version: 2, }, }); expect(promptV2?.labels).toEqual(["production"]); // Check version 1 had the label removed const promptV1 = await prisma.prompt.findFirst({ where: { projectId: newProjectId, name: "prompt-1", version: 1, }, }); expect(promptV1?.labels).toEqual([]); await waitForExpect(async () => { const actionExecution = await prisma.automationExecution.findFirst({ where: { projectId: newProjectId, triggerId, actionId, sourceId: promptV2?.id, }, }); expect(actionExecution).not.toBeNull(); expect(actionExecution?.status).toBe("PENDING"); expect(actionExecution?.sourceId).toBe(promptV2?.id); const actionExecution2 = await prisma.automationExecution.findFirst({ where: { projectId: newProjectId, triggerId, actionId, sourceId: promptV1?.id, }, }); expect(actionExecution2).not.toBeNull(); expect(actionExecution2?.status).toBe("PENDING"); expect(actionExecution2?.sourceId).toBe(promptV1?.id); }); }); it("trying to set 'latest' label results in 400 error", async () => { const { projectId: newProjectId, auth: newAuth } = await createOrgProjectAndApiKey(); // Create initial prompt version await prisma.prompt.create({ data: { name: "prompt-1", projectId: newProjectId, version: 1, labels: [], createdBy: "user-test", prompt: "prompt-1", }, }); // Try to set "latest" label const response = await makeAPICall( "PATCH", `${baseURI}/prompt-1/versions/1`, { newLabels: ["latest"], }, newAuth, ); expect(response.status).toBe(400); }); it("updating non existing prompt results in 404", async () => { const { auth: newAuth } = await createOrgProjectAndApiKey(); // Try to update non-existing prompt const response = await makeAPICall( "PATCH", `${baseURI}/non-existing-prompt/versions/1`, { newLabels: ["production"], }, newAuth, ); expect(response.status).toBe(404); }); describe("prompt composability", () => { it("can create a prompt with dependencies linked via label", async () => { const { projectId: newProjectId, auth: newAuth } = await createOrgProjectAndApiKey(); // Create child prompt await makeAPICall( "POST", baseURI, { name: "child-prompt", prompt: "I am a child prompt", type: "text", labels: ["production"], }, newAuth, ); // Create parent prompt with dependency const parentPromptContent = "Parent prompt with dependency: @@@langfusePrompt:name=child-prompt|label=production@@@"; const response = await makeAPICall( "POST", baseURI, { name: "parent-prompt", prompt: parentPromptContent, type: "text", }, newAuth, ); expect(response.status).toBe(201); // Verify dependency was created const dependencies = await prisma.promptDependency.findMany({ where: { projectId: newProjectId, childName: "child-prompt", }, }); expect(dependencies.length).toBe(1); expect(dependencies[0].childLabel).toBe("production"); // Get the resolved prompt const getResponse = await makeAPICall( "GET", `${baseURI}/parent-prompt?version=1`, undefined, newAuth, ); expect(getResponse.status).toBe(200); const responseBody = getResponse.body as unknown as Prompt; const parsedPrompt = responseBody.prompt as string; // Verify the resolution graph is returned with the correct structure const parentId = responseBody.id; expect(responseBody.resolutionGraph).toEqual({ root: { name: "parent-prompt", version: 1, id: parentId, }, dependencies: { [parentId]: [ { name: "child-prompt", version: 1, id: expect.any(String), }, ], }, }); // Verify the dependency was resolved correctly expect(parsedPrompt).toBe( "Parent prompt with dependency: I am a child prompt", ); // Create another version of the child prompt with the same name and production label // This will automatically strip the production label from the previous version await makeAPICall( "POST", baseURI, { name: "child-prompt", prompt: "I am an updated child prompt", type: "text", labels: ["production"], }, newAuth, ); // Get the resolved prompt again to check if it now uses the new version const getResponseAfterUpdate = await makeAPICall( "GET", `${baseURI}/parent-prompt?version=1`, undefined, newAuth, ); expect(getResponseAfterUpdate.status).toBe(200); const responseBodyAfterUpdate = getResponseAfterUpdate.body as unknown as Prompt; const parsedPromptAfterUpdate = responseBodyAfterUpdate.prompt as string; expect(parsedPromptAfterUpdate).toBe( "Parent prompt with dependency: I am an updated child prompt", ); expect(responseBodyAfterUpdate.resolutionGraph).toEqual({ root: { name: "parent-prompt", version: 1, id: parentId, }, dependencies: { [parentId]: [ { name: "child-prompt", version: 2, id: expect.any(String), }, ], }, }); const childPrompts = await prisma.prompt.findMany({ where: { projectId: newProjectId, name: "child-prompt", }, orderBy: { version: "asc", }, }); expect(childPrompts.length).toBe(2); }); it("resolves prompt dependencies correctly when linked via label", async () => { const { auth: newAuth } = await createOrgProjectAndApiKey(); // Create child prompt await makeAPICall( "POST", baseURI, { name: "nested-child", prompt: "I am a nested child", type: "text", labels: ["production"], }, newAuth, ); // Create intermediate prompt with dependency await makeAPICall( "POST", baseURI, { name: "intermediate", prompt: "Intermediate with dependency: @@@langfusePrompt:name=nested-child|label=production@@@", type: "text", labels: ["production"], }, newAuth, ); // Create parent prompt with dependency await makeAPICall( "POST", baseURI, { name: "nested-parent", prompt: "Parent with nested dependency: @@@langfusePrompt:name=intermediate|label=production@@@", type: "text", }, newAuth, ); // Get the resolved prompt const response = await makeAPICall( "GET", `${baseURI}/nested-parent?version=1`, undefined, newAuth, ); expect(response.status).toBe(200); const responseBody = response.body as unknown as Prompt; expect(responseBody.prompt).toBe( "Parent with nested dependency: Intermediate with dependency: I am a nested child", ); }); it("resolves prompt dependencies correctly when linked via version", async () => { const { auth: newAuth } = await createOrgProjectAndApiKey(); // Create child prompt with version await makeAPICall( "POST", baseURI, { name: "version-linked-child", prompt: "I am version 3 of the child prompt", type: "text", version: 1, labels: ["production"], }, newAuth, ); // Create parent prompt with version-specific dependency const parentPromptContent = "Parent with version dependency: @@@langfusePrompt:name=version-linked-child|version=1@@@"; await makeAPICall( "POST", baseURI, { name: "version-linked-parent", prompt: parentPromptContent, type: "text", }, newAuth, ); // Get the resolved prompt const response = await makeAPICall( "GET", `${baseURI}/version-linked-parent?version=1`, undefined, newAuth, ); expect(response.status).toBe(200); const responseBody = response.body as unknown as Prompt; expect(responseBody.prompt).toBe( "Parent with version dependency: I am version 3 of the child prompt", ); expect(responseBody.prompt).not.toContain("@@@langfusePrompt"); }); it("detects circular dependencies", async () => { const { auth: newAuth } = await createOrgProjectAndApiKey(); // Create first prompt await makeAPICall( "POST", baseURI, { name: "circular-a", prompt: "Prompt A without dependency", type: "text", labels: ["production"], }, newAuth, ); // Create second prompt that depends on the first - this should be rejected due to circular dependency await makeAPICall( "POST", baseURI, { name: "circular-b", prompt: "Prompt B with dependency: @@@langfusePrompt:name=circular-a|label=production@@@", type: "text", labels: ["production"], }, newAuth, ); const circularResponse = await makeAPICall( "POST", baseURI, { name: "circular-a", prompt: "Prompt A with dependency: @@@langfusePrompt:name=circular-b|label=production@@@", type: "text", labels: ["production"], }, newAuth, ); // The second call should be rejected with a 400 error expect(circularResponse.status).toBe(400); const circularResponseBody = circularResponse.body as unknown as { error: string; }; expect(JSON.stringify(circularResponseBody)).toContain("circular"); }); it("handles deeply nested dependencies (3+ levels)", async () => { //const { auth: newAuth } = await createOrgProjectAndApiKey(); const newAuth = undefined; // Create level 3 (deepest) prompts await makeAPICall( "POST", baseURI, { name: "level-3-a", prompt: "I am level 3 prompt A", type: "text", labels: ["production"], }, newAuth, ); await makeAPICall( "POST", baseURI, { name: "level-3-b", prompt: "I am level 3 prompt B", type: "text", labels: ["production"], }, newAuth, ); // Create level 2 prompt that depends on level 3 prompts await makeAPICall( "POST", baseURI, { name: "level-2", prompt: "Level 2 with dependencies: @@@langfusePrompt:name=level-3-a|label=production@@@ and @@@langfusePrompt:name=level-3-b|label=production@@@", type: "text", labels: ["production"], }, newAuth, ); // Create level 1 prompt that depends on level 2 await makeAPICall( "POST", baseURI, { name: "level-1", prompt: "Level 1 with dependency: @@@langfusePrompt:name=level-2|label=production@@@", type: "text", }, newAuth, ); // Get the resolved prompt const response = await makeAPICall( "GET", `${baseURI}/level-1?version=1`, undefined, newAuth, ); expect(response.status).toBe(200); const responseBody = response.body as unknown as Prompt; expect(responseBody.prompt).toBe( "Level 1 with dependency: Level 2 with dependencies: I am level 3 prompt A and I am level 3 prompt B", ); expect(responseBody.prompt).not.toContain("@@@langfusePrompt"); }); it("handles multiple dependencies at the same level", async () => { const { auth: newAuth } = await createOrgProjectAndApiKey(); // Create multiple child prompts await makeAPICall( "POST", baseURI, { name: "child-a", prompt: "I am child prompt A", type: "text", labels: ["production"], }, newAuth, ); await makeAPICall( "POST", baseURI, { name: "child-b", prompt: "I am child prompt B", type: "text", labels: ["production"], }, newAuth, ); await makeAPICall( "POST", baseURI, { name: "child-c", prompt: "I am child prompt C", type: "text", labels: ["production"], }, newAuth, ); // Create parent prompt with multiple dependencies const parentPromptContent = `Parent prompt with multiple dependencies: First: @@@langfusePrompt:name=child-a|label=production@@@ Second: @@@langfusePrompt:name=child-b|label=production@@@ Third: @@@langfusePrompt:name=child-c|label=production@@@`; await makeAPICall( "POST", baseURI, { name: "multi-parent", prompt: parentPromptContent, type: "text", }, newAuth, ); // Get the resolved prompt const response = await makeAPICall( "GET", `${baseURI}/multi-parent?version=1`, undefined, newAuth, ); expect(response.status).toBe(200); const responseBody = response.body as unknown as Prompt; expect(responseBody.prompt).toBe( "Parent prompt with multiple dependencies: First: I am child prompt A Second: I am child prompt B Third: I am child prompt C", ); expect(responseBody.prompt).not.toContain("@@@langfusePrompt"); }); it("handles version-specific dependencies", async () => { const { auth: newAuth } = await createOrgProjectAndApiKey(); // Create child prompt with multiple versions await makeAPICall( "POST", baseURI, { name: "versioned-child", prompt: "I am version 1", type: "text", labels: ["production"], }, newAuth, ); await makeAPICall( "POST", baseURI, { name: "versioned-child", prompt: "I am version 2", type: "text", version: 2, labels: ["production"], }, newAuth, ); // Create parent prompt with version-specific dependency const parentPromptContent = "Parent with version dependency: @@@langfusePrompt:name=versioned-child|version=1@@@"; await makeAPICall( "POST", baseURI, { name: "version-parent", prompt: parentPromptContent, type: "text", }, newAuth, ); // Get the resolved prompt const response = await makeAPICall( "GET", `${baseURI}/version-parent?version=1`, undefined, newAuth, ); expect(response.status).toBe(200); const responseBody = response.body as unknown as Prompt; expect(responseBody.prompt).toBe( "Parent with version dependency: I am version 1", ); expect(responseBody.prompt).not.toContain("@@@langfusePrompt"); }); it("resolves prompt dependencies in chat prompts correctly", async () => { const { auth: newAuth } = await createOrgProjectAndApiKey(); // Create child text prompt await makeAPICall( "POST", baseURI, { name: "chat-child-text", prompt: "I am a text prompt used in a chat prompt", type: "text", labels: ["production"], }, newAuth, ); // Create parent chat prompt with dependency const chatPromptContent = [ { role: "system", content: "You are a helpful assistant" }, { role: "user", content: "Here's some context: @@@langfusePrompt:name=chat-child-text|label=production@@@", }, ]; await makeAPICall( "POST", baseURI, { name: "chat-parent", prompt: chatPromptContent, type: "chat", }, newAuth, ); // Get the resolved prompt const response = await makeAPICall( "GET", `${baseURI}/chat-parent?version=1`, undefined, newAuth, ); expect(response.status).toBe(200); const responseBody = response.body as unknown as Prompt; expect(responseBody.prompt).toEqual([ { role: "system", content: "You are a helpful assistant" }, { role: "user", content: "Here's some context: I am a text prompt used in a chat prompt", }, ]); expect(JSON.stringify(responseBody.prompt)).not.toContain( "@@@langfusePrompt", ); }); it("supports nested dependencies in chat prompts", async () => { const { auth: newAuth } = await createOrgProjectAndApiKey(); // Create nested child text prompt await makeAPICall( "POST", baseURI, { name: "nested-child", prompt: "I am a nested child prompt", type: "text", labels: ["production"], }, newAuth, ); // Create intermediate text prompt with dependency const intermediatePromptContent = "Intermediate prompt with dependency: @@@langfusePrompt:name=nested-child|label=production@@@"; await makeAPICall( "POST", baseURI, { name: "intermediate-prompt", prompt: intermediatePromptContent, type: "text", labels: ["production"], }, newAuth, ); // Create parent chat prompt with dependency to intermediate const chatPromptContent = [ { role: "system", content: "You are a helpful assistant" }, { role: "user", content: "Here's some context: @@@langfusePrompt:name=intermediate-prompt|label=production@@@", }, ]; await makeAPICall( "POST", baseURI, { name: "nested-chat-parent", prompt: chatPromptContent, type: "chat", }, newAuth, ); // Get the resolved prompt const response = await makeAPICall( "GET", `${baseURI}/nested-chat-parent?version=1`, undefined, newAuth, ); expect(response.status).toBe(200); const responseBody = response.body as unknown as Prompt; const parsedPrompt = responseBody.prompt; expect(parsedPrompt).toEqual([ { role: "system", content: "You are a helpful assistant" }, { role: "user", content: "Here's some context: Intermediate prompt with dependency: I am a nested child prompt", }, ]); expect(JSON.stringify(parsedPrompt)).not.toContain("@@@langfusePrompt"); }); it("should ignore invalid prompt tags", async () => { const { auth: newAuth } = await createOrgProjectAndApiKey(); // Create a prompt with invalid dependency tags const invalidTagPromptContent = JSON.stringify({ text: "This prompt has invalid tags: @@@langfusePrompt:invalid@@@, @@@langfusePrompt:name=missing-type@@@, @@@langfusePrompt:name=no-version-or-label@@@", }); await makeAPICall( "POST", baseURI, { name: "prompt-with-invalid-tags", prompt: invalidTagPromptContent, type: "text", }, newAuth, ); // Get the resolved prompt const response = await makeAPICall( "GET", `${baseURI}/prompt-with-invalid-tags?version=1`, undefined, newAuth, ); expect(response.status).toBe(200); const responseBody = response.body as unknown as Prompt; const parsedPrompt = JSON.parse(responseBody.prompt as string); // The invalid tags should remain unchanged in the resolved prompt expect(parsedPrompt.text).toContain("@@@langfusePrompt:invalid@@@"); expect(parsedPrompt.text).toContain( "@@@langfusePrompt:name=missing-type@@@", ); expect(parsedPrompt.text).toContain( "@@@langfusePrompt:name=no-version-or-label@@@", ); }); it("should handle duplicate dependency tags for the same prompt", async () => { const { auth: newAuth } = await createOrgProjectAndApiKey(); // Create a parent prompt await makeAPICall( "POST", baseURI, { name: "parent-prompt", prompt: "I am a parent prompt", type: "text", }, newAuth, ); // Create a prompt with duplicate dependency tags const duplicateTagsPromptContent = "This prompt has duplicate tags: @@@langfusePrompt:name=parent-prompt|version=1@@@ and again @@@langfusePrompt:name=parent-prompt|version=1@@@"; await makeAPICall( "POST", baseURI, { name: "prompt-with-duplicate-tags", prompt: duplicateTagsPromptContent, type: "text", }, newAuth, ); // Get the resolved prompt const response = await makeAPICall( "GET", `${baseURI}/prompt-with-duplicate-tags?version=1`, undefined, newAuth, ); expect(response.status).toBe(200); const responseBody = response.body as unknown as Prompt; const parsedPrompt = responseBody.prompt as string; // The duplicate tags should be resolved to the same prompt expect(parsedPrompt).toBe( "This prompt has duplicate tags: I am a parent prompt and again I am a parent prompt", ); }); it("should handle special characters in prompt names", async () => { const { auth: newAuth } = await createOrgProjectAndApiKey(); // Create prompts with special characters in names const specialCharPrompts = [ { name: "prompt-with-hyphens", prompt: "Hyphen content", type: "text" }, { name: "prompt_with_underscores", prompt: "Underscore content", type: "text", }, { name: "prompt.with.dots", prompt: "Dot content", type: "text" }, { name: "prompt123WithNumbers", prompt: "Number content", type: "text", }, { name: "prompt with spaces", prompt: "Space content", type: "text" }, { name: "prompt/with/slashes", prompt: "Slash content", type: "text" }, ]; // Create all the special character prompts for (const promptData of specialCharPrompts) { const response = await makeAPICall( "POST", baseURI, promptData, newAuth, ); expect(response.status).toBe(201); } // Create a prompt that references all the special character prompts const dependencyContent = ` Reference prompts with special chars: @@@langfusePrompt:name=prompt-with-hyphens|version=1@@@ @@@langfusePrompt:name=prompt_with_underscores|version=1@@@ @@@langfusePrompt:name=prompt.with.dots|version=1@@@ @@@langfusePrompt:name=prompt123WithNumbers|version=1@@@ @@@langfusePrompt:name=prompt with spaces|version=1@@@ @@@langfusePrompt:name=prompt/with/slashes|version=1@@@ `; await makeAPICall( "POST", baseURI, { name: "prompt-with-special-char-dependencies", prompt: dependencyContent, type: "text", }, newAuth, ); // Get the resolved prompt const response = await makeAPICall( "GET", `${baseURI}/prompt-with-special-char-dependencies?version=1`, undefined, newAuth, ); expect(response.status).toBe(200); const responseBody = response.body as unknown as Prompt; const parsedPrompt = responseBody.prompt as string; // Verify the exact resolved prompt string const expectedPrompt = ` Reference prompts with special chars: Hyphen content Underscore content Dot content Number content Space content Slash content `; expect(parsedPrompt).toBe(expectedPrompt); }, 10_000); it("should disallow a prompt that references itself", async () => { const { auth } = await createOrgProjectAndApiKey(); // First, create the prompt without self-reference const initialPromptName = "self-referencing-prompt"; const initialPromptContent = "This is the initial content"; const createResponse = await makeAPICall( "POST", baseURI, { name: initialPromptName, prompt: initialPromptContent, type: "text", }, auth, ); expect(createResponse.status).toBe(201); // Now try to update the prompt to include a reference to itself const selfReferenceContent = `This prompt references itself: @@@langfusePrompt:name=self-referencing-prompt|version=1@@@ And that's it!`; const updateResponse = await makeAPICall( "POST", baseURI, { name: initialPromptName, prompt: selfReferenceContent, type: "text", }, auth, ); // Expect the request to fail with a 400 Bad Request expect(updateResponse.status).toBe(400); // Check that the response contains an error about circular dependency expect(JSON.stringify(updateResponse.body)).toContain( "Circular dependency", ); }); it("should return an error when a dependent prompt doesn't exist", async () => { const { auth: newAuth } = await createOrgProjectAndApiKey(); // Create a prompt that references a non-existent prompt const promptWithNonExistentDependency = `This prompt references a non-existent prompt: @@@langfusePrompt:name=non-existent-prompt|version=1@@@ End of prompt.`; // Create the prompt with the non-existent dependency const createResponse = await makeAPICall( "POST", baseURI, { name: "prompt-with-missing-dependency", prompt: promptWithNonExistentDependency, type: "text", }, newAuth, ); // The creation should succeed as dependencies are resolved at retrieval time expect(createResponse.status).toBe(400); expect(JSON.stringify(createResponse.body)).toContain("not found"); }); it("should return an error when a dependent prompt exists but in a different project", async () => { // Create two different organizations with their own projects await createOrgProjectAndApiKey(); const { auth: auth2 } = await createOrgProjectAndApiKey(); // Create a prompt in the first project const foreignProjectId = randomUUID(); await prisma.project.upsert({ where: { id: foreignProjectId }, update: { name: "test-foreign-llm-app", orgId: "seed-org-id", }, create: { id: foreignProjectId, name: "test-foreign-llm-app", orgId: "seed-org-id", }, }); await prisma.prompt.create({ data: { name: "cross-project-prompt", prompt: "This prompt belongs to project 1", type: "TEXT", version: 1, projectId: foreignProjectId, createdBy: "test-user", }, }); // Create a prompt in the second project that references the prompt from the first project const promptWithCrossProjectDependency = `This prompt references a prompt from another project: @@@langfusePrompt:name=cross-project-prompt|version=1@@@ End of prompt.`; // Create the prompt with the cross-project dependency const createResponse = await makeAPICall( "POST", baseURI, { name: "prompt-with-cross-project-dependency", prompt: promptWithCrossProjectDependency, type: "text", }, auth2, ); expect(createResponse.status).toBe(400); expect(JSON.stringify(createResponse.body)).toContain("not found"); await prisma.project.delete({ where: { id: foreignProjectId }, }); }); it("should throw an error if MAX_PROMPT_NESTING_DEPTH is exceeded", async () => { const { auth: newAuth } = await createOrgProjectAndApiKey(); // Create prompts with increasing depth for (let i = MAX_PROMPT_NESTING_DEPTH; i >= 1; i--) { const promptName = `depth-${i}`; let promptContent = ""; if (i === MAX_PROMPT_NESTING_DEPTH) { // The deepest prompt has no dependencies promptContent = "I am the deepest prompt"; } else { // Each prompt depends on the one deeper than it promptContent = `Level ${i} with dependency: @@@langfusePrompt:name=depth-${i + 1}|label=production@@@`; } const response = await makeAPICall( "POST", baseURI, { name: promptName, prompt: promptContent, type: "text", labels: ["production"], }, newAuth, ); expect(response.status).toBe(201); } // Try to create the depth-0 prompt which would exceed the max nesting depth const promptName = "depth-0"; const promptContent = `Level 0 with dependency: @@@langfusePrompt:name=depth-1|label=production@@@`; const createResponse = await makeAPICall( "POST", baseURI, { name: promptName, prompt: promptContent, type: "text", labels: ["production"], }, newAuth, ); // Expect an error response expect(createResponse.status).toBe(400); expect(JSON.stringify(createResponse.body)).toContain( "Maximum nesting depth exceeded", ); }, 10_000); }); describe("Parsing prompt dependency tags", () => { it("should extract prompt dependency tags with version", () => { const content = "This is a test with @@@langfusePrompt:name=test|version=1@@@ dependency"; const result = parsePromptDependencyTags(content); expect(result).toHaveLength(1); expect(result[0]).toEqual({ name: "test", version: 1, type: "version", }); }); it("should handle prompt names with special characters", () => { const content = ` @@@langfusePrompt:name=test-with-hyphens|version=1@@@ @@@langfusePrompt:name=test with spaces|label=production@@@ @@@langfusePrompt:name=test_with_underscores|version=2@@@ @@@langfusePrompt:name=test.with.dots|label=staging@@@ @@@langfusePrompt:name=test123WithNumbers|version=3@@@ `; const result = parsePromptDependencyTags(content); expect(result).toHaveLength(5); expect(result[0]).toEqual({ name: "test-with-hyphens", version: 1, type: "version", }); expect(result[1]).toEqual({ name: "test with spaces", label: "production", type: "label", }); expect(result[2]).toEqual({ name: "test_with_underscores", version: 2, type: "version", }); expect(result[3]).toEqual({ name: "test.with.dots", label: "staging", type: "label", }); expect(result[4]).toEqual({ name: "test123WithNumbers", version: 3, type: "version", }); }); it("should extract prompt dependency tags with label", () => { const content = "This is a test with @@@langfusePrompt:name=test|label=production@@@ dependency"; const result = parsePromptDependencyTags(content); expect(result).toHaveLength(1); expect(result[0]).toEqual({ name: "test", label: "production", type: "label", }); }); it("should extract multiple prompt dependency tags", () => { const content = ` First dependency: @@@langfusePrompt:name=first|version=1@@@ Second dependency: @@@langfusePrompt:name=second|label=staging@@@ `; const result = parsePromptDependencyTags(content); expect(result).toHaveLength(2); expect(result[0]).toEqual({ name: "first", version: 1, type: "version", }); expect(result[1]).toEqual({ name: "second", label: "staging", type: "label", }); }); it("should ignore invalid prompt dependency tags", () => { const content = ` Valid: @@@langfusePrompt:name=valid|version=1@@@ Invalid: @@@langfusePrompt:version=1@@@ Also invalid: @@@langfusePrompt:name=invalid|something=else@@@ `; const result = parsePromptDependencyTags(content); expect(result).toHaveLength(1); expect(result[0]).toEqual({ name: "valid", version: 1, type: "version", }); }); it("should return empty array when no tags are found", () => { const content = "This is a test with no dependency tags"; const result = parsePromptDependencyTags(content); expect(result).toEqual([]); }); it("should handle tags with special characters in name", () => { const content = "Tag with special chars @@@langfusePrompt:name=test-prompt_123|version=2@@@"; const result = parsePromptDependencyTags(content); expect(result).toHaveLength(1); expect(result[0]).toEqual({ name: "test-prompt_123", version: 2, type: "version", }); }); it("should handle tags with special characters in label", () => { const content = "Tag with special chars @@@langfusePrompt:name=test|label=prod-v1.0_beta@@@"; const result = parsePromptDependencyTags(content); expect(result).toHaveLength(1); expect(result[0]).toEqual({ name: "test", label: "prod-v1.0_beta", type: "label", }); }); it("should correctly coerce version to number", () => { const content = "Version as string @@@langfusePrompt:name=test|version=123@@@"; const result = parsePromptDependencyTags(content); expect(result).toHaveLength(1); expect(result[0]).toEqual({ name: "test", version: 123, type: "version", }); expect(typeof (result[0] as any).version).toBe("number"); }); it("should handle tags with spaces in the content", () => { const content = "Tag with spaces @@@langfusePrompt:name=my prompt|label=production label@@@"; const result = parsePromptDependencyTags(content); expect(result).toHaveLength(1); expect(result[0]).toEqual({ name: "my prompt", label: "production label", type: "label", }); }); it("should handle multiple tags with the same name but different versions/labels", () => { const content = ` @@@langfusePrompt:name=same|version=1@@@ @@@langfusePrompt:name=same|version=2@@@ @@@langfusePrompt:name=same|label=production@@@ `; const result = parsePromptDependencyTags(content); expect(result).toHaveLength(3); expect(result[0]).toEqual({ name: "same", version: 1, type: "version" }); expect(result[1]).toEqual({ name: "same", version: 2, type: "version" }); expect(result[2]).toEqual({ name: "same", label: "production", type: "label", }); }); it("should handle tags with the PRODUCTION_LABEL constant value", () => { const content = "@@@langfusePrompt:name=test|label=production@@@"; const result = parsePromptDependencyTags(content); expect(result).toHaveLength(1); expect(result[0]).toEqual({ name: "test", label: "production", type: "label", }); }); it("should ignore malformed tags that don't match the regex pattern", () => { const content = ` Valid: @@@langfusePrompt:name=valid|version=1@@@ Malformed: @@langfusePrompt:name=test|version=1@@ Also malformed: @@@langfusePrompt:name=test|version=1 And: langfusePrompt:name=test|version=1@@@ `; const result = parsePromptDependencyTags(content); expect(result).toHaveLength(1); expect(result[0]).toEqual({ name: "valid", version: 1, type: "version", }); }); it("should not parse langfuseMedia tags as prompt dependency tags", () => { const content = ` @@@langfusePrompt:name=valid|version=1@@@ @@@langfuseMedia:type=image/jpeg|id=cc48838a-3da8-4ca4-a007-2cf8df930e69|source=base64@@@ @@@langfusePrompt:name=another|label=production@@@ `; const result = parsePromptDependencyTags(content); expect(result).toHaveLength(2); expect(result[0]).toEqual({ name: "valid", version: 1, type: "version", }); expect(result[1]).toEqual({ name: "another", label: "production", type: "label", }); }); it("should reject tags where name is not the first parameter", () => { const content = ` Valid: @@@langfusePrompt:name=valid|version=1@@@ Invalid: @@@langfusePrompt:version=1|name=test@@@ `; const result = parsePromptDependencyTags(content); expect(result).toHaveLength(1); expect(result[0]).toEqual({ name: "valid", version: 1, type: "version", }); }); }); }); const isPrompt = (x: unknown): x is Prompt => { if (typeof x !== "object" || x === null) return false; const prompt = x as Prompt; return ( typeof prompt.id === "string" && typeof prompt.name === "string" && typeof prompt.version === "number" && typeof prompt.prompt === "string" && Array.isArray(prompt.labels) && Array.isArray(prompt.tags) && typeof prompt.projectId === "string" && typeof prompt.createdBy === "string" && typeof prompt.config === "object" && Object.values(PromptType).includes(prompt.type as PromptType) ); }; const validatePrompt = (obj: Record): ValidatedPrompt => { Object.keys(obj).forEach((key) => { obj[key] = key === "createdAt" || key === "updatedAt" ? new Date(obj[key] as string) : obj[key]; }); return PromptSchema.parse(obj); }; const getMockPrompts = (projectId: string, otherProjectId: string) => [ // Prompt with multiple versions { name: "prompt-1", labels: ["production"], prompt: "prompt-1", createdBy: "user-test", projectId, config: { version: 1 }, version: 1, updatedAt: new Date("2024-01-01T00:00:00.000Z"), }, { name: "prompt-1", labels: ["production", "version2"], prompt: "prompt-1", createdBy: "user-test", projectId, config: { version: 2 }, version: 2, updatedAt: new Date("2024-01-02T00:00:00.000Z"), }, { name: "prompt-1", labels: ["production"], prompt: "prompt-1", createdBy: "user-test", projectId, config: { version: 4 }, version: 4, updatedAt: new Date("2024-01-04T00:00:00.000Z"), }, // Prompt with different labels { name: "prompt-2", labels: ["production"], prompt: "prompt-2", createdBy: "user-test", projectId, config: {}, version: 1, updatedAt: new Date("2000-01-01T00:00:00.000Z"), }, { name: "prompt-2", labels: ["staging"], prompt: "prompt-2", createdBy: "user-test", projectId, config: {}, version: 2, updatedAt: new Date("2000-03-01T00:00:00.000Z"), }, { name: "prompt-2", labels: ["dev"], prompt: "prompt-2", createdBy: "user-test", projectId, config: {}, version: 3, updatedAt: new Date("2000-02-01T00:00:00.000Z"), }, // Prompt with different labels { name: "prompt-3", labels: ["production"], prompt: "prompt-2", createdBy: "user-test", projectId, config: {}, tags: ["tag-1"], version: 1, updatedAt: new Date("2000-01-01T00:00:00.000Z"), }, // Prompt in different project { name: "prompt-4", labels: ["production"], prompt: "prompt-2", createdBy: "user-test", projectId: otherProjectId, config: {}, version: 1, updatedAt: new Date("2000-01-01T00:00:00.000Z"), }, ];