diff --git a/ui/litellm-dashboard/src/components/prompts/prompt_editor_view/utils.test.ts b/ui/litellm-dashboard/src/components/prompts/prompt_editor_view/utils.test.ts index 59fcaccb639..2ecaf864049 100644 --- a/ui/litellm-dashboard/src/components/prompts/prompt_editor_view/utils.test.ts +++ b/ui/litellm-dashboard/src/components/prompts/prompt_editor_view/utils.test.ts @@ -339,7 +339,7 @@ User: Hello`, }, }; - expect(() => parseExistingPrompt(apiResponse)).toThrow("No dotprompt_content found in API response"); + expect(() => parseExistingPrompt(apiResponse)).toThrow("No dotprompt_content or prompt_data found in API response"); }); it("should throw error for invalid dotprompt format", () => { @@ -377,6 +377,143 @@ output: { role: "user", content: "Enter task specifics. Use {{template_variables}} for dynamic inputs" }, ]); }); + + // Tests for prompt_data fallback + it("should parse prompt_data with direct format", () => { + const apiResponse = { + prompt_spec: { + litellm_params: { + prompt_data: { + content: "User: Hello from prompt_data", + metadata: { + model: "gpt-4", + temperature: 0.8, + }, + }, + }, + prompt_id: "test-prompt", + }, + }; + + const result = parseExistingPrompt(apiResponse); + expect(result.name).toBe("test-prompt"); + expect(result.model).toBe("gpt-4"); + expect(result.config.temperature).toBe(0.8); + expect(result.messages).toEqual([{ role: "user", content: "Hello from prompt_data" }]); + }); + + it("should parse prompt_data with nested format", () => { + const apiResponse = { + prompt_spec: { + litellm_params: { + prompt_data: { + "my-prompt": { + content: "User: Hello from nested prompt_data", + metadata: { + model: "gpt-3.5-turbo", + max_tokens: 500, + }, + }, + }, + }, + prompt_id: "my-prompt", + }, + }; + + const result = parseExistingPrompt(apiResponse); + expect(result.name).toBe("my-prompt"); + expect(result.model).toBe("gpt-3.5-turbo"); + expect(result.config.max_tokens).toBe(500); + expect(result.messages).toEqual([{ role: "user", content: "Hello from nested prompt_data" }]); + }); + + it("should prefer dotprompt_content over prompt_data", () => { + const apiResponse = { + prompt_spec: { + litellm_params: { + dotprompt_content: `--- +model: gpt-4 +input: + schema: +output: + format: text +--- + +User: Hello from dotprompt`, + prompt_data: { + content: "User: Hello from prompt_data", + metadata: { model: "gpt-3.5-turbo" }, + }, + }, + prompt_id: "test-prompt", + }, + }; + + const result = parseExistingPrompt(apiResponse); + // Should use dotprompt_content, not prompt_data + expect(result.model).toBe("gpt-4"); + expect(result.messages).toEqual([{ role: "user", content: "Hello from dotprompt" }]); + }); + + it("should handle prompt_data with developer message", () => { + const apiResponse = { + prompt_spec: { + litellm_params: { + prompt_data: { + content: "Developer: You are a helpful bot\n\nUser: Hello", + metadata: { model: "gpt-4" }, + }, + }, + prompt_id: "test-prompt", + }, + }; + + const result = parseExistingPrompt(apiResponse); + expect(result.developerMessage).toBe("You are a helpful bot"); + expect(result.messages).toEqual([{ role: "user", content: "Hello" }]); + }); + + it("should handle prompt_data with tools", () => { + const apiResponse = { + prompt_spec: { + litellm_params: { + prompt_data: { + content: "User: What's the weather?", + metadata: { + model: "gpt-4", + tools: [{ type: "function", function: { name: "get_weather", description: "Get weather info" } }], + }, + }, + }, + prompt_id: "test-prompt", + }, + }; + + const result = parseExistingPrompt(apiResponse); + expect(result.tools).toHaveLength(1); + expect(result.tools[0].name).toBe("get_weather"); + expect(result.tools[0].description).toBe("Get weather info"); + }); + + it("should use default values for empty prompt_data", () => { + const apiResponse = { + prompt_spec: { + litellm_params: { + prompt_data: { + content: "", + metadata: {}, + }, + }, + prompt_id: "test-prompt", + }, + }; + + const result = parseExistingPrompt(apiResponse); + expect(result.model).toBe("gpt-4o"); + expect(result.messages).toEqual([ + { role: "user", content: "Enter task specifics. Use {{template_variables}} for dynamic inputs" }, + ]); + }); }); describe("getVersionNumber", () => { diff --git a/ui/litellm-dashboard/src/components/prompts/prompt_editor_view/utils.ts b/ui/litellm-dashboard/src/components/prompts/prompt_editor_view/utils.ts index 523470e34d0..16467729567 100644 --- a/ui/litellm-dashboard/src/components/prompts/prompt_editor_view/utils.ts +++ b/ui/litellm-dashboard/src/components/prompts/prompt_editor_view/utils.ts @@ -216,10 +216,21 @@ export const parseExistingPrompt = (apiResponse: any): PromptType => { // Extract dotprompt_content from litellm_params const dotpromptContent = apiResponse?.prompt_spec?.litellm_params?.dotprompt_content || ""; - if (!dotpromptContent) { - throw new Error("No dotprompt_content found in API response"); + // If dotprompt_content is available, use it + if (dotpromptContent) { + return parseDotpromptContent(dotpromptContent, apiResponse); + } + + // Fallback: try to construct from prompt_data + const promptData = apiResponse?.prompt_spec?.litellm_params?.prompt_data; + if (promptData) { + return parsePromptData(promptData, apiResponse); } + throw new Error("No dotprompt_content or prompt_data found in API response"); +}; + +const parseDotpromptContent = (dotpromptContent: string, apiResponse: any): PromptType => { // Split into frontmatter and content const parts = dotpromptContent.split("---"); if (parts.length < 3) { @@ -249,6 +260,71 @@ export const parseExistingPrompt = (apiResponse: any): PromptType => { }; }; +const parsePromptData = (promptData: any, apiResponse: any): PromptType => { + // Handle nested structure: { prompt_id: { content, metadata } } or { content, metadata } + let content: string; + let metadata: any; + + if (promptData.content !== undefined) { + // Direct format: { content, metadata } + content = promptData.content || ""; + metadata = promptData.metadata || {}; + } else { + // Nested format: { prompt_id: { content, metadata } } + const keys = Object.keys(promptData); + if (keys.length > 0) { + const firstKey = keys[0]; + const nestedData = promptData[firstKey]; + content = nestedData?.content || ""; + metadata = nestedData?.metadata || {}; + } else { + content = ""; + metadata = {}; + } + } + + // Strip version suffix from prompt name for display + const promptId = apiResponse?.prompt_spec?.prompt_id || "Unnamed Prompt"; + const baseName = stripVersionFromPromptId(promptId) || promptId; + + // Extract model from metadata + const model = metadata?.model || "gpt-4o"; + + // Extract config parameters from metadata + const config: { temperature?: number; max_tokens?: number; top_p?: number } = {}; + if (metadata?.temperature !== undefined) config.temperature = metadata.temperature; + if (metadata?.max_tokens !== undefined) config.max_tokens = metadata.max_tokens; + if (metadata?.top_p !== undefined) config.top_p = metadata.top_p; + + // Parse the content to extract messages + // Try to parse as role-prefixed format first, then fall back to simple content + const parsedBody = parseDotpromptBody(content); + + // Extract tools from metadata + const tools: Tool[] = []; + if (metadata?.tools && Array.isArray(metadata.tools)) { + metadata.tools.forEach((tool: any) => { + tools.push({ + name: tool?.function?.name || "Unnamed Tool", + description: tool?.function?.description || "", + json: JSON.stringify(tool, null, 2), + }); + }); + } + + return { + name: baseName, + model, + config, + tools, + developerMessage: parsedBody.developerMessage, + messages: + parsedBody.messages.length > 0 + ? parsedBody.messages + : [{ role: "user", content: content || "Enter task specifics. Use {{template_variables}} for dynamic inputs" }], + }; +}; + export const getVersionNumber = (promptId?: string): string => { if (!promptId) return "1"; // Match version with dot (.v), underscore (_v), or hyphen (-v) separator