mirror of
https://github.com/tiennm99/litellm.git
synced 2026-08-13 14:23:04 +00:00
Test Key UI Embeddings (#16065)
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
import { render } from "@testing-library/react";
|
||||
import { describe, it, expect } from "vitest";
|
||||
import ChatUI from "./ChatUI";
|
||||
|
||||
describe("ChatUI", () => {
|
||||
it("should render the chat UI", () => {
|
||||
const { getByText } = render(
|
||||
<ChatUI
|
||||
accessToken="1234567890"
|
||||
token="1234567890"
|
||||
userRole="user"
|
||||
userID="1234567890"
|
||||
disabledPersonalKeyCreation={false}
|
||||
/>,
|
||||
);
|
||||
expect(getByText("Test Key")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -1,12 +1,6 @@
|
||||
import React, { useState, useEffect, useRef } from "react";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import {
|
||||
Card,
|
||||
Title,
|
||||
Text,
|
||||
TextInput,
|
||||
Button as TremorButton,
|
||||
} from "@tremor/react";
|
||||
import { Card, Title, Text, TextInput, Button as TremorButton } from "@tremor/react";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
|
||||
import { Select, Spin, Typography, Tooltip, Input, Upload, Modal, Button } from "antd";
|
||||
@@ -57,6 +51,8 @@ import {
|
||||
ArrowUpOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import NotificationsManager from "../molecules/notifications_manager";
|
||||
import { makeOpenAIEmbeddingsRequest } from "./llm_calls/embeddings_api";
|
||||
import { truncateString } from "./chatUtils";
|
||||
|
||||
const { TextArea } = Input;
|
||||
const { Dragger } = Upload;
|
||||
@@ -497,6 +493,13 @@ const ChatUI: React.FC<ChatUIProps> = ({ accessToken, token, userRole, userID, d
|
||||
setChatHistory((prevHistory) => [...prevHistory, { role: "assistant", content: imageUrl, model, isImage: true }]);
|
||||
};
|
||||
|
||||
const updateEmbeddingsUI = (embeddings: string, model?: string) => {
|
||||
setChatHistory((prevHistory) => [
|
||||
...prevHistory,
|
||||
{ role: "assistant", content: truncateString(embeddings, 100), model, isEmbeddings: true },
|
||||
]);
|
||||
};
|
||||
|
||||
const updateChatImageUI = (imageUrl: string, model?: string) => {
|
||||
setChatHistory((prev) => {
|
||||
const last = prev[prev.length - 1];
|
||||
@@ -786,6 +789,14 @@ const ChatUI: React.FC<ChatUIProps> = ({ accessToken, token, userRole, userID, d
|
||||
selectedGuardrails.length > 0 ? selectedGuardrails : undefined,
|
||||
selectedMCPTools, // Pass the selected tools array
|
||||
);
|
||||
} else if (endpointType === EndpointType.EMBEDDINGS) {
|
||||
await makeOpenAIEmbeddingsRequest(
|
||||
inputMessage,
|
||||
(embeddings, model) => updateEmbeddingsUI(embeddings, model),
|
||||
selectedModel,
|
||||
effectiveApiKey,
|
||||
selectedTags,
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -1413,6 +1424,7 @@ const ChatUI: React.FC<ChatUIProps> = ({ accessToken, token, userRole, userID, d
|
||||
onKeyDown={handleKeyDown}
|
||||
placeholder={
|
||||
endpointType === EndpointType.CHAT ||
|
||||
endpointType === EndpointType.EMBEDDINGS ||
|
||||
endpointType === EndpointType.RESPONSES ||
|
||||
endpointType === EndpointType.ANTHROPIC_MESSAGES
|
||||
? "Type your message... (Shift+Enter for new line)"
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { generateCodeSnippet } from "./CodeSnippets";
|
||||
import { EndpointType } from "./mode_endpoint_mapping";
|
||||
|
||||
describe("CodeSnippets", () => {
|
||||
it("should generate the correct code snippet for embeddings", () => {
|
||||
const code = generateCodeSnippet({
|
||||
endpointType: EndpointType.EMBEDDINGS,
|
||||
inputMessage: "Hello, world!",
|
||||
selectedModel: "text-embedding-3-small",
|
||||
apiKeySource: "session",
|
||||
accessToken: "1234567890",
|
||||
apiKey: "1234567890",
|
||||
chatHistory: [],
|
||||
selectedTags: [],
|
||||
selectedVectorStores: [],
|
||||
selectedGuardrails: [],
|
||||
selectedMCPTools: [],
|
||||
selectedSdk: "openai",
|
||||
});
|
||||
expect(code).toContain("text-embedding-3-small");
|
||||
expect(code).toContain("Hello, world!");
|
||||
expect(code).toContain("client.embeddings.create");
|
||||
expect(code).toContain("print(response.data[0].embedding)");
|
||||
});
|
||||
});
|
||||
@@ -493,6 +493,17 @@ else:
|
||||
`;
|
||||
}
|
||||
break;
|
||||
case EndpointType.EMBEDDINGS:
|
||||
endpointSpecificCode = `
|
||||
response = client.embeddings.create(
|
||||
input="${inputMessage || "Your string here"}",
|
||||
model="${modelNameForCode}",
|
||||
encoding_format="base64" # or "float"
|
||||
)
|
||||
|
||||
print(response.data[0].embedding)
|
||||
`;
|
||||
break;
|
||||
default:
|
||||
endpointSpecificCode = "\n# Code generation for this endpoint is not implemented yet.";
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import { render, waitFor } from "@testing-library/react";
|
||||
import { describe, it, expect } from "vitest";
|
||||
import EndpointSelector, { endpointOptions } from "./EndpointSelector";
|
||||
|
||||
describe("EndpointSelector", () => {
|
||||
Object.values(endpointOptions).forEach((endpointType) => {
|
||||
it(`should render the endpoint selector for ${endpointType.value}`, async () => {
|
||||
const { getByText } = render(<EndpointSelector endpointType={endpointType.value} onEndpointChange={() => {}} />);
|
||||
await waitFor(() => {
|
||||
expect(getByText(endpointType.label)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -12,16 +12,16 @@ interface EndpointSelectorProps {
|
||||
/**
|
||||
* A reusable component for selecting API endpoints
|
||||
*/
|
||||
const EndpointSelector: React.FC<EndpointSelectorProps> = ({ endpointType, onEndpointChange, className }) => {
|
||||
// Map endpoint types to their display labels
|
||||
const endpointOptions = [
|
||||
{ value: EndpointType.CHAT, label: "/v1/chat/completions" },
|
||||
{ value: EndpointType.RESPONSES, label: "/v1/responses" },
|
||||
{ value: EndpointType.ANTHROPIC_MESSAGES, label: "/v1/messages" },
|
||||
{ value: EndpointType.IMAGE, label: "/v1/images/generations" },
|
||||
{ value: EndpointType.IMAGE_EDITS, label: "/v1/images/edits" },
|
||||
];
|
||||
export const endpointOptions = [
|
||||
{ value: EndpointType.CHAT, label: "/v1/chat/completions" },
|
||||
{ value: EndpointType.RESPONSES, label: "/v1/responses" },
|
||||
{ value: EndpointType.ANTHROPIC_MESSAGES, label: "/v1/messages" },
|
||||
{ value: EndpointType.IMAGE, label: "/v1/images/generations" },
|
||||
{ value: EndpointType.IMAGE_EDITS, label: "/v1/images/edits" },
|
||||
{ value: EndpointType.EMBEDDINGS, label: "/v1/embeddings" },
|
||||
];
|
||||
|
||||
const EndpointSelector: React.FC<EndpointSelectorProps> = ({ endpointType, onEndpointChange, className }) => {
|
||||
return (
|
||||
<div className={className}>
|
||||
<Text>Endpoint Type:</Text>
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { truncateString } from "./chatUtils";
|
||||
|
||||
describe("chatUtils", () => {
|
||||
describe("truncateString", () => {
|
||||
it("should truncate a string", () => {
|
||||
expect(truncateString("Hello, world!", 5)).toBe("Hello...");
|
||||
});
|
||||
|
||||
it("should return the original string if it is less than the max length", () => {
|
||||
expect(truncateString("Hello, world!", 20)).toBe("Hello, world!");
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,3 @@
|
||||
export function truncateString(str: string, maxLength: number) {
|
||||
return str.length > maxLength ? str.substring(0, maxLength) + "..." : str;
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { makeOpenAIEmbeddingsRequest } from "./embeddings_api";
|
||||
import OpenAI from "openai";
|
||||
|
||||
vi.mock("openai");
|
||||
|
||||
describe("embeddings_api", () => {
|
||||
const mockCreate = vi.fn();
|
||||
const mockUpdateEmbeddingsUI = vi.fn();
|
||||
|
||||
beforeEach(() => {
|
||||
// Mock the response structure from OpenAI embeddings API
|
||||
mockCreate.mockResolvedValue({
|
||||
data: [
|
||||
{
|
||||
embedding: [0.1, 0.2, 0.3, 0.4, 0.5],
|
||||
index: 0,
|
||||
object: "embedding",
|
||||
},
|
||||
],
|
||||
model: "text-embedding-3-small",
|
||||
object: "list",
|
||||
usage: {
|
||||
prompt_tokens: 5,
|
||||
total_tokens: 5,
|
||||
},
|
||||
});
|
||||
|
||||
// Mock the OpenAI constructor and its methods
|
||||
(OpenAI as any).mockImplementation(() => ({
|
||||
embeddings: {
|
||||
create: mockCreate,
|
||||
},
|
||||
}));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("should make a request to the embeddings API", async () => {
|
||||
await makeOpenAIEmbeddingsRequest(
|
||||
"Hello, world!",
|
||||
mockUpdateEmbeddingsUI,
|
||||
"text-embedding-3-small",
|
||||
"1234567890",
|
||||
[],
|
||||
);
|
||||
|
||||
expect(mockCreate).toHaveBeenCalledWith({
|
||||
model: "text-embedding-3-small",
|
||||
input: "Hello, world!",
|
||||
});
|
||||
expect(mockUpdateEmbeddingsUI).toHaveBeenCalledWith(
|
||||
JSON.stringify([0.1, 0.2, 0.3, 0.4, 0.5]),
|
||||
"text-embedding-3-small",
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,50 @@
|
||||
import NotificationManager from "@/components/molecules/notifications_manager";
|
||||
import { getProxyBaseUrl } from "@/components/networking";
|
||||
import openai from "openai";
|
||||
|
||||
export async function makeOpenAIEmbeddingsRequest(
|
||||
input: string,
|
||||
updateEmbeddingsUI: (embeddings: string, model?: string) => void,
|
||||
selectedModel: string,
|
||||
accessToken: string,
|
||||
tags?: string[],
|
||||
) {
|
||||
if (!accessToken) {
|
||||
throw new Error("API key is required");
|
||||
}
|
||||
|
||||
// Base URL should be the current base_url
|
||||
const isLocal = process.env.NODE_ENV === "development";
|
||||
if (isLocal !== true) {
|
||||
console.log = function () {};
|
||||
}
|
||||
|
||||
const proxyBaseUrl = getProxyBaseUrl();
|
||||
// Prepare headers with tags and trace ID
|
||||
const headers: Record<string, string> = {};
|
||||
if (tags && tags.length > 0) {
|
||||
headers["x-litellm-tags"] = tags.join(",");
|
||||
}
|
||||
|
||||
const client = new openai.OpenAI({
|
||||
apiKey: accessToken,
|
||||
baseURL: proxyBaseUrl,
|
||||
dangerouslyAllowBrowser: true,
|
||||
defaultHeaders: headers,
|
||||
});
|
||||
|
||||
try {
|
||||
const response = await client.embeddings.create({
|
||||
model: selectedModel,
|
||||
input: input,
|
||||
});
|
||||
|
||||
updateEmbeddingsUI(JSON.stringify(response.data[0].embedding), selectedModel);
|
||||
} catch (error: unknown) {
|
||||
NotificationManager.fromBackend(
|
||||
`Error occurred while making embeddings request. Please try again. Error: ${error}`,
|
||||
);
|
||||
|
||||
throw error; // Re-throw to allow the caller to handle the error
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@ export enum EndpointType {
|
||||
RESPONSES = "responses",
|
||||
IMAGE_EDITS = "image_edits",
|
||||
ANTHROPIC_MESSAGES = "anthropic_messages",
|
||||
EMBEDDINGS = "embeddings",
|
||||
// add additional endpoint types if required
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user