Mintplex-Labs/anything-llm · error · Error
${e.message}
Error message
${e.message} What it means
Re-thrown from the OpenAI SDK rejection inside PPIOLLM.getChatCompletion via `.catch((e) => { throw new Error(e.message); })`. The wrapper discards the SDK error class and HTTP status, leaving only the message string, so a 401, 429, and a 500 look identical to the caller except for wording.
Source
Thrown at server/utils/AiProviders/ppio/index.js:162
};
return [prompt, ...chatHistory, { role: "user", content: userPrompt }];
}
async getChatCompletion(messages = null, { temperature = 0.7 }) {
if (!(await this.isValidChatCompletionModel(this.model)))
throw new Error(
`PPIO chat: ${this.model} is not valid for chat completion!`
);
const result = await LLMPerformanceMonitor.measureAsyncFunction(
this.openai.chat.completions
.create({
model: this.model,
messages,
temperature,
})
.catch((e) => {
throw new Error(e.message);
})
);
if (
!Object.prototype.hasOwnProperty.call(result.output, "choices") ||
result.output.choices.length === 0
)
return null;
return {
textResponse: result.output.choices[0].message.content,
metrics: {
prompt_tokens: result.output.usage.prompt_tokens || 0,
completion_tokens: result.output.usage.completion_tokens || 0,
total_tokens: result.output.usage.total_tokens || 0,
outputTps: result.output.usage.completion_tokens / result.duration,
duration: result.duration,
model: this.model,View on GitHub (pinned to 526360e320)
Solutions
- Match the message: '401'/'Unauthorized' -> key issue; '429'/'rate' -> throttle; 'model'/'not found' -> model id issue; 'timeout'/'ECONN' -> network.
- curl the same payload to https://api.ppintra.com/v3/openai/chat/completions to isolate auth vs model vs quota.
- Check the PPIO console for quota/usage.
- For transient errors, wrap the call in bounded exponential-backoff retry.
Example fix
// before
const out = await llm.getChatCompletion(messages, { temperature: 0.7 });
// after
try {
const out = await llm.getChatCompletion(messages, { temperature: 0.7 });
} catch (err) {
if (/401|unauthorized/i.test(err.message)) throw new Error("PPIO key invalid — reconfigure", { cause: err });
if (/429|rate/i.test(err.message)) throw new Error("PPIO rate limited — back off", { cause: err });
throw err;
} Defensive patterns
Strategy: retry
Validate before calling
function classifyPpioError(message) {
if (/401|unauthorized|invalid api key/i.test(message)) return "auth";
if (/429|rate limit|quota/i.test(message)) return "rate";
if (/5\d{2}|timeout|ECONN/i.test(message)) return "transient";
return "fatal";
} Type guard
function isTransientPpioError(message) {
return typeof message === "string" && /429|5\d{2}|timeout|ECONNRESET|fetch failed/i.test(message);
} Try / catch
async function ppioCall(llm, messages, opts, retries = 3) {
for (let i = 0; i <= retries; i++) {
try {
return await llm.getChatCompletion(messages, opts);
} catch (e) {
if (/401|not valid for chat/i.test(e.message) || i === retries) throw e;
await new Promise((r) => setTimeout(r, 2 ** i * 500));
}
}
} Prevention
- Throw `new Error(e.message, { cause: e })` to preserve the SDK status code for diagnosis.
- Classify errors so auth failures are not retried.
- Set per-provider rate budgets to avoid 429s on PPIO's free tier.
- curl the endpoint directly when an ambiguous message appears to confirm auth/quota.
When it happens
Trigger: PPIO's endpoint returns non-2xx or the client throws pre-response: 401 invalid key, 403 forbidden, 404 model not found server-side, 429 rate/quota, 5xx, or a transport error (DNS, TLS, abort).
Common situations: PPIO_API_KEY was correct at startup but later deactivated; burst traffic tripped the per-minute rate limit; requesting a model the key's plan excludes; cold-start timeout against ppinfra.com; client aborted the request while streaming.
Related errors
AI-assisted analysis of Mintplex-Labs/anything-llm@526360e320 (2026-08-13).
Data as JSON: /api/errors/f8727e0e5df1c74e.
Report an issue: GitHub.