refactor(agent): 优化 architectPlan 解析兼容无 steps 的直接回答

- 移除旧的 tryParsePlan 函数,统一使用 parsePlanOutput 解析内容
- 解析 architectChat.content 时兼容没有 steps 的直接回答场景
- 改进错误处理逻辑,直接使用 parsePlanOutput 返回的错误信息
- 新增测试覆盖无 steps 的直接回答恢复场景,确保行为正确
This commit is contained in:
“chenhuachun”
2026-08-28 10:28:15 +08:00
parent 39bc35c77a
commit 5b564cc62f
2 changed files with 36 additions and 14 deletions

View File

@@ -35,15 +35,6 @@ function extractIntent(content: string): string {
return content.trim();
}
/** 尝试从 content 中解析 JSON 计划 */
function tryParsePlan(content: string): PlanResult | null {
const parsed = parseJsonObject<PlanResult>(content);
if (parsed?.intent && Array.isArray(parsed.steps)) {
return parsed;
}
return null;
}
/**
* 从 editor chat 中解析步骤元数据
* 优先读取持久化的 stepMeta 快照(新记录);旧记录回退到正则解析 prompt
@@ -372,8 +363,9 @@ function buildRound(chats: ChatRecord[]): ConversationRound | null {
round.attachments = architectChat.files || undefined;
round.promptSent = architectChat.prompt || '';
// 尝试解析 plan
round.architectPlan = tryParsePlan(architectChat.content || '');
// 与运行时使用相同的解析规则,兼容没有 steps 的直接回答
const parsedPlan = parsePlanOutput(architectChat.content || '');
round.architectPlan = parsedPlan.plan;
// 若 plan 解析失败,从 editor chats 重建最小 plan
if (!round.architectPlan && editorChats.length > 0) {
@@ -408,9 +400,8 @@ function buildRound(chats: ChatRecord[]): ConversationRound | null {
} else if (editorChats.length === 0 && !round.architectPlan) {
const content = architectChat.content || '';
// 模型自报错误({"error": "..."})→ 还原为具体规划失败原因
const parsed = parsePlanOutput(content);
if (parsed.error) {
round.architectError = parsed.error;
if (parsedPlan.error) {
round.architectError = parsedPlan.error;
} else if (content.trim()) {
// 无法解析 plan 且无 stepscontent 作为直接回答
round.architectAnswer = content;

View File

@@ -4,6 +4,37 @@ import { useReplayChat } from '../src/components/widgets/agent/composables/useRe
import type { ConversationRound } from '../src/components/widgets/agent/types/agent';
describe('useReplayChat', () => {
it('restores a direct answer without steps', async () => {
const rounds = ref<ConversationRound[]>([]);
const answer = 'VTJ 可视化设计器的主要使用流程';
const content = JSON.stringify({
intent: '介绍设计器的使用方法',
contextKeys: [],
safety: 'readonly',
answer
});
const { loadChatHistory } = useReplayChat(
{
getChats: vi.fn(async () => [
{
id: 'architect',
agentRole: 'architect',
prompt: '设计器如何使用',
content,
status: 'Success'
}
]) as any,
setStatus: vi.fn()
},
rounds
);
await loadChatHistory('topic');
expect(rounds.value[0].architectAnswer).toBe(answer);
expect(rounds.value[0].architectPlan?.steps).toEqual([]);
});
it('clears the previous conversation before loading an empty topic', async () => {
const rounds = ref([{ id: 'old' }] as ConversationRound[]);
const statusText = ref('');