paper-burner/js/chatbot/react/json-parser.js

165 lines
4.8 KiB
JavaScript
Raw 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.

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