165 lines
4.8 KiB
JavaScript
165 lines
4.8 KiB
JavaScript
// json-parser.js
|
||
// 增强的 JSON 解析器,提高容错性
|
||
|
||
(function(window) {
|
||
'use strict';
|
||
|
||
/**
|
||
* 从 LLM 响应中提取并解析 JSON
|
||
* 支持多种格式的响应,提高容错性
|
||
*/
|
||
class ReActJsonParser {
|
||
/**
|
||
* 解析 LLM 响应,提取决策信息
|
||
* @param {string} response - LLM 的原始响应
|
||
* @returns {Object} 解析后的决策对象
|
||
*/
|
||
static parse(response) {
|
||
console.log('[JsonParser] 开始解析响应,长度:', response.length);
|
||
|
||
// 策略 1: 尝试直接提取 JSON 块(支持 markdown 代码块)
|
||
let parsed = this._extractFromCodeBlock(response);
|
||
if (parsed) return parsed;
|
||
|
||
// 策略 2: 尝试提取裸 JSON(最常见)
|
||
parsed = this._extractRawJson(response);
|
||
if (parsed) return parsed;
|
||
|
||
// 策略 3: 尝试修复常见 JSON 错误后解析
|
||
parsed = this._extractWithFixing(response);
|
||
if (parsed) return parsed;
|
||
|
||
// 策略 4: 如果完全无法解析,判断是否是纯文本回答
|
||
console.warn('[JsonParser] 无法提取 JSON,尝试作为纯文本回答处理');
|
||
return {
|
||
action: 'answer',
|
||
thought: '无法解析为工具调用,作为直接回答',
|
||
answer: response.trim()
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 从 markdown 代码块中提取 JSON
|
||
*/
|
||
static _extractFromCodeBlock(response) {
|
||
const codeBlockMatch = response.match(/```(?:json)?\s*\n?([\s\S]*?)\n?```/);
|
||
if (codeBlockMatch) {
|
||
try {
|
||
const json = codeBlockMatch[1].trim();
|
||
const parsed = JSON.parse(json);
|
||
console.log('[JsonParser] 成功从代码块提取 JSON');
|
||
return this._normalizeDecision(parsed);
|
||
} catch (e) {
|
||
console.warn('[JsonParser] 代码块中的 JSON 格式错误:', e.message);
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
|
||
/**
|
||
* 提取裸 JSON(最宽松的匹配)
|
||
*/
|
||
static _extractRawJson(response) {
|
||
// 匹配从第一个 { 到最后一个 }
|
||
const jsonMatch = response.match(/\{[\s\S]*\}/);
|
||
if (jsonMatch) {
|
||
try {
|
||
const parsed = JSON.parse(jsonMatch[0]);
|
||
console.log('[JsonParser] 成功提取裸 JSON');
|
||
return this._normalizeDecision(parsed);
|
||
} catch (e) {
|
||
console.warn('[JsonParser] 裸 JSON 格式错误:', e.message);
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
|
||
/**
|
||
* 尝试修复常见 JSON 错误后解析
|
||
*/
|
||
static _extractWithFixing(response) {
|
||
const jsonMatch = response.match(/\{[\s\S]*\}/);
|
||
if (!jsonMatch) return null;
|
||
|
||
let jsonStr = jsonMatch[0];
|
||
|
||
// 修复 1: 移除尾随逗号
|
||
jsonStr = jsonStr.replace(/,\s*([}\]])/g, '$1');
|
||
|
||
// 修复 2: 修复单引号为双引号
|
||
jsonStr = jsonStr.replace(/'/g, '"');
|
||
|
||
// 修复 3: 移除注释
|
||
jsonStr = jsonStr.replace(/\/\/.*$/gm, '');
|
||
jsonStr = jsonStr.replace(/\/\*[\s\S]*?\*\//g, '');
|
||
|
||
try {
|
||
const parsed = JSON.parse(jsonStr);
|
||
console.log('[JsonParser] 成功通过修复提取 JSON');
|
||
return this._normalizeDecision(parsed);
|
||
} catch (e) {
|
||
console.warn('[JsonParser] 修复后的 JSON 仍然错误:', e.message);
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
/**
|
||
* 规范化决策对象,确保字段完整
|
||
*/
|
||
static _normalizeDecision(parsed) {
|
||
if (!parsed || typeof parsed !== 'object') {
|
||
throw new Error('解析结果不是对象');
|
||
}
|
||
|
||
// 验证必须字段
|
||
if (!parsed.action) {
|
||
throw new Error('缺少 action 字段');
|
||
}
|
||
|
||
if (parsed.action === 'answer') {
|
||
return {
|
||
action: 'answer',
|
||
thought: parsed.thought || '',
|
||
answer: parsed.answer || ''
|
||
};
|
||
}
|
||
|
||
if (parsed.action === 'use_tool') {
|
||
// 支持两种格式:单工具和并行工具
|
||
if (parsed.tool_calls && Array.isArray(parsed.tool_calls)) {
|
||
// 并行工具调用
|
||
return {
|
||
action: 'use_tool',
|
||
thought: parsed.thought || '',
|
||
parallel: true,
|
||
tool_calls: parsed.tool_calls.map(call => ({
|
||
tool: call.tool,
|
||
params: call.params || {}
|
||
}))
|
||
};
|
||
} else if (parsed.tool) {
|
||
// 单工具调用
|
||
return {
|
||
action: 'use_tool',
|
||
thought: parsed.thought || '',
|
||
parallel: false,
|
||
tool: parsed.tool,
|
||
params: parsed.params || {}
|
||
};
|
||
} else {
|
||
throw new Error('use_tool 需要指定 tool 或 tool_calls');
|
||
}
|
||
}
|
||
|
||
throw new Error('未知的 action 类型: ' + parsed.action);
|
||
}
|
||
}
|
||
|
||
// 导出到全局
|
||
window.ReActJsonParser = ReActJsonParser;
|
||
|
||
console.log('[ReActJsonParser] 模块已加载');
|
||
|
||
})(window);
|