1
This commit is contained in:
@@ -356,6 +356,12 @@ function buildNpcDialoguePromptBase(
|
||||
payload: NpcChatDialogueRequest | NpcChatTurnRequest | NpcRecruitDialogueRequest,
|
||||
) {
|
||||
const encounter = describeEncounter(payload.encounter);
|
||||
const character =
|
||||
(payload as NpcChatTurnRequest).character ??
|
||||
(payload as NpcChatTurnRequest).player;
|
||||
if (!(payload as NpcChatTurnRequest).character && character) {
|
||||
(payload as NpcChatTurnRequest).character = character;
|
||||
}
|
||||
|
||||
return [
|
||||
`世界:${describeWorld(payload.worldType)}`,
|
||||
@@ -422,12 +428,16 @@ export function buildNpcChatTurnReplyPrompt(
|
||||
) {
|
||||
const encounter = describeEncounter(payload.encounter);
|
||||
const npcState = asRecord(payload.npcState);
|
||||
const conversationHistory =
|
||||
Array.isArray(payload.conversationHistory) && payload.conversationHistory.length > 0
|
||||
? payload.conversationHistory
|
||||
: payload.dialogue ?? payload.conversationHistory ?? [];
|
||||
const affinity = readNumber(npcState?.affinity, 0);
|
||||
const chattedCount = readNumber(npcState?.chattedCount, 0);
|
||||
|
||||
return [
|
||||
buildNpcDialoguePromptBase(payload),
|
||||
describeNpcConversationHistory(payload.conversationHistory, encounter.npcName),
|
||||
describeNpcConversationHistory(conversationHistory, encounter.npcName),
|
||||
`当前关系值:${affinity}`,
|
||||
`已聊天轮次:${chattedCount}`,
|
||||
`玩家刚刚说:${payload.playerMessage}`,
|
||||
@@ -442,10 +452,14 @@ export function buildNpcChatTurnSuggestionPrompt(
|
||||
npcReply: string,
|
||||
) {
|
||||
const encounter = describeEncounter(payload.encounter);
|
||||
const conversationHistory =
|
||||
Array.isArray(payload.conversationHistory) && payload.conversationHistory.length > 0
|
||||
? payload.conversationHistory
|
||||
: payload.dialogue ?? payload.conversationHistory ?? [];
|
||||
|
||||
return [
|
||||
buildNpcDialoguePromptBase(payload),
|
||||
describeNpcConversationHistory(payload.conversationHistory, encounter.npcName),
|
||||
describeNpcConversationHistory(conversationHistory, encounter.npcName),
|
||||
`玩家刚刚说:${payload.playerMessage}`,
|
||||
`NPC 刚刚回复:${npcReply}`,
|
||||
`请围绕刚刚这轮对话,为玩家生成 3 条可以继续和 ${encounter.npcName} 聊下去的中文短句候选。`,
|
||||
|
||||
43
server-node/src/services/chatService.test.ts
Normal file
43
server-node/src/services/chatService.test.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
|
||||
import { npcChatTurnRequestSchema } from './chatService.js';
|
||||
|
||||
test('npc chat turn schema normalizes player and dialogue aliases', () => {
|
||||
const payload = npcChatTurnRequestSchema.parse({
|
||||
worldType: 'WUXIA',
|
||||
player: {
|
||||
id: 'hero',
|
||||
name: '沈行',
|
||||
},
|
||||
encounter: {
|
||||
id: 'npc-liu',
|
||||
npcName: '柳无声',
|
||||
},
|
||||
monsters: [],
|
||||
history: [],
|
||||
context: {
|
||||
sceneName: '客栈内室',
|
||||
},
|
||||
dialogue: [
|
||||
{
|
||||
speaker: 'player',
|
||||
text: '你刚才那句话是什么意思?',
|
||||
},
|
||||
],
|
||||
playerMessage: '你能说得再明白一点吗?',
|
||||
npcState: {
|
||||
affinity: 4,
|
||||
chattedCount: 1,
|
||||
recruited: false,
|
||||
},
|
||||
});
|
||||
|
||||
assert.equal(payload.character.name, '沈行');
|
||||
assert.deepEqual(payload.conversationHistory, [
|
||||
{
|
||||
speaker: 'player',
|
||||
text: '你刚才那句话是什么意思?',
|
||||
},
|
||||
]);
|
||||
});
|
||||
@@ -23,7 +23,8 @@ const baseCharacterChatSchema = z.object({
|
||||
|
||||
const baseNpcChatSchema = z.object({
|
||||
worldType: z.string().trim().min(1),
|
||||
character: jsonObjectSchema,
|
||||
character: jsonObjectSchema.optional(),
|
||||
player: jsonObjectSchema.optional(),
|
||||
encounter: jsonObjectSchema,
|
||||
monsters: z.array(jsonObjectSchema).default([]),
|
||||
history: z.array(jsonObjectSchema).default([]),
|
||||
@@ -47,17 +48,35 @@ export const characterChatSummaryRequestSchema = baseCharacterChatSchema.extend(
|
||||
) satisfies z.ZodType<CharacterChatSummaryRequest>;
|
||||
|
||||
export const npcChatDialogueRequestSchema = baseNpcChatSchema.extend({
|
||||
character: jsonObjectSchema,
|
||||
topic: z.string().trim().min(1),
|
||||
resultSummary: z.string().optional().default(''),
|
||||
}) satisfies z.ZodType<NpcChatDialogueRequest>;
|
||||
|
||||
export const npcChatTurnRequestSchema = baseNpcChatSchema.extend({
|
||||
conversationHistory: z.array(jsonObjectSchema).default([]),
|
||||
playerMessage: z.string().trim().min(1),
|
||||
npcState: jsonObjectSchema,
|
||||
}) satisfies z.ZodType<NpcChatTurnRequest>;
|
||||
export const npcChatTurnRequestSchema = baseNpcChatSchema
|
||||
.extend({
|
||||
conversationHistory: z.array(jsonObjectSchema).optional(),
|
||||
dialogue: z.array(jsonObjectSchema).optional(),
|
||||
playerMessage: z.string().trim().min(1),
|
||||
npcState: jsonObjectSchema,
|
||||
})
|
||||
.superRefine((value, ctx) => {
|
||||
if (!value.character && !value.player) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: 'npc chat turn request requires character or player',
|
||||
path: ['character'],
|
||||
});
|
||||
}
|
||||
})
|
||||
.transform((value) => ({
|
||||
...value,
|
||||
character: value.character ?? value.player ?? {},
|
||||
conversationHistory: value.conversationHistory ?? value.dialogue ?? [],
|
||||
})) satisfies z.ZodType<NpcChatTurnRequest>;
|
||||
|
||||
export const npcRecruitDialogueRequestSchema = baseNpcChatSchema.extend({
|
||||
character: jsonObjectSchema,
|
||||
invitationText: z.string().trim().min(1),
|
||||
recruitSummary: z.string().optional().default(''),
|
||||
}) satisfies z.ZodType<NpcRecruitDialogueRequest>;
|
||||
|
||||
Reference in New Issue
Block a user