代码修改
This commit is contained in:
1 parent
ef1481be63
commit
a4b7ab5e9d
168 files changed
+76575
No files matched your search
@@ -0,0 +1,322 @@
|
||||
export interface AgentStepItem {
|
||||
title: string;
|
||||
content: string;
|
||||
}
|
||||
|
||||
export interface AgentParsed {
|
||||
steps: AgentStepItem[];
|
||||
thinkingMarkdown: string;
|
||||
answerMarkdown: string;
|
||||
thinkingComplete: boolean;
|
||||
}
|
||||
|
||||
const STEP_RE = /<step\s+title="([^"]+)">([\s\S]*?)<\/step>/g;
|
||||
const ANSWER_RE = /<answer>([\s\S]*?)<\/answer>/;
|
||||
const THINKING_COMMENT_RE = /<!--\s*thinking:\s*([\s\S]*?)\s*-->/i;
|
||||
const WAITING_PREFIX = '仍在等待模型首个 token...';
|
||||
|
||||
const stripCodeFence = (raw: string): string => {
|
||||
const trimmed = raw.trim();
|
||||
const match = /```(?:json)?\s*([\s\S]*?)```/i.exec(trimmed);
|
||||
if (match) return match[1].trim();
|
||||
return trimmed.replace(/^```(?:json)?\s*/i, '').trim();
|
||||
};
|
||||
|
||||
const stripThinkTags = (
|
||||
text: string,
|
||||
): { remainder: string; thinkBlock: string; closed: boolean } => {
|
||||
const start = text.indexOf('<think>');
|
||||
if (start === -1) return { remainder: text, thinkBlock: '', closed: false };
|
||||
const after = text.slice(start + '<think>'.length);
|
||||
const endIdx = after.indexOf('</think>');
|
||||
if (endIdx === -1) {
|
||||
return {
|
||||
remainder: text.slice(0, start),
|
||||
thinkBlock: after,
|
||||
closed: false,
|
||||
};
|
||||
}
|
||||
return {
|
||||
remainder: text.slice(0, start) + after.slice(endIdx + '</think>'.length),
|
||||
thinkBlock: after.slice(0, endIdx),
|
||||
closed: true,
|
||||
};
|
||||
};
|
||||
|
||||
const mergeStep = (steps: AgentStepItem[], nextStep: AgentStepItem) => {
|
||||
const existingIndex = steps.findIndex((step) => step.title === nextStep.title);
|
||||
if (existingIndex === -1) {
|
||||
steps.push(nextStep);
|
||||
return;
|
||||
}
|
||||
|
||||
const existing = steps[existingIndex];
|
||||
const nextIsWaiting = nextStep.content.startsWith(WAITING_PREFIX);
|
||||
const existingLines = existing.content
|
||||
.split('\n')
|
||||
.map((line) => line.trim())
|
||||
.filter(Boolean);
|
||||
const stableLines = existingLines.filter((line) => !line.startsWith(WAITING_PREFIX));
|
||||
|
||||
if (nextIsWaiting) {
|
||||
const mergedLines = stableLines.length > 0 ? [...stableLines, nextStep.content] : [nextStep.content];
|
||||
steps[existingIndex] = {
|
||||
...existing,
|
||||
content: mergedLines.join('\n'),
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
const normalizedLines = stableLines.includes(nextStep.content)
|
||||
? stableLines
|
||||
: [...stableLines, nextStep.content];
|
||||
steps[existingIndex] = {
|
||||
...existing,
|
||||
content: normalizedLines.join('\n'),
|
||||
};
|
||||
};
|
||||
|
||||
const extractSteps = (text: string): { steps: AgentStepItem[]; remainder: string } => {
|
||||
const steps: AgentStepItem[] = [];
|
||||
const remainder = text.replace(STEP_RE, (_, title: string, content: string) => {
|
||||
mergeStep(steps, { title: title.trim(), content: content.trim() });
|
||||
return '';
|
||||
});
|
||||
return { steps, remainder };
|
||||
};
|
||||
|
||||
const extractTaggedSection = (
|
||||
text: string,
|
||||
re: RegExp,
|
||||
): { value: string; remainder: string } => {
|
||||
const match = re.exec(text);
|
||||
if (!match) return { value: '', remainder: text };
|
||||
return {
|
||||
value: match[1].trim(),
|
||||
remainder: text.replace(match[0], '').trim(),
|
||||
};
|
||||
};
|
||||
|
||||
const extractJsonStringField = (
|
||||
text: string,
|
||||
field: string,
|
||||
): { value: string; complete: boolean; matched: boolean } => {
|
||||
const re = new RegExp(`"${field}"\\s*:\\s*"`);
|
||||
const match = re.exec(text);
|
||||
if (!match) return { value: '', complete: false, matched: false };
|
||||
let i = match.index + match[0].length;
|
||||
let value = '';
|
||||
while (i < text.length) {
|
||||
const ch = text[i];
|
||||
if (ch === '\\') {
|
||||
if (i + 1 >= text.length) return { value, complete: false, matched: true };
|
||||
const next = text[i + 1];
|
||||
switch (next) {
|
||||
case 'n':
|
||||
value += '\n';
|
||||
break;
|
||||
case 't':
|
||||
value += '\t';
|
||||
break;
|
||||
case 'r':
|
||||
value += '\r';
|
||||
break;
|
||||
case '"':
|
||||
value += '"';
|
||||
break;
|
||||
case '\\':
|
||||
value += '\\';
|
||||
break;
|
||||
case '/':
|
||||
value += '/';
|
||||
break;
|
||||
case 'b':
|
||||
value += '\b';
|
||||
break;
|
||||
case 'f':
|
||||
value += '\f';
|
||||
break;
|
||||
case 'u':
|
||||
if (i + 5 < text.length) {
|
||||
const hex = text.slice(i + 2, i + 6);
|
||||
value += String.fromCharCode(parseInt(hex, 16));
|
||||
i += 6;
|
||||
continue;
|
||||
}
|
||||
return { value, complete: false, matched: true };
|
||||
default:
|
||||
value += next;
|
||||
}
|
||||
i += 2;
|
||||
} else if (ch === '"') {
|
||||
return { value, complete: true, matched: true };
|
||||
} else {
|
||||
value += ch;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
return { value, complete: false, matched: true };
|
||||
};
|
||||
|
||||
const extractItems = (text: string): unknown[] => {
|
||||
const re = /"items"\s*:\s*\[/;
|
||||
const match = re.exec(text);
|
||||
if (!match) return [];
|
||||
let i = match.index + match[0].length;
|
||||
const out: unknown[] = [];
|
||||
while (i < text.length) {
|
||||
while (i < text.length && /[\s,]/.test(text[i])) i++;
|
||||
if (i >= text.length) break;
|
||||
if (text[i] === ']') break;
|
||||
if (text[i] !== '{') {
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
let depth = 0;
|
||||
let inStr = false;
|
||||
let escape = false;
|
||||
let j = i;
|
||||
for (; j < text.length; j++) {
|
||||
const c = text[j];
|
||||
if (escape) {
|
||||
escape = false;
|
||||
continue;
|
||||
}
|
||||
if (inStr) {
|
||||
if (c === '\\') escape = true;
|
||||
else if (c === '"') inStr = false;
|
||||
} else if (c === '"') inStr = true;
|
||||
else if (c === '{') depth++;
|
||||
else if (c === '}') {
|
||||
depth--;
|
||||
if (depth === 0) {
|
||||
j++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (depth !== 0) break;
|
||||
try {
|
||||
out.push(JSON.parse(text.slice(i, j)));
|
||||
} catch {
|
||||
// ignore partial item
|
||||
}
|
||||
i = j;
|
||||
}
|
||||
return out;
|
||||
};
|
||||
|
||||
const itemToMarkdown = (item: unknown, idx: number): string => {
|
||||
if (item == null) return `${idx + 1}.`;
|
||||
if (typeof item !== 'object') return `${idx + 1}. ${String(item)}`;
|
||||
const o = item as Record<string, unknown>;
|
||||
const title =
|
||||
(typeof o.title === 'string' && o.title) ||
|
||||
(typeof o.expected_behavior === 'string' && o.expected_behavior) ||
|
||||
(typeof o.name === 'string' && o.name) ||
|
||||
`条目 ${idx + 1}`;
|
||||
const body =
|
||||
(typeof o.content === 'string' && o.content) ||
|
||||
(typeof o.reason === 'string' && o.reason) ||
|
||||
'';
|
||||
|
||||
const lines: string[] = [`**${idx + 1}. ${title}**`];
|
||||
if (body) lines.push(body);
|
||||
|
||||
if (typeof o.expected_behavior === 'string' && typeof o.driving_factor === 'string') {
|
||||
if (!o.title) {
|
||||
lines.length = 0;
|
||||
lines.push(`**${idx + 1}. ${o.expected_behavior}**`);
|
||||
}
|
||||
lines.push(`_驱动因素_:${o.driving_factor}`);
|
||||
}
|
||||
|
||||
if (Array.isArray(o.references) && o.references.length > 0) {
|
||||
const refs = (o.references as unknown[])
|
||||
.filter((x) => typeof x === 'string' || typeof x === 'number')
|
||||
.map((x) => `\`${String(x)}\``)
|
||||
.join(' ');
|
||||
if (refs) lines.push(`引用:${refs}`);
|
||||
}
|
||||
|
||||
return lines.join('\n\n');
|
||||
};
|
||||
|
||||
const itemsToMarkdown = (items: unknown[]): string =>
|
||||
items.map((item, idx) => itemToMarkdown(item, idx)).join('\n\n---\n\n');
|
||||
|
||||
export const parseAgentResponse = (raw: string): AgentParsed => {
|
||||
if (!raw) {
|
||||
return {
|
||||
steps: [],
|
||||
thinkingMarkdown: '',
|
||||
answerMarkdown: '',
|
||||
thinkingComplete: false,
|
||||
};
|
||||
}
|
||||
|
||||
const stripped = stripThinkTags(raw);
|
||||
const stepsResult = extractSteps(stripped.remainder);
|
||||
let body = stripCodeFence(stepsResult.remainder);
|
||||
const extraThinking = stripped.thinkBlock;
|
||||
let thinkingComplete = stripped.closed;
|
||||
|
||||
const answerSection = extractTaggedSection(body, ANSWER_RE);
|
||||
body = answerSection.remainder;
|
||||
const thinkingCommentSection = extractTaggedSection(body, THINKING_COMMENT_RE);
|
||||
body = thinkingCommentSection.remainder;
|
||||
|
||||
if (body) {
|
||||
const start = body.indexOf('{');
|
||||
if (start !== -1) body = body.slice(start);
|
||||
}
|
||||
|
||||
if (body) {
|
||||
try {
|
||||
const obj = JSON.parse(body) as Record<string, unknown>;
|
||||
if (obj && typeof obj === 'object') {
|
||||
const thinking = typeof obj.thinking === 'string' ? obj.thinking : '';
|
||||
const items = Array.isArray(obj.items) ? (obj.items as unknown[]) : [];
|
||||
const answerMarkdown = items.length > 0 ? itemsToMarkdown(items) : answerSection.value;
|
||||
return {
|
||||
steps: stepsResult.steps,
|
||||
thinkingMarkdown: [extraThinking, thinking, thinkingCommentSection.value]
|
||||
.filter(Boolean)
|
||||
.join('\n\n'),
|
||||
answerMarkdown,
|
||||
thinkingComplete: true,
|
||||
};
|
||||
}
|
||||
} catch {
|
||||
// fall through
|
||||
}
|
||||
}
|
||||
|
||||
const thinkingField = extractJsonStringField(body, 'thinking');
|
||||
const items = extractItems(body);
|
||||
|
||||
if (thinkingField.complete) thinkingComplete = true;
|
||||
|
||||
let fallbackAnswer = answerSection.value || body.trim();
|
||||
if (items.length > 0) {
|
||||
fallbackAnswer = itemsToMarkdown(items);
|
||||
}
|
||||
|
||||
if (!thinkingField.matched && items.length === 0 && !extraThinking) {
|
||||
return {
|
||||
steps: stepsResult.steps,
|
||||
thinkingMarkdown: '',
|
||||
answerMarkdown: fallbackAnswer,
|
||||
thinkingComplete: false,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
steps: stepsResult.steps,
|
||||
thinkingMarkdown: [extraThinking, thinkingField.value, thinkingCommentSection.value]
|
||||
.filter(Boolean)
|
||||
.join('\n\n'),
|
||||
answerMarkdown: fallbackAnswer,
|
||||
thinkingComplete,
|
||||
};
|
||||
};
|
||||
Reference in new issue
Block a user