fix(ai): 模型解析 - 兼容多种 JSON 顶层形态

This commit is contained in:
ddCat
2026-09-01 20:57:36 +08:00
6 changed files with 368 additions and 5 deletions

View File

@@ -108,6 +108,30 @@ function findJsonValueEnd(source: string, start: number) {
return -1;
}
// 角色卡数组的最小特征:至少有一个元素带 name 字段。用于把真正的角色数组和模型正文里的
// 旁枝数组(角色名列表、aliases 片段等)区分开,避免提取阶段命中错误片段。
function isCharacterCardArray(value: unknown): value is unknown[] {
if (!Array.isArray(value) || !value.length) return false;
return value.some((item) => Boolean(item) && typeof item === "object" && typeof (item as Record<string, unknown>).name === "string");
}
// 模型偶尔把角色数组再包一层,或把 characters 写成以角色名为键的对象,这里统一摊平成候选列表。
function flattenCharacterCandidates(value: unknown): unknown[] | undefined {
if (Array.isArray(value)) {
const result: unknown[] = [];
value.forEach((item) => {
if (Array.isArray(item)) {
result.push(...(flattenCharacterCandidates(item) ?? []));
return;
}
result.push(item);
});
return result;
}
if (value && typeof value === "object") return Object.values(value as Record<string, unknown>);
return undefined;
}
function extractCharacterBreakdownJson(raw: string) {
for (let start = 0; start < raw.length; start += 1) {
if (raw[start] !== "{" && raw[start] !== "[") continue;
@@ -115,9 +139,11 @@ function extractCharacterBreakdownJson(raw: string) {
if (end < start) continue;
try {
const parsed: unknown = JSON.parse(raw.slice(start, end + 1));
// 顶层角色数组是常见的契约偏离,先判数组再判对象,两者都视为合法载荷。
if (isCharacterCardArray(parsed)) return parsed;
// 模型的推理文字可能包含 aliases: [] 等合法 JSON 片段;角色契约只接受带 characters 字段的对象。
const candidates = parsed && typeof parsed === "object" && !Array.isArray(parsed) ? (parsed as { characters?: unknown }).characters : undefined;
if (Array.isArray(candidates)) return parsed;
const candidates = parsed && typeof parsed === "object" && !Array.isArray(parsed) ? flattenCharacterCandidates((parsed as { characters?: unknown }).characters) : undefined;
if (Array.isArray(candidates) && candidates.length) return parsed;
} catch {
// Ignore unrelated braces in model prose and continue to the next complete JSON value.
}
@@ -128,7 +154,7 @@ function extractCharacterBreakdownJson(raw: string) {
export function parseCharacterBreakdown(raw: string): CharacterBreakdown[] {
const unfenced = raw.trim().replace(/^```(?:json)?\s*/i, "").replace(/\s*```$/i, "");
const parsed = extractCharacterBreakdownJson(unfenced);
const candidates = Array.isArray(parsed) ? parsed : parsed && typeof parsed === "object" ? (parsed as { characters?: unknown }).characters : undefined;
const candidates = Array.isArray(parsed) ? flattenCharacterCandidates(parsed) : flattenCharacterCandidates((parsed as { characters?: unknown }).characters);
if (!Array.isArray(candidates)) throw new Error("角色拆解结果缺少 characters 数组");
const seen = new Set<string>();