Files
bolt-diy/app/routes/api.llmcall.ts
xKevIsDev 9d6ff741d9 feat: enhance error handling for LLM API calls
Add LLM error alert functionality to display specific error messages based on API responses. Introduce new LlmErrorAlertType interface for structured error alerts. Update chat components to manage and display LLM error alerts effectively, improving user feedback during error scenarios.
2025-07-03 11:43:58 +01:00

174 lines
5.0 KiB
TypeScript

import { type ActionFunctionArgs } from '@remix-run/cloudflare';
import { streamText } from '~/lib/.server/llm/stream-text';
import type { IProviderSetting, ProviderInfo } from '~/types/model';
import { generateText } from 'ai';
import { PROVIDER_LIST } from '~/utils/constants';
import { MAX_TOKENS } from '~/lib/.server/llm/constants';
import { LLMManager } from '~/lib/modules/llm/manager';
import type { ModelInfo } from '~/lib/modules/llm/types';
import { getApiKeysFromCookie, getProviderSettingsFromCookie } from '~/lib/api/cookies';
import { createScopedLogger } from '~/utils/logger';
export async function action(args: ActionFunctionArgs) {
return llmCallAction(args);
}
async function getModelList(options: {
apiKeys?: Record<string, string>;
providerSettings?: Record<string, IProviderSetting>;
serverEnv?: Record<string, string>;
}) {
const llmManager = LLMManager.getInstance(import.meta.env);
return llmManager.updateModelList(options);
}
const logger = createScopedLogger('api.llmcall');
async function llmCallAction({ context, request }: ActionFunctionArgs) {
const { system, message, model, provider, streamOutput } = await request.json<{
system: string;
message: string;
model: string;
provider: ProviderInfo;
streamOutput?: boolean;
}>();
const { name: providerName } = provider;
// validate 'model' and 'provider' fields
if (!model || typeof model !== 'string') {
throw new Response('Invalid or missing model', {
status: 400,
statusText: 'Bad Request',
});
}
if (!providerName || typeof providerName !== 'string') {
throw new Response('Invalid or missing provider', {
status: 400,
statusText: 'Bad Request',
});
}
const cookieHeader = request.headers.get('Cookie');
const apiKeys = getApiKeysFromCookie(cookieHeader);
const providerSettings = getProviderSettingsFromCookie(cookieHeader);
if (streamOutput) {
try {
const result = await streamText({
options: {
system,
},
messages: [
{
role: 'user',
content: `${message}`,
},
],
env: context.cloudflare?.env as any,
apiKeys,
providerSettings,
});
return new Response(result.textStream, {
status: 200,
headers: {
'Content-Type': 'text/plain; charset=utf-8',
},
});
} catch (error: unknown) {
console.log(error);
if (error instanceof Error && error.message?.includes('API key')) {
throw new Response('Invalid or missing API key', {
status: 401,
statusText: 'Unauthorized',
});
}
throw new Response(null, {
status: 500,
statusText: 'Internal Server Error',
});
}
} else {
try {
const models = await getModelList({ apiKeys, providerSettings, serverEnv: context.cloudflare?.env as any });
const modelDetails = models.find((m: ModelInfo) => m.name === model);
if (!modelDetails) {
throw new Error('Model not found');
}
const dynamicMaxTokens = modelDetails && modelDetails.maxTokenAllowed ? modelDetails.maxTokenAllowed : MAX_TOKENS;
const providerInfo = PROVIDER_LIST.find((p) => p.name === provider.name);
if (!providerInfo) {
throw new Error('Provider not found');
}
logger.info(`Generating response Provider: ${provider.name}, Model: ${modelDetails.name}`);
const result = await generateText({
system,
messages: [
{
role: 'user',
content: `${message}`,
},
],
model: providerInfo.getModelInstance({
model: modelDetails.name,
serverEnv: context.cloudflare?.env as any,
apiKeys,
providerSettings,
}),
maxTokens: dynamicMaxTokens,
toolChoice: 'none',
});
logger.info(`Generated response`);
return new Response(JSON.stringify(result), {
status: 200,
headers: {
'Content-Type': 'application/json',
},
});
} catch (error: unknown) {
console.log(error);
const errorResponse = {
error: true,
message: error instanceof Error ? error.message : 'An unexpected error occurred',
statusCode: (error as any).statusCode || 500,
isRetryable: (error as any).isRetryable !== false,
provider: (error as any).provider || 'unknown',
};
if (error instanceof Error && error.message?.includes('API key')) {
return new Response(
JSON.stringify({
...errorResponse,
message: 'Invalid or missing API key',
statusCode: 401,
isRetryable: false,
}),
{
status: 401,
headers: { 'Content-Type': 'application/json' },
statusText: 'Unauthorized',
},
);
}
return new Response(JSON.stringify(errorResponse), {
status: errorResponse.statusCode,
headers: { 'Content-Type': 'application/json' },
statusText: 'Error',
});
}
}
}