120 lines
3.8 KiB
JavaScript
120 lines
3.8 KiB
JavaScript
'use strict';
|
||
|
||
/**
|
||
* Node-RED Function 节点脚本:处理大模型 HTTP 响应。
|
||
* 将响应解析为 mock 数据并写回 msg。
|
||
*/
|
||
|
||
function processLlmResponse(msg, node) {
|
||
const context = msg.llmContext || {};
|
||
if (!context.prompt || !context.operationCtx) {
|
||
const err = 'LLM 上下文缺失,无法解析响应';
|
||
node.error(err, msg);
|
||
msg.error = err;
|
||
return msg;
|
||
}
|
||
|
||
if (msg.statusCode && (msg.statusCode < 200 || msg.statusCode >= 300)) {
|
||
const err = `大模型接口返回状态码 ${msg.statusCode}`;
|
||
node.error(err, msg);
|
||
msg.error = err;
|
||
return msg;
|
||
}
|
||
|
||
let raw = msg.payload;
|
||
if (Buffer.isBuffer(raw)) {
|
||
raw = raw.toString('utf8');
|
||
}
|
||
if (typeof raw === 'string') {
|
||
try {
|
||
raw = JSON.parse(raw);
|
||
} catch (error) {
|
||
node.error(`无法解析大模型响应:${error.message}`, msg);
|
||
msg.error = error.message;
|
||
return msg;
|
||
}
|
||
}
|
||
|
||
try {
|
||
const parsed = parseModelResponse(raw);
|
||
|
||
msg.llmRaw = raw;
|
||
msg.mock = Object.assign({}, msg.mock, parsed.mock);
|
||
msg.mockSource = context.provider || 'dashscope';
|
||
msg.mockPrompt = context.prompt;
|
||
msg.mockCandidates = context.operationCtx.candidates;
|
||
msg.mockAutoSelected = !!context.operationCtx.autoSelected;
|
||
msg.payload = msg.mock;
|
||
|
||
delete msg.llmContext;
|
||
if (msg.headers && msg.headers.Authorization) {
|
||
delete msg.headers.Authorization;
|
||
}
|
||
|
||
delete msg.error;
|
||
return msg;
|
||
} catch (error) {
|
||
node.error(`大模型参数生成失败:${error.message}`, msg);
|
||
msg.error = error.message;
|
||
return msg;
|
||
}
|
||
}
|
||
|
||
function parseModelResponse(response) {
|
||
if (!response || typeof response !== 'object') {
|
||
throw new Error('大模型响应为空或不是对象');
|
||
}
|
||
|
||
if (response.mock && typeof response.mock === 'object') {
|
||
return { mock: response.mock };
|
||
}
|
||
|
||
const choices = Array.isArray(response.choices) ? response.choices : [];
|
||
const firstChoice = choices[0];
|
||
const message = firstChoice && firstChoice.message ? firstChoice.message : null;
|
||
const content = message && typeof message === 'object' ? message.content : null;
|
||
|
||
if (!content) {
|
||
throw new Error('响应中缺少 choices[0].message.content');
|
||
}
|
||
|
||
let mockObject;
|
||
if (typeof content === 'string') {
|
||
try {
|
||
mockObject = JSON.parse(content);
|
||
} catch (err) {
|
||
throw new Error(`无法解析模型返回的 JSON:${err.message},原始文本:${content}`);
|
||
}
|
||
} else if (Array.isArray(content)) {
|
||
const jsonPart = content.find(part => part.type === 'output_text' || part.type === 'text' || part.type === 'json');
|
||
const text = jsonPart && jsonPart.text ? jsonPart.text : null;
|
||
if (!text) {
|
||
throw new Error('响应内容不是字符串,且未找到可解析的文本段');
|
||
}
|
||
try {
|
||
mockObject = JSON.parse(text);
|
||
} catch (err) {
|
||
throw new Error(`无法解析模型返回的 JSON:${err.message},原始文本:${text}`);
|
||
}
|
||
} else {
|
||
throw new Error('模型返回的 message.content 既不是字符串也不是文本片段数组');
|
||
}
|
||
|
||
if (!mockObject || typeof mockObject !== 'object') {
|
||
throw new Error('模型返回的 JSON 不是对象');
|
||
}
|
||
|
||
const normalisedMock = {
|
||
pathParams: mockObject.pathParams || mockObject.path_parameters || {},
|
||
query: mockObject.query || mockObject.query_params || {},
|
||
headers: mockObject.headers || {},
|
||
cookies: mockObject.cookies || {},
|
||
body: mockObject.body || {},
|
||
notes: mockObject.notes || '',
|
||
};
|
||
|
||
return { mock: normalisedMock };
|
||
}
|
||
|
||
return processLlmResponse(msg, node);
|