fix: send provider model ids
This commit is contained in:
1 parent
6bd331bc73
commit
af75bcd741
9 files changed
+235
-33
No files matched your search
@@ -0,0 +1,34 @@
|
||||
import { describe, expect, test } from "vitest";
|
||||
|
||||
import { getAssistantStreamErrorMessage } from "./assistant-stream-error";
|
||||
|
||||
describe("assistant stream errors", () => {
|
||||
test("explains invalid provider model identifiers", () => {
|
||||
expect(
|
||||
getAssistantStreamErrorMessage({
|
||||
statusCode: 400,
|
||||
responseBody: JSON.stringify({
|
||||
error: {
|
||||
message: "DeepSeek: DeepSeek V4 Pro (deepseek/deepseek-v4-pro) is not a valid model ID",
|
||||
},
|
||||
}),
|
||||
}),
|
||||
).toBe("The provider rejected the selected model. Ask an administrator to select a valid model ID.");
|
||||
});
|
||||
|
||||
test.each([
|
||||
[401, "The provider rejected the configured credentials. Ask an administrator to update the API key."],
|
||||
[402, "The provider account has insufficient credits for this request."],
|
||||
[404, "The selected model or chat endpoint was not found. Ask an administrator to verify the model and API URL."],
|
||||
[429, "The model endpoint is rate limited. Wait a moment and try again."],
|
||||
[503, "The model provider is temporarily unavailable. Try again later."],
|
||||
])("maps provider status %s to an actionable message", (statusCode, expected) => {
|
||||
expect(getAssistantStreamErrorMessage({ statusCode })).toBe(expected);
|
||||
});
|
||||
|
||||
test("recognizes nested timeout errors", () => {
|
||||
expect(getAssistantStreamErrorMessage(new Error("request failed", { cause: new Error("timed out") }))).toBe(
|
||||
"The model endpoint took too long to respond. Try again.",
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,61 @@
|
||||
const asRecord = (value: unknown): Record<string, unknown> | null =>
|
||||
typeof value === "object" && value !== null && !Array.isArray(value) ? (value as Record<string, unknown>) : null;
|
||||
|
||||
const getStatusCode = (error: unknown): number | undefined => {
|
||||
const record = asRecord(error);
|
||||
if (!record) return undefined;
|
||||
const statusCode =
|
||||
typeof record.statusCode === "number"
|
||||
? record.statusCode
|
||||
: typeof record.status === "number"
|
||||
? record.status
|
||||
: undefined;
|
||||
if (statusCode !== undefined) return statusCode;
|
||||
return getStatusCode(record.cause) ?? getStatusCode(record.response);
|
||||
};
|
||||
|
||||
const getErrorMessage = (error: unknown): string => {
|
||||
if (error instanceof Error) {
|
||||
return `${error.message} ${getErrorMessage(error.cause)}`.trim();
|
||||
}
|
||||
const record = asRecord(error);
|
||||
if (!record) return typeof error === "string" ? error : "";
|
||||
const responseBody = typeof record.responseBody === "string" ? record.responseBody : "";
|
||||
const message = typeof record.message === "string" ? record.message : "";
|
||||
return `${message} ${responseBody} ${getErrorMessage(record.cause)}`.trim();
|
||||
};
|
||||
|
||||
export const getAssistantStreamErrorMessage = (error: unknown) => {
|
||||
const statusCode = getStatusCode(error);
|
||||
const message = getErrorMessage(error);
|
||||
|
||||
if (
|
||||
/\bmodel(?:\s+id)?\b/iu.test(message) &&
|
||||
/\b(invalid|unknown|unavailable|not found|not a valid)\b/iu.test(message)
|
||||
) {
|
||||
return "The provider rejected the selected model. Ask an administrator to select a valid model ID.";
|
||||
}
|
||||
if (statusCode === 400) {
|
||||
return "The provider rejected the request. The selected model may not support the requested input or tools.";
|
||||
}
|
||||
if (statusCode === 401 || statusCode === 403) {
|
||||
return "The provider rejected the configured credentials. Ask an administrator to update the API key.";
|
||||
}
|
||||
if (statusCode === 402) {
|
||||
return "The provider account has insufficient credits for this request.";
|
||||
}
|
||||
if (statusCode === 404) {
|
||||
return "The selected model or chat endpoint was not found. Ask an administrator to verify the model and API URL.";
|
||||
}
|
||||
if (statusCode === 408 || statusCode === 504 || /\b(timeout|timed out)\b/iu.test(message)) {
|
||||
return "The model endpoint took too long to respond. Try again.";
|
||||
}
|
||||
if (statusCode === 429) {
|
||||
return "The model endpoint is rate limited. Wait a moment and try again.";
|
||||
}
|
||||
if (statusCode !== undefined && statusCode >= 500) {
|
||||
return "The model provider is temporarily unavailable. Try again later.";
|
||||
}
|
||||
|
||||
return "The model endpoint stopped the response. Try again, or ask an administrator to verify its URL, model, and credentials.";
|
||||
};
|
||||
@@ -27,6 +27,7 @@ import type {
|
||||
|
||||
import { extractMcpTools } from "../../mcp/_extract-tools";
|
||||
import { getRequestedMentionIds, sanitizeAttachmentFilename } from "./assistant-chat-input";
|
||||
import { getAssistantStreamErrorMessage } from "./assistant-stream-error";
|
||||
|
||||
export const maxDuration = 60;
|
||||
|
||||
@@ -35,8 +36,6 @@ const getToolApprovalSecret = () =>
|
||||
Buffer.from(
|
||||
hkdfSync("sha256", Buffer.from(env.SECRET_ENCRYPTION_KEY, "hex"), "", "assistant-tool-approval", 32),
|
||||
).toString("base64url");
|
||||
const safeStreamError =
|
||||
"The model endpoint stopped the response. Try again, or ask an administrator to verify its URL, model, and credentials.";
|
||||
const allowedAttachmentMediaTypes = new Set([
|
||||
"image/gif",
|
||||
"image/jpeg",
|
||||
@@ -314,8 +313,6 @@ export async function POST(request: Request) {
|
||||
if (!configuration?.enabled || !configuration.modelId || (requiresApiKey && !configuration.encryptedApiKey)) {
|
||||
return Response.json({ error: "Homarr Assistant is not configured." }, { status: 503 });
|
||||
}
|
||||
const modelId = configuration.modelId;
|
||||
|
||||
const thread = await db.query.assistantThreads.findFirst({
|
||||
where: and(eq(assistantThreads.id, parsed.data.id), eq(assistantThreads.userId, session.user.id)),
|
||||
});
|
||||
@@ -330,6 +327,7 @@ export async function POST(request: Request) {
|
||||
getSelectedModelDetailsAsync(configuration).catch(() => null),
|
||||
getMentionContextAsync(context, parsed.data.messages),
|
||||
]);
|
||||
const modelId = selectedModel?.id ?? configuration.modelId;
|
||||
if (
|
||||
selectedModel &&
|
||||
selectedModel.inputModalities.length > 0 &&
|
||||
@@ -488,6 +486,9 @@ export async function POST(request: Request) {
|
||||
},
|
||||
onError: ({ error }) => {
|
||||
logger.error("Assistant response stream failed", {
|
||||
requestId,
|
||||
provider: configuration.provider,
|
||||
modelId,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
});
|
||||
},
|
||||
@@ -555,7 +556,7 @@ export async function POST(request: Request) {
|
||||
},
|
||||
};
|
||||
},
|
||||
onError: () => safeStreamError,
|
||||
onError: getAssistantStreamErrorMessage,
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error("Assistant response could not start", {
|
||||
|
||||
Reference in new issue
Block a user