{"record":{"id":"8189e61d142ba62e","repo":"Mintplex-Labs/anything-llm","slug":"e-message-8189e6","errorCode":null,"errorMessage":"${e.message}","messagePattern":"\\$\\{e\\.message\\}","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"server/utils/AiProviders/perplexity/index.js","lineNumber":104,"sourceCode":"    };\n    return [prompt, ...chatHistory, { role: \"user\", content: userPrompt }];\n  }\n\n  async getChatCompletion(messages = null, { temperature = 0.7 }) {\n    if (!(await this.isValidChatCompletionModel(this.model)))\n      throw new Error(\n        `Perplexity chat: ${this.model} is not valid for chat completion!`\n      );\n\n    const result = await LLMPerformanceMonitor.measureAsyncFunction(\n      this.openai.chat.completions\n        .create({\n          model: this.model,\n          messages,\n          temperature,\n        })\n        .catch((e) => {\n          throw new Error(e.message);\n        })\n    );\n\n    if (\n      !result.output.hasOwnProperty(\"choices\") ||\n      result.output.choices.length === 0\n    )\n      return null;\n\n    return {\n      textResponse: result.output.choices[0].message.content,\n      metrics: {\n        prompt_tokens: result.output.usage?.prompt_tokens || 0,\n        completion_tokens: result.output.usage?.completion_tokens || 0,\n        total_tokens: result.output.usage?.total_tokens || 0,\n        outputTps: result.output.usage?.completion_tokens / result.duration,\n        duration: result.duration,\n        model: this.model,","sourceCodeStart":86,"sourceCodeEnd":122,"githubUrl":"https://github.com/Mintplex-Labs/anything-llm/blob/526360e320da9d1b36074be5ed64fe76e5bbfbbd/server/utils/AiProviders/perplexity/index.js#L86-L122","documentation":"Re-thrown from the OpenAI SDK's promise rejection inside getChatCompletion: the `.catch((e) => { throw new Error(e.message); })` wrapper flattens whatever the Perplexity endpoint returned (auth failure, rate limit, malformed request, network timeout) into a plain Error carrying only the message string. The original status code / SDK error class (e.g. AuthenticationError, RateLimitError) is lost.","triggerScenarios":"Perplexity's api.perplexity.ai returns non-2xx: 401 (bad/expired key), 403, 404 (unknown model on their side despite passing local validation), 429 (rate limit / quota), 5xx, or the OpenAI client throws a connection/timeout/abort error before a response arrives.","commonSituations":"PERPLEXITY_API_KEY was valid at construction but revoked later; exceeded the Perplexity plan's request rate; the chosen model is online-only and the account tier does not include it; flaky egress through a corporate proxy; the response stream was aborted by the client mid-request.","solutions":["Read the message verbatim: '401' / 'Incorrect API key' -> rotate the key; '429' / 'rate limit' -> back off or upgrade plan; 'model not found' -> switch model.","Reproduce with curl against https://api.perplexity.ai/chat/completions using the same key and model to confirm whether it is auth, quota, or the model id.","Check Perplexity status/usage dashboard for outages or quota exhaustion.","If transient (5xx / network), add a bounded retry with exponential backoff around getChatCompletion."],"exampleFix":"// before\nconst text = (await llm.getChatCompletion(messages, { temperature: 0.7 })).textResponse;\n\n// after\nasync function callWithRetry(llm, messages, opts, retries = 3) {\n  for (let attempt = 0; attempt <= retries; attempt++) {\n    try {\n      return await llm.getChatCompletion(messages, opts);\n    } catch (err) {\n      const transient = /429|5\\d{2}|timeout|ECONNRESET|fetch failed/i.test(err.message);\n      if (!transient || attempt === retries) throw err;\n      await new Promise((r) => setTimeout(r, 2 ** attempt * 500));\n    }\n  }\n}\nconst text = (await callWithRetry(llm, messages, { temperature: 0.7 })).textResponse;","handlingStrategy":"retry","validationCode":"function classifyProviderError(message) {\n  if (/401|unauthorized|invalid api key/i.test(message)) return \"auth\";\n  if (/429|rate limit|quota/i.test(message)) return \"rate\";\n  if (/5\\d{2}|server error|timeout|ECONN/i.test(message)) return \"transient\";\n  return \"fatal\";\n}\n// decide before retrying whether the error is worth retrying\nconst kind = classifyProviderError(err.message);","typeGuard":"function isTransientProviderError(message) {\n  return typeof message === \"string\" && /429|5\\d{2}|timeout|ECONNRESET|fetch failed|socket hang up/i.test(message);\n}","tryCatchPattern":"async function perplexityCall(llm, messages, opts, retries = 3) {\n  for (let i = 0; i <= retries; i++) {\n    try {\n      return await llm.getChatCompletion(messages, opts);\n    } catch (e) {\n      if (/401|not valid for chat/i.test(e.message) || i === retries) throw e;\n      await new Promise((r) => setTimeout(r, 2 ** i * 500));\n    }\n  }\n}","preventionTips":["Wrap every provider call in a classifier that distinguishes auth/rate/transient/fatal so retry policy is correct.","Preserve the original SDK error by throwing `new Error(e.message, { cause: e })` so the status code is not lost.","Set conservative concurrency limits and per-minute caps to avoid 429s.","Monitor p95 latency and error rate per provider; alert on auth spikes indicating key rotation problems."],"tags":["runtime","api-error","llm-provider","perplexity","network","error-wrapping"],"backgroundTag":null,"analyzedSha":"526360e320da9d1b36074be5ed64fe76e5bbfbbd","analyzedAt":"2026-08-13T01:45:47.170Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}