node-red/compiliance-js/process-llm-response.js
ruoyunbai d9b08c89ee js
2025-11-17 10:55:25 +08:00

120 lines
3.8 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'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);