import { isPromise } from "#Source/basic/index.ts" import { repairJson } from "#Source/json/index.ts" import type { LoggerFriendly, LoggerFriendlyOptions } from "#Source/log/index.ts" import { Logger } from "#Source/log/index.ts" import type { Standard } from "#Source/validation/index.ts" /** * @description 从响应文本中提取第一个 JSON Markdown 代码块。 * * 当前返回值保留原始代码块外壳,即结果仍包含 ```json 与结尾的 ```。 * 如果文本中不存在符合约定的 JSON 代码块,则会抛出异常。 */ export const extractJsonBlock = (response: string): string => { try { const jsonBlockRegex = /```json\s*([\s\S]*?)\n```/gi const match = response.match(jsonBlockRegex) if (match === null || match.length === 0) { throw new Error("No json block found in response.") } return match[0] } catch (exception) { throw new Error(`Failed to extract json block from response`, { cause: exception }) } } /** * @description 定义 JSON 模式响应解析器的构造参数。 */ export interface JsonModeResponseParserOptions< Output extends Standard.Schema, > extends LoggerFriendlyOptions { outputSchema: Output } /** * @description 将 AI 返回文本解析为通过标准 schema 校验的结构化 JSON 结果。 * * 该解析器会按既定顺序执行 JSON 代码块提取、文本修复、JSON.parse 与同步 schema 校验。 */ export class JsonModeResponseParser implements LoggerFriendly { protected readonly options: JsonModeResponseParserOptions readonly logger: Logger private readonly parsedMap: Map> constructor(options: JsonModeResponseParserOptions) { this.options = options this.logger = Logger.fromOptions(options).setDefaultName("JsonModeResponseParser") this.parsedMap = new Map() } /** * @description 从原始文本中抽取并整理可供 JSON.parse 使用的 JSON 内容字符串。 * * 若文本中存在 ```json 代码块,则优先提取该代码块;否则直接把整段文本视为待解析内容。 * 在提取后会尝试去除代码块包裹并调用 JSON 修复能力,但不会在修复失败时抛出额外错误。 */ extractJsonContent(text: string): string { // 尝试从 response 中提取 json block let assumedJsonBlock = text try { assumedJsonBlock = extractJsonBlock(text) } catch { // do nothing } // 将开头的 ```json 和结尾的 ``` 去除。 const assumedJsonContent = assumedJsonBlock .trim() .replace(/^```json/, "") .replace(/```$/, "") // 尝试将 json 修复 let assumedValidJsonContent = assumedJsonContent try { assumedValidJsonContent = repairJson(assumedJsonContent) } catch { // do nothing } return assumedValidJsonContent } /** * @description 解析并校验响应文本,返回满足输出 schema 的结构化结果。 * * 同一段原始文本会命中内部缓存,避免重复解析与重复校验。 */ parse(text: string): Standard.Schema.InferOutput { if (this.parsedMap.has(text)) { return this.parsedMap.get(text)! } const assumedValidJsonContent = this.extractJsonContent(text) // 尝试解析 json const parsedJson = JSON.parse(assumedValidJsonContent) as unknown const validateResult = this.options.outputSchema["~standard"].validate(parsedJson) if (isPromise(validateResult)) { throw new Error("Validation result is a promise, expected synchronous validation.") } if (validateResult.issues !== undefined) { throw new Error(`Variable validation failed: ${JSON.stringify(validateResult.issues)}`) } const value = validateResult.value this.parsedMap.set(text, value) return value } /** * @description 检查响应文本是否能被成功解析并通过 schema 校验。 * * 解析失败时会记录日志并返回 false,而不是向外抛出异常。 */ check(text: string): boolean { try { this.parse(text) return true } catch (exception) { this.logger.log("check error:", exception) this.logger.log("error response:", text) return false } } }