mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-09 08:18:12 +00:00
fix(ui): keep chat text sent back to the model in English (#50304)
The /chat request errors and deep research notice, and Aria's step limit
and no answer replies, are written into assistant messages that are sent
to the model as history on the next turn. Pin them to English with
i18n.getFixedT("en") so a future UI language never changes the model
payload. English output is unchanged.
This commit is contained in:
parent
80b1b9b188
commit
e3272da848
4 changed files with 107 additions and 6 deletions
|
|
@ -1,5 +1,6 @@
|
|||
import OpenAI from "openai";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import i18n from "@/i18n/i18n";
|
||||
import { makeOpenAIResponsesRequest } from "@/components/llm_calls/responses_api";
|
||||
import { generateChatImages } from "./imageHelpers";
|
||||
import { toResponsesMessages } from "./attachmentHelpers";
|
||||
|
|
@ -192,4 +193,67 @@ describe("runChatRequest", () => {
|
|||
expect(options.onUpdate).toHaveBeenCalledExactlyOnceWith({ content: " [stopped]" });
|
||||
expect(options.onResponseId).toHaveBeenCalledWith(null);
|
||||
});
|
||||
|
||||
describe("when the UI language is not English", () => {
|
||||
beforeEach(async () => {
|
||||
i18n.addResourceBundle(
|
||||
"de",
|
||||
"translation",
|
||||
{
|
||||
chat: {
|
||||
runChatRequest: { genericError: "Fehler", fileInputHint: " Hinweis", researchNeedsBackground: "Hinweis" },
|
||||
},
|
||||
},
|
||||
true,
|
||||
true,
|
||||
);
|
||||
await i18n.changeLanguage("de");
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await i18n.changeLanguage("en");
|
||||
i18n.removeResourceBundle("de", "translation");
|
||||
});
|
||||
|
||||
it("should keep the generic error in English because it is sent back to the model", async () => {
|
||||
const options = requestOptions();
|
||||
vi.mocked(makeOpenAIResponsesRequest).mockRejectedValue("offline");
|
||||
await runChatRequest(options, i18n.t.bind(i18n));
|
||||
expect(options.onUpdate).toHaveBeenLastCalledWith({ content: "Something went wrong. Please try again." });
|
||||
});
|
||||
|
||||
it("should keep the file input hint in English because it is sent back to the model", async () => {
|
||||
const options = requestOptions({
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
content: "Read the report",
|
||||
attachments: [
|
||||
{
|
||||
id: "report",
|
||||
kind: "file",
|
||||
name: "report.pdf",
|
||||
mimeType: "application/pdf",
|
||||
url: "data:application/pdf;base64,JVBERg==",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
const error = new OpenAI.APIError(400, { message: "Provider detail" }, "Provider detail", new Headers());
|
||||
vi.mocked(makeOpenAIResponsesRequest).mockRejectedValue(error);
|
||||
await runChatRequest(options, i18n.t.bind(i18n));
|
||||
expect(options.onUpdate).toHaveBeenLastCalledWith({
|
||||
content: error.message + " Try a model that supports file inputs, or paste the relevant text.",
|
||||
});
|
||||
});
|
||||
|
||||
it("should keep the deep research notice in English because it is sent back to the model", async () => {
|
||||
const options = requestOptions({ research: true });
|
||||
await runChatRequest(options, i18n.t.bind(i18n));
|
||||
expect(options.onUpdate).toHaveBeenCalledExactlyOnceWith({
|
||||
content: "Enable background chats before starting deep research.",
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -31,8 +31,8 @@ function requestErrorMessage(
|
|||
error: unknown,
|
||||
messages: ChatRequestOptions["messages"],
|
||||
mode: ChatRequestOptions["mode"],
|
||||
t: TFunction = i18n.t.bind(i18n),
|
||||
): string {
|
||||
const t = i18n.getFixedT("en");
|
||||
const message = error instanceof Error ? error.message : t("chat.runChatRequest.genericError");
|
||||
const hasFile =
|
||||
mode === "chat" && messages.some((item) => item.attachments?.some((attachment) => attachment.kind === "file"));
|
||||
|
|
@ -67,7 +67,7 @@ export async function runChatRequest(
|
|||
try {
|
||||
signal.throwIfAborted();
|
||||
if (research && !background) {
|
||||
onUpdate({ content: t("chat.runChatRequest.researchNeedsBackground") });
|
||||
onUpdate({ content: i18n.getFixedT("en")("chat.runChatRequest.researchNeedsBackground") });
|
||||
return;
|
||||
}
|
||||
if (mode === "image") {
|
||||
|
|
@ -125,7 +125,7 @@ export async function runChatRequest(
|
|||
} catch (error: unknown) {
|
||||
onResponseId(null);
|
||||
const stopped = signal.aborted || (error instanceof Error && error.name === "AbortError");
|
||||
const errorMessage = requestErrorMessage(error, messages, mode, t);
|
||||
const errorMessage = requestErrorMessage(error, messages, mode);
|
||||
onUpdate({ content: stopped ? content + " [stopped]" : content || errorMessage });
|
||||
} finally {
|
||||
if (mcpEvents.length) onUpdate({ mcpEvents });
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|||
import type { ChatCompletion, ChatCompletionCreateParamsNonStreaming } from "openai/resources/chat/completions";
|
||||
import { createGatewayClient } from "@/components/llm_calls/gateway_client";
|
||||
import { registerAuthHeaderNameGetter } from "@/lib/http/runtime";
|
||||
import i18n from "@/i18n/i18n";
|
||||
import {
|
||||
getPreferredLiteAdminModel,
|
||||
MAX_INPUT_LENGTH,
|
||||
|
|
@ -889,6 +890,42 @@ describe("SDK conversation and action lifecycle", () => {
|
|||
);
|
||||
});
|
||||
|
||||
describe("LiteAdmin replies sent back to the model when the UI language is not English", () => {
|
||||
beforeEach(async () => {
|
||||
i18n.addResourceBundle(
|
||||
"de",
|
||||
"translation",
|
||||
{ aria: { agent: { stepLimit: "Schrittlimit", noAnswer: "Keine Antwort" } } },
|
||||
true,
|
||||
true,
|
||||
);
|
||||
await i18n.changeLanguage("de");
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await i18n.changeLanguage("en");
|
||||
i18n.removeResourceBundle("de", "translation");
|
||||
});
|
||||
|
||||
it("keeps the step limit reply in English", async () => {
|
||||
const input = options();
|
||||
const model = transport(Array.from({ length: 6 }, () => completion([call("teams_list", teamsArgs)])));
|
||||
await runLiteAdmin(input, model.client);
|
||||
expect(input.onMessage).toHaveBeenCalledExactlyOnceWith(
|
||||
"I reached the step limit. Any completed actions remain applied; check the relevant page before continuing.",
|
||||
);
|
||||
});
|
||||
|
||||
it("keeps the no answer reply in English", async () => {
|
||||
const input = options();
|
||||
const model = transport([completion([], null)]);
|
||||
await runLiteAdmin(input, model.client);
|
||||
expect(input.onMessage).toHaveBeenCalledExactlyOnceWith(
|
||||
"The model returned no answer. Try another request or model.",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("inference destination", () => {
|
||||
const destinations = [
|
||||
["", "/gateway", "https://dashboard.example/ui/", "https://dashboard.example/gateway", false],
|
||||
|
|
|
|||
|
|
@ -141,8 +141,8 @@ export async function runLiteAdmin(options: LiteAdminOptions, client?: OpenAI):
|
|||
active();
|
||||
const message = completion.choices[0]?.message;
|
||||
if (message?.tool_calls?.length) {
|
||||
options.onMessage(i18n.t("aria.agent.stepLimit"));
|
||||
options.onMessage(i18n.getFixedT("en")("aria.agent.stepLimit"));
|
||||
} else if (!message?.content && !message?.refusal) {
|
||||
options.onMessage(i18n.t("aria.agent.noAnswer"));
|
||||
options.onMessage(i18n.getFixedT("en")("aria.agent.noAnswer"));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue