77 lines
2.7 KiB
TypeScript
77 lines
2.7 KiB
TypeScript
import { Injectable } from "@nestjs/common";
|
|
import { PrismaService } from "../prisma/prisma.service.js";
|
|
import { EmotionService, dominantState } from "../emotion/emotion.service.js";
|
|
import { toResponseStyle } from "../emotion/response-style.js";
|
|
import { RelationshipService } from "../relationship/relationship.service.js";
|
|
import { KeywordMemoryRetriever } from "../memory/memory-retriever.service.js";
|
|
import { WorkingMemoryService } from "../memory/working-memory.service.js";
|
|
import { hashToSeed } from "./seeded-random.js";
|
|
import type { GenerationContext } from "./types.js";
|
|
|
|
export interface AssembleOptions {
|
|
skipMemoryRetrieval?: boolean;
|
|
seed?: number;
|
|
now?: Date;
|
|
}
|
|
|
|
// F-6 上下文組裝器:把人設、當前情緒、檢索到的記憶、關係參數、對話歷史組裝成統一上下文物件。
|
|
@Injectable()
|
|
export class ContextAssemblerService {
|
|
constructor(
|
|
private readonly prisma: PrismaService,
|
|
private readonly emotion: EmotionService,
|
|
private readonly relationship: RelationshipService,
|
|
private readonly retriever: KeywordMemoryRetriever,
|
|
private readonly workingMemory: WorkingMemoryService,
|
|
) {}
|
|
|
|
async assemble(
|
|
characterId: string,
|
|
userId: string,
|
|
sessionId: string,
|
|
userInput: string,
|
|
options: AssembleOptions = {},
|
|
): Promise<GenerationContext> {
|
|
const now = options.now ?? new Date();
|
|
|
|
const character = await this.prisma.client.character.findUniqueOrThrow({ where: { id: characterId } });
|
|
const emotionState = await this.emotion.getState(characterId, now);
|
|
const dominant = dominantState(emotionState);
|
|
const style = toResponseStyle(dominant);
|
|
const { relationship } = await this.relationship.getState(characterId, userId, now);
|
|
|
|
const retrievedMemories = options.skipMemoryRetrieval
|
|
? []
|
|
: await this.retriever.retrieve(characterId, userInput, { relatedUserId: userId });
|
|
|
|
const history = (await this.workingMemory.getContext(sessionId)).map((entry) => ({
|
|
role: entry.role,
|
|
content: entry.content,
|
|
timestamp: entry.timestamp.toISOString(),
|
|
}));
|
|
|
|
const seed = options.seed ?? hashToSeed(`${sessionId}:${history.length}`);
|
|
|
|
return {
|
|
character: {
|
|
id: character.id,
|
|
personalityArchetype: character.personalityArchetype,
|
|
speechStyle: character.speechStyle,
|
|
likesDislikes: character.likesDislikes,
|
|
basicInfo: character.basicInfo,
|
|
},
|
|
emotion: { dominant, style },
|
|
relationship: {
|
|
intimacy: relationship.intimacy,
|
|
trust: relationship.trust,
|
|
stage: relationship.stage,
|
|
},
|
|
retrievedMemories,
|
|
history,
|
|
userInput,
|
|
injectedMemoryIds: retrievedMemories.map((memory) => memory.id),
|
|
seed,
|
|
};
|
|
}
|
|
}
|