{"record":{"id":"7975806bf6eb5425","repo":"nocobase/nocobase","slug":"kimi-extract-failed-for-sourcefile-filename","errorCode":null,"errorMessage":"Kimi extract failed for \"${sourceFile.filename}\": ${message}","messagePattern":"Kimi extract failed for \"(.+?)\": (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/plugins/@nocobase/plugin-ai/src/server/llm-providers/kimi/document-loader.ts","lineNumber":79,"sourceCode":"        uploaded = await this.client.files.create({\n          file: createReadStream(tempFilePath),\n          // @ts-ignore\n          purpose: 'file-extract',\n        });\n      } catch (error) {\n        throw new Error(this.formatApiError(error, sourceFile, 'upload'));\n      }\n\n      uploadedFileId = uploaded?.id;\n      if (!uploadedFileId) {\n        throw new Error('Kimi files.create response missing id');\n      }\n\n      let parsedResponse: any;\n      try {\n        parsedResponse = await this.client.files.content(uploadedFileId);\n      } catch (error) {\n        throw new Error(this.formatApiError(error, sourceFile, 'extract'));\n      }\n      if (typeof parsedResponse?.text === 'function') {\n        return await parsedResponse.text();\n      }\n      return typeof parsedResponse === 'string' ? parsedResponse : String(parsedResponse ?? '');\n    } finally {\n      await fs.rm(tempFilePath, { force: true });\n      if (uploadedFileId) {\n        await this.deleteRemoteFile(uploadedFileId);\n      }\n    }\n  }\n\n  private async deleteRemoteFile(fileId: string) {\n    try {\n      await this.client.files.delete(fileId);\n    } catch (error) {\n      // Ignore cleanup errors to avoid blocking main parsing flow.","sourceCodeStart":61,"sourceCodeEnd":97,"githubUrl":"https://github.com/nocobase/nocobase/blob/fa42722fefe44265490dff2c27d79e2882bce4fa/packages/plugins/@nocobase/plugin-ai/src/server/llm-providers/kimi/document-loader.ts#L61-L97","documentation":"After uploading a file to Kimi, parseByApi calls client.files.content(uploadedFileId) to retrieve the extracted text. If that call rejects, formatApiError normalizes the underlying error message (error.error.message, response.data..., error.message) into 'Kimi file extraction failed for \"<filename>\": <message>'. This wraps any network/HTTP/auth failure of the extraction step.","triggerScenarios":"client.files.content(uploadedFileId) throws: 401 invalid API key, 404 file not found (file expired or wrong baseURL routing to a different backend than the upload), 429 rate limit, 5xx from Moonshot, or network timeout/DNS failure while fetching content.","commonSituations":"Expired or revoked Kimi API key; baseURL mismatch so the content lookup hits a different service; uploading a file type Moonshot rejects for extraction; hitting Moonshot rate limits during bulk document parsing.","solutions":["Read the wrapped <message> for the real cause (401 → fix API key; 404 → check baseURL consistency; 429 → back off and retry).","Confirm apiKey is valid and active for api.moonshot.cn, and that baseURL (if overridden) points to the same service the file was uploaded to.","Check the uploaded file's format/size is supported by Kimi file-extract.","Retry with backoff for 429/5xx; check network/proxy connectivity to api.moonshot.cn."],"exampleFix":"// before\nparsedResponse = await this.client.files.content(uploadedFileId);\n// after (retry transient failures)\nfor (let i = 0; i < 3; i++) {\n  try { parsedResponse = await this.client.files.content(uploadedFileId); break; }\n  catch (e) { if (i === 2) throw e; await new Promise((r) => setTimeout(r, 1000 * 2 ** i)); }\n}","handlingStrategy":"try-catch","validationCode":"// preflight: key + connectivity check before parsing files\nconst res = await fetch(`${baseURL}/models`, { headers: { Authorization: `Bearer ${apiKey}` } });\nif (!res.ok) throw new Error(`Kimi unreachable/unauthorized: ${res.status}`);","typeGuard":null,"tryCatchPattern":"try {\n  const docs = await loader.load(file);\n} catch (err) {\n  if (/Kimi file extraction failed/.test(err.message)) {\n    const cause = err.message.split(': ').slice(1).join(': ');\n    logger.warn(`Kimi extract failed for ${file.filename}: ${cause}`);\n    // 429/5xx -> retry with backoff; 401 -> surface credential error to admin\n  } else throw err;\n}","preventionTips":["Validate the API key at provider save time with a cheap /models call.","Keep upload and content calls on the same baseURL.","Add retry-with-backoff for 429/5xx in bulk parsing jobs.","Monitor Moonshot status/quotas during large ingestion runs."],"tags":["network","api-error","kimi","file-extraction"],"backgroundTag":"llm-provider-api-error","analyzedSha":"fa42722fefe44265490dff2c27d79e2882bce4fa","analyzedAt":"2026-09-01T00:54:31.202Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}