mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-09 22:31:41 +00:00
fix(ui): guard playground cost metric against null and NaN
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
parent
1af7a403c6
commit
06497ab42a
6 changed files with 63 additions and 3 deletions
|
|
@ -51,4 +51,23 @@ describe("ResponseMetrics prompt cache chips", () => {
|
|||
|
||||
expect(screen.queryByText(/Response Cache/)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("does not render the Cost chip when a persisted cost is null", () => {
|
||||
render(<ResponseMetrics usage={{ promptTokens: 1, cost: null as unknown as number }} />);
|
||||
|
||||
expect(screen.queryByText(/Cost:/)).not.toBeInTheDocument();
|
||||
expect(screen.getByText("In: 1")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("does not render the Cost chip for NaN", () => {
|
||||
render(<ResponseMetrics usage={{ ...baseUsage, cost: Number.NaN }} />);
|
||||
|
||||
expect(screen.queryByText(/Cost:/)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders the Cost chip for a finite cost", () => {
|
||||
render(<ResponseMetrics usage={{ ...baseUsage, cost: 0.000063 }} />);
|
||||
|
||||
expect(screen.getByText("Cost: $0.000063")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -159,7 +159,7 @@ const ResponseMetrics: React.FC<ResponseMetricsProps> = ({ timeToFirstToken, tot
|
|||
/>
|
||||
)}
|
||||
|
||||
{usage?.cost !== undefined && (
|
||||
{typeof usage?.cost === "number" && Number.isFinite(usage.cost) && (
|
||||
<MetricItem
|
||||
label="Cost"
|
||||
tooltip="Cost"
|
||||
|
|
|
|||
|
|
@ -466,6 +466,12 @@ describe("chat_completion prompt cache usage", () => {
|
|||
expect(usageData).not.toHaveProperty("cacheReadTokens");
|
||||
expect(usageData).not.toHaveProperty("cacheCreationTokens");
|
||||
});
|
||||
|
||||
it("omits cost when the provider reports a non-numeric value", async () => {
|
||||
const usageData = await captureUsage({ cost: "not-a-number" });
|
||||
|
||||
expect(usageData).toEqual(expect.not.objectContaining({ cost: expect.anything() }));
|
||||
});
|
||||
});
|
||||
|
||||
describe("chat_completion response cache", () => {
|
||||
|
|
|
|||
|
|
@ -245,7 +245,10 @@ export async function makeOpenAIChatCompletionRequest(
|
|||
|
||||
// Extract cost from usage object if available
|
||||
if (chunkWithUsage.usage.cost !== undefined && chunkWithUsage.usage.cost !== null) {
|
||||
usageData.cost = parseFloat(chunkWithUsage.usage.cost);
|
||||
const parsedCost = parseFloat(chunkWithUsage.usage.cost);
|
||||
if (Number.isFinite(parsedCost)) {
|
||||
usageData.cost = parsedCost;
|
||||
}
|
||||
}
|
||||
|
||||
onUsageData(usageData);
|
||||
|
|
|
|||
|
|
@ -231,6 +231,35 @@ describe("responses_api", () => {
|
|||
expect(onUsageData).toHaveBeenCalledWith(expect.not.objectContaining({ cost: expect.anything() }), "");
|
||||
});
|
||||
|
||||
it("should omit cost when the proxy reports a non-numeric cost", async () => {
|
||||
async function* streamWithNonNumericCost() {
|
||||
yield {
|
||||
type: "response.completed",
|
||||
response: {
|
||||
id: "resp_non_numeric_cost",
|
||||
usage: { output_tokens: 12, input_tokens: 12, total_tokens: 24, cost: "not-a-number" },
|
||||
},
|
||||
};
|
||||
}
|
||||
mockResponsesCreate.mockResolvedValueOnce(streamWithNonNumericCost());
|
||||
|
||||
const onUsageData = vi.fn();
|
||||
|
||||
await makeOpenAIResponsesRequest(
|
||||
messages,
|
||||
mockUpdateTextUI,
|
||||
"gpt-4",
|
||||
"test-token",
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
onUsageData,
|
||||
);
|
||||
|
||||
expect(onUsageData).toHaveBeenCalledWith(expect.not.objectContaining({ cost: expect.anything() }), "");
|
||||
});
|
||||
|
||||
it("should replay MCP output items as events for a non-streaming response", async () => {
|
||||
mockResponsesCreate.mockReturnValueOnce(
|
||||
nonStreamingResponse({
|
||||
|
|
|
|||
|
|
@ -312,7 +312,10 @@ export async function makeOpenAIResponsesRequest(
|
|||
}
|
||||
|
||||
if (usage.cost !== undefined && usage.cost !== null) {
|
||||
usageData.cost = Number(usage.cost);
|
||||
const parsedCost = Number(usage.cost);
|
||||
if (Number.isFinite(parsedCost)) {
|
||||
usageData.cost = parsedCost;
|
||||
}
|
||||
}
|
||||
|
||||
onUsageData(usageData, mcpToolUsed);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue