Integrate role asset studio into custom world agent flow
This commit is contained in:
@@ -5,12 +5,15 @@ import type {
|
||||
CharacterChatSuggestionsRequest,
|
||||
} from '../../../../packages/shared/src/contracts/story.js';
|
||||
import { createTestPlayerCharacter } from '../../testFixtures/runtimeCharacter.js';
|
||||
import { CHARACTER_PANEL_CHAT_SUGGESTION_SYSTEM_PROMPT } from './chatPromptBuilders.js';
|
||||
import { SYSTEM_PROMPT } from './storyPromptBuilders.js';
|
||||
import {
|
||||
generateCharacterChatSuggestionsFromOrchestrator,
|
||||
} from './chatOrchestrator.js';
|
||||
import { CHARACTER_PANEL_CHAT_SUGGESTION_SYSTEM_PROMPT } from './chatPromptBuilders.js';
|
||||
import {
|
||||
generateCustomWorldProfileFromOrchestrator,
|
||||
} from './customWorldOrchestrator.js';
|
||||
import { generateInitialStoryFromOrchestrator } from './storyOrchestrator.js';
|
||||
import { SYSTEM_PROMPT } from './storyPromptBuilders.js';
|
||||
|
||||
type TestStoryContext = Parameters<typeof generateInitialStoryFromOrchestrator>[4];
|
||||
type TestStoryOption = Awaited<
|
||||
@@ -191,3 +194,105 @@ test('chat orchestrator builds character suggestion prompts on the server side',
|
||||
assert.match(capturedPrompts[0]?.userPrompt ?? '', /两人刚在客栈里察觉到不寻常的动静/u);
|
||||
assert.match(capturedPrompts[0]?.userPrompt ?? '', new RegExp(payload.targetCharacter.name, 'u'));
|
||||
});
|
||||
|
||||
test('custom world orchestrator requests LLM content before compiling the profile', async () => {
|
||||
const capturedPrompts: Array<{ systemPrompt: string; userPrompt: string }> = [];
|
||||
const storyNpcNames = Array.from(
|
||||
{ length: 8 },
|
||||
(_, index) => `潮灯见证者${index + 1}`,
|
||||
);
|
||||
const llmClient = {
|
||||
requestMessageContent: async ({
|
||||
systemPrompt,
|
||||
userPrompt,
|
||||
}: {
|
||||
systemPrompt: string;
|
||||
userPrompt: string;
|
||||
}) => {
|
||||
capturedPrompts.push({ systemPrompt, userPrompt });
|
||||
return JSON.stringify({
|
||||
name: '潮灯列岛',
|
||||
subtitle: '雾潮之下',
|
||||
summary: '旧灯塔、潮雾与沉船盟约纠缠出的列岛冒险。',
|
||||
tone: '潮湿、悬疑、克制',
|
||||
playerGoal: '查明潮雾为何吞掉守灯人的名字',
|
||||
templateWorldType: 'WUXIA',
|
||||
majorFactions: ['守灯会', '沉船商盟', '潮雾祭司'],
|
||||
coreConflicts: ['守灯会与沉船商盟争夺航道解释权'],
|
||||
camp: {
|
||||
name: '旧灯塔下层',
|
||||
description: '潮水退去时才露出的临时据点。',
|
||||
dangerLevel: 'low',
|
||||
},
|
||||
playableNpcs: Array.from({ length: 3 }, (_, index) => ({
|
||||
name: `守灯旅人${index + 1}`,
|
||||
title: `第${index + 1}盏灯`,
|
||||
role: '守灯同行者',
|
||||
description: '在潮雾边缘辨认灯火与人声。',
|
||||
backstory: '曾经守过一座被除名的灯塔。',
|
||||
personality: '谨慎、沉静、记仇',
|
||||
motivation: '找回被潮雾吞掉的名字。',
|
||||
combatStyle: '短刃牵制后借灯火逼退敌人。',
|
||||
initialAffinity: 18,
|
||||
relationshipHooks: ['守灯', '旧名'],
|
||||
tags: ['潮雾', '灯塔'],
|
||||
})),
|
||||
storyNpcs: storyNpcNames.map((name, index) => ({
|
||||
name,
|
||||
title: `第${index + 1}位见证者`,
|
||||
role: '潮雾见证者',
|
||||
description: '知道一段被潮水洗掉的航线传闻。',
|
||||
backstory: '在沉船夜里听见过不该出现的钟声。',
|
||||
personality: '警觉、克制',
|
||||
motivation: '确认下一次潮雾会带走谁。',
|
||||
combatStyle: '先试探再撤入雾中。',
|
||||
initialAffinity: 6,
|
||||
relationshipHooks: ['沉船夜', '钟声'],
|
||||
tags: ['潮雾', '线索'],
|
||||
})),
|
||||
landmarks: Array.from({ length: 4 }, (_, index) => ({
|
||||
name: `潮灯地标${index + 1}`,
|
||||
description: '潮雾会在这里折回,留下盐痕和旧灯影。',
|
||||
dangerLevel: index === 0 ? 'medium' : 'high',
|
||||
sceneNpcNames: storyNpcNames.slice(index, index + 3),
|
||||
connections: [
|
||||
{
|
||||
targetLandmarkName: `潮灯地标${(index + 1) % 4 + 1}`,
|
||||
relativePosition: 'forward',
|
||||
summary: '沿潮痕继续前行即可抵达下一处灯影。',
|
||||
},
|
||||
],
|
||||
})),
|
||||
items: [],
|
||||
});
|
||||
},
|
||||
} as const;
|
||||
const progressEvents: Array<{ phaseId: string; overallProgress: number }> = [];
|
||||
|
||||
const profile = await generateCustomWorldProfileFromOrchestrator(
|
||||
llmClient as never,
|
||||
{
|
||||
settingText: '一个被潮雾与失落列岛切碎的边境世界。',
|
||||
generationMode: 'fast',
|
||||
},
|
||||
{
|
||||
onProgress: (progress) => {
|
||||
progressEvents.push({
|
||||
phaseId: progress.phaseId,
|
||||
overallProgress: progress.overallProgress,
|
||||
});
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
assert.equal(capturedPrompts.length, 1);
|
||||
assert.match(capturedPrompts[0]?.systemPrompt ?? '', /JSON 生成器/u);
|
||||
assert.match(capturedPrompts[0]?.userPrompt ?? '', /生成模式:fast/u);
|
||||
assert.match(capturedPrompts[0]?.userPrompt ?? '', /潮雾与失落列岛/u);
|
||||
assert.equal(profile.name, '潮灯列岛');
|
||||
assert.equal(profile.generationMode, 'fast');
|
||||
assert.equal(profile.generationStatus, 'key_only');
|
||||
assert.equal((profile.playableNpcs as unknown[]).length, 3);
|
||||
assert.ok(progressEvents.some((event) => event.phaseId === 'llm-profile'));
|
||||
assert.equal(progressEvents.at(-1)?.overallProgress, 100);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user