Node.js 接入降重降AI接口
Node.js 调用降重降AI API 完整示例:原生 fetch 零依赖基础调用、逐段落异步处理、jszip 处理 OOXML 脚注占位符、p-limit 控制并发。
Node.js 接入降重降AI接口
Node.js 18+ 内置全局 fetch,调用降重降AI接口零依赖。接口一次改写同时完成降重和降AI,单次请求处理一个正文自然段(不超过1000字),详细参数说明见接口文档。
基础调用
以下代码包含超时控制与错误重试,可直接复制运行(环境变量 LLMAPI_KEY):
const API_URL = 'https://api.llmapi.fit/completion/v2/reduce';
const API_KEY = process.env.LLMAPI_KEY || 'YOUR_API_KEY';
async function reduceParagraph(text, maxRetries = 3) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
const resp = await fetch(API_URL, {
method: 'POST',
headers: {
Authorization: `Bearer ${API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ text }),
signal: AbortSignal.timeout(180_000), // 高峰期智能排队最长约2分钟
});
if (resp.status === 200) {
const data = await resp.json();
if (data.code === 'success') return data.output_text;
throw new Error(`业务错误: ${data.code} ${data.message}`);
}
if (resp.status === 429 || resp.status === 503) {
// 429 请求过于频繁 / 503 GPU服务繁忙:等待后重试
await new Promise(r => setTimeout(r, 4000));
continue;
}
// 400 参数或余额问题、401 密钥问题:不可重试
throw new Error(`HTTP ${resp.status}: ${await resp.text()}`);
}
throw new Error('重试次数已用尽,请稍后再试');
}
// 用法
const rewritten = await reduceParagraph('待改写的正文自然段……');
逐段落处理全文
接口按段落级别改写,用 for...of 顺序处理论文的正文自然段(并发方案见下文):
const paragraphs = [
'第一个正文自然段……',
'第二个正文自然段……',
];
const rewritten = [];
for (const p of paragraphs) {
rewritten.push(await reduceParagraph(p));
await new Promise(r => setTimeout(r, 500)); // 速率控制在 10 次/秒以内
}
并发控制(p-limit)
批量任务想并发时,用 p-limit 限制同时进行的请求数,配合间隔控制总量不超过 10 次/秒:
npm install p-limit
import pLimit from 'p-limit';
const limit = pLimit(5); // 最多 5 个并发
const rewritten = await Promise.all(
paragraphs.map(p => limit(() => reduceParagraph(p)))
);
处理 Word 文档脚注(jszip)
论文段落常带脚注引用,直接发送会丢失。正确做法是把脚注引用替换为 [[FNn]] 占位符,改写后原样保留,写回时恢复(完整规范见接口文档第8节)。
Node 生态多数 docx 编辑库对脚注支持有限,直接用 jszip 操作 OOXML 包最可靠:
npm install jszip
import JSZip from 'jszip';
async function extractParagraphsWithPlaceholders(buffer) {
const zip = await JSZip.loadAsync(buffer);
// word/document.xml:正文的脚注引用替换为占位符
const docXml = await zip.file('word/document.xml').async('string');
let fnIndex = 0;
const processed = docXml.replace(
/<w:footnoteReference[^>]*\/>/g,
() => `[[FN${fnIndex++}]]`
);
// 注意:此正则示意单段处理;实际按 <w:p> 切分后,
// 每段重置 fnIndex 从 [[FN0]] 开始编号
// word/footnotes.xml:条目存储文件,全程不做任何修改
return processed;
}
按 <w:p> 切分段落、每段独立编号(从 [[FN0]] 开始),把含占位符的段落文本发给接口;返回结果按占位符切分写回,[[FNn]] 处插入原 <w:footnoteReference w:id="原id"/>,保存时 Word 按引用出现顺序自动重排编号。
常见问题
| 现象 | 原因 | 处理 |
|---|---|---|
401 unauthorized | API Key 错误 | 检查请求头 Authorization: Bearer YOUR_API_KEY |
400 textTooLong | 单段超过1000字 | 按语义拆分成多个自然段分别调用 |
TimeoutError | 高峰期排队 | AbortSignal.timeout 保持 180 秒,重试 2-3 次 |
| Node 16 及以下无 fetch | 版本过旧 | 升级 Node 18+,或用 node-fetch + abort-controller |