mirror of
https://github.com/AventurasTeam/Aventuras.git
synced 2026-04-28 03:40:11 +00:00
implemented final sdk migrations
This commit is contained in:
parent
9723dff31b
commit
ca504fad8e
5 changed files with 206 additions and 66 deletions
44
src/lib/services/ai/sdk/schemas/cardimport.ts
Normal file
44
src/lib/services/ai/sdk/schemas/cardimport.ts
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
/**
|
||||||
|
* Card Import Schemas
|
||||||
|
*
|
||||||
|
* Zod schemas for SillyTavern character card import operations.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NPC extracted from character card.
|
||||||
|
* Maps to the character-card-import template output.
|
||||||
|
*/
|
||||||
|
export const cardImportNpcSchema = z.object({
|
||||||
|
name: z.string().describe("Character's actual name"),
|
||||||
|
role: z.string().describe("Character's role (ally, mentor, antagonist, love interest, guide, friend)"),
|
||||||
|
description: z.string().describe("1-2 sentences: who they are and key appearance details"),
|
||||||
|
personality: z.string().describe("Key personality traits as comma-separated list"),
|
||||||
|
relationship: z.string().describe("Their relationship to the protagonist"),
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Result from character-card-import template.
|
||||||
|
* Cleans SillyTavern cards and converts to Aventura scenario settings.
|
||||||
|
*/
|
||||||
|
export const cardImportResultSchema = z.object({
|
||||||
|
primaryCharacterName: z.string().describe("The ACTUAL name of the main character that {{char}} refers to"),
|
||||||
|
settingSeed: z.string().describe("The FULL cleaned text with {{char}} replaced, {{user}} kept as-is"),
|
||||||
|
npcs: z.array(cardImportNpcSchema).describe("All significant characters from the card"),
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Result from vault-character-import template.
|
||||||
|
* Extracts clean character data for the vault.
|
||||||
|
*/
|
||||||
|
export const vaultCharacterImportSchema = z.object({
|
||||||
|
name: z.string().describe("The character's actual name"),
|
||||||
|
description: z.string().describe("1-2 paragraphs describing who this character is"),
|
||||||
|
traits: z.array(z.string()).describe("3-8 personality traits"),
|
||||||
|
visualDescriptors: z.array(z.string()).describe("Physical appearance details for image generation"),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type CardImportNpc = z.infer<typeof cardImportNpcSchema>;
|
||||||
|
export type CardImportResult = z.infer<typeof cardImportResultSchema>;
|
||||||
|
export type VaultCharacterImport = z.infer<typeof vaultCharacterImportSchema>;
|
||||||
|
|
@ -120,3 +120,20 @@ export const finishLoreManagementSchema = z.object({
|
||||||
});
|
});
|
||||||
|
|
||||||
export type FinishLoreManagementSchema = z.infer<typeof finishLoreManagementSchema>;
|
export type FinishLoreManagementSchema = z.infer<typeof finishLoreManagementSchema>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Classification result for a single entry.
|
||||||
|
*/
|
||||||
|
export const entryClassificationSchema = z.object({
|
||||||
|
index: z.number().describe('Index of the entry being classified'),
|
||||||
|
type: entryTypeSchema.describe('The classified type for this entry'),
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Result from lorebook-classifier template.
|
||||||
|
* Array of entry classifications.
|
||||||
|
*/
|
||||||
|
export const lorebookClassificationResultSchema = z.array(entryClassificationSchema);
|
||||||
|
|
||||||
|
export type EntryClassification = z.infer<typeof entryClassificationSchema>;
|
||||||
|
export type LorebookClassificationResult = z.infer<typeof lorebookClassificationResultSchema>;
|
||||||
|
|
|
||||||
|
|
@ -3,15 +3,16 @@
|
||||||
*
|
*
|
||||||
* Imports SillyTavern character cards (V1/V2 JSON format) into Aventura's wizard.
|
* Imports SillyTavern character cards (V1/V2 JSON format) into Aventura's wizard.
|
||||||
* Supports both JSON files and PNG files with embedded character data.
|
* Supports both JSON files and PNG files with embedded character data.
|
||||||
*
|
|
||||||
* STATUS: PARTIALLY STUBBED - Awaiting SDK migration
|
|
||||||
* - PNG/JSON parsing: WORKING
|
|
||||||
* - LLM conversion: STUBBED
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { StoryMode } from '$lib/types';
|
import type { StoryMode, VisualDescriptors } from '$lib/types';
|
||||||
import type { Genre, GeneratedCharacter } from '$lib/services/ai/wizard/ScenarioService';
|
import type { Genre, GeneratedCharacter } from '$lib/services/ai/wizard/ScenarioService';
|
||||||
import { settings } from '$lib/stores/settings.svelte';
|
import { promptService, type PromptContext } from '$lib/services/prompts';
|
||||||
|
import { generateStructured } from './ai/sdk/generate';
|
||||||
|
import {
|
||||||
|
cardImportResultSchema,
|
||||||
|
vaultCharacterImportSchema,
|
||||||
|
} from './ai/sdk/schemas/cardimport';
|
||||||
import { createLogger } from './ai/core/config';
|
import { createLogger } from './ai/core/config';
|
||||||
|
|
||||||
const log = createLogger('CharacterCardImporter');
|
const log = createLogger('CharacterCardImporter');
|
||||||
|
|
@ -262,7 +263,6 @@ function buildCardContext(card: ParsedCard): string {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert a parsed character card into a scenario setting using LLM.
|
* Convert a parsed character card into a scenario setting using LLM.
|
||||||
* @throws Error - LLM conversion not implemented during SDK migration
|
|
||||||
*/
|
*/
|
||||||
export async function convertCardToScenario(
|
export async function convertCardToScenario(
|
||||||
jsonString: string,
|
jsonString: string,
|
||||||
|
|
@ -290,20 +290,51 @@ export async function convertCardToScenario(
|
||||||
const preprocessedFirstMessage = normalizeUserMacro(card.firstMessage);
|
const preprocessedFirstMessage = normalizeUserMacro(card.firstMessage);
|
||||||
const preprocessedAlternateGreetings = card.alternateGreetings.map(g => normalizeUserMacro(g));
|
const preprocessedAlternateGreetings = card.alternateGreetings.map(g => normalizeUserMacro(g));
|
||||||
|
|
||||||
// Return a basic fallback without LLM conversion
|
// Build card content for LLM
|
||||||
const fallbackDescription = normalizeUserMacro(
|
const cardContent = buildCardContext(card);
|
||||||
[card.scenario, card.description].filter(s => s.trim()).join('\n\n')
|
|
||||||
);
|
// Minimal context for prompt rendering
|
||||||
|
const promptContext: PromptContext = {
|
||||||
|
mode,
|
||||||
|
pov: 'second',
|
||||||
|
tense: 'present',
|
||||||
|
protagonistName: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
const system = promptService.renderPrompt('character-card-import', promptContext);
|
||||||
|
const prompt = promptService.renderUserPrompt('character-card-import', promptContext, {
|
||||||
|
genre,
|
||||||
|
title: cardTitle,
|
||||||
|
cardContent,
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await generateStructured({
|
||||||
|
presetId: 'classification',
|
||||||
|
schema: cardImportResultSchema,
|
||||||
|
system,
|
||||||
|
prompt,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Convert LLM result to CardImportResult format
|
||||||
|
const npcs: GeneratedCharacter[] = result.npcs.map(npc => ({
|
||||||
|
name: npc.name,
|
||||||
|
role: npc.role,
|
||||||
|
description: npc.description,
|
||||||
|
relationship: npc.relationship,
|
||||||
|
traits: npc.personality.split(',').map(t => t.trim()).filter(Boolean),
|
||||||
|
}));
|
||||||
|
|
||||||
|
log('Card import successful', { primaryCharacter: result.primaryCharacterName, npcCount: npcs.length });
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: true,
|
||||||
settingSeed: fallbackDescription.slice(0, 2000),
|
settingSeed: result.settingSeed,
|
||||||
npcs: [],
|
npcs,
|
||||||
primaryCharacterName: cardTitle,
|
primaryCharacterName: result.primaryCharacterName,
|
||||||
storyTitle: cardTitle,
|
storyTitle: result.primaryCharacterName || cardTitle,
|
||||||
firstMessage: preprocessedFirstMessage,
|
firstMessage: preprocessedFirstMessage,
|
||||||
alternateGreetings: preprocessedAlternateGreetings,
|
alternateGreetings: preprocessedAlternateGreetings,
|
||||||
errors: ['LLM conversion not implemented - awaiting SDK migration. Basic extraction was used as fallback.'],
|
errors: [],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -311,12 +342,11 @@ export interface SanitizedCharacter {
|
||||||
name: string;
|
name: string;
|
||||||
description: string;
|
description: string;
|
||||||
traits: string[];
|
traits: string[];
|
||||||
visualDescriptors: import('$lib/types').VisualDescriptors;
|
visualDescriptors: VisualDescriptors;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sanitize a character card using LLM to extract clean character data.
|
* Sanitize a character card using LLM to extract clean character data.
|
||||||
* @throws Error - LLM sanitization not implemented during SDK migration
|
|
||||||
*/
|
*/
|
||||||
export async function sanitizeCharacterCard(
|
export async function sanitizeCharacterCard(
|
||||||
jsonString: string,
|
jsonString: string,
|
||||||
|
|
@ -328,13 +358,41 @@ export async function sanitizeCharacterCard(
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
log('Sanitization not implemented - returning basic extraction');
|
// Build card content for LLM
|
||||||
|
const cardContent = buildCardContext(card);
|
||||||
|
|
||||||
|
// Minimal context for prompt rendering
|
||||||
|
const promptContext: PromptContext = {
|
||||||
|
mode: 'adventure',
|
||||||
|
pov: 'second',
|
||||||
|
tense: 'present',
|
||||||
|
protagonistName: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
const system = promptService.renderPrompt('vault-character-import', promptContext);
|
||||||
|
const prompt = promptService.renderUserPrompt('vault-character-import', promptContext, {
|
||||||
|
cardContent,
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await generateStructured({
|
||||||
|
presetId: 'classification',
|
||||||
|
schema: vaultCharacterImportSchema,
|
||||||
|
system,
|
||||||
|
prompt,
|
||||||
|
});
|
||||||
|
|
||||||
|
log('Character sanitization successful', { name: result.name });
|
||||||
|
|
||||||
|
// Convert array of visual descriptors to VisualDescriptors object
|
||||||
|
const visualDescriptors: VisualDescriptors = {};
|
||||||
|
result.visualDescriptors.forEach((desc, i) => {
|
||||||
|
visualDescriptors[`descriptor_${i}`] = desc;
|
||||||
|
});
|
||||||
|
|
||||||
// Return basic extraction without LLM
|
|
||||||
return {
|
return {
|
||||||
name: card.name,
|
name: result.name,
|
||||||
description: card.description || card.personality || '',
|
description: result.description,
|
||||||
traits: [],
|
traits: result.traits,
|
||||||
visualDescriptors: {},
|
visualDescriptors,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,15 +2,13 @@
|
||||||
* Lorebook Importer Service
|
* Lorebook Importer Service
|
||||||
*
|
*
|
||||||
* Imports lorebooks from various formats (primarily SillyTavern) into Aventura's Entry system.
|
* Imports lorebooks from various formats (primarily SillyTavern) into Aventura's Entry system.
|
||||||
*
|
|
||||||
* STATUS: PARTIALLY STUBBED - Awaiting SDK migration
|
|
||||||
* - Parsing and basic type inference: WORKING
|
|
||||||
* - LLM classification: STUBBED
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Entry, EntryType, EntryInjectionMode, EntryCreator } from '$lib/types';
|
import type { Entry, EntryType, EntryInjectionMode, EntryCreator } from '$lib/types';
|
||||||
import { settings } from '$lib/stores/settings.svelte';
|
|
||||||
import type { StoryMode } from '$lib/services/prompts';
|
import type { StoryMode } from '$lib/services/prompts';
|
||||||
|
import { promptService, type PromptContext } from '$lib/services/prompts';
|
||||||
|
import { generateStructured } from './ai/sdk/generate';
|
||||||
|
import { lorebookClassificationResultSchema } from './ai/sdk/schemas/lorebook';
|
||||||
import { createLogger } from './ai/core/config';
|
import { createLogger } from './ai/core/config';
|
||||||
|
|
||||||
const log = createLogger('LorebookImporter');
|
const log = createLogger('LorebookImporter');
|
||||||
|
|
@ -171,7 +169,7 @@ function inferEntryType(name: string, content: string): EntryType {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* LLM-based entry type classification.
|
* LLM-based entry type classification.
|
||||||
* @throws Error - LLM classification not implemented during SDK migration
|
* Classifies entries in batches to avoid token limits.
|
||||||
*/
|
*/
|
||||||
export async function classifyEntriesWithLLM(
|
export async function classifyEntriesWithLLM(
|
||||||
entries: ImportedEntry[],
|
entries: ImportedEntry[],
|
||||||
|
|
@ -180,14 +178,67 @@ export async function classifyEntriesWithLLM(
|
||||||
): Promise<ImportedEntry[]> {
|
): Promise<ImportedEntry[]> {
|
||||||
if (entries.length === 0) return entries;
|
if (entries.length === 0) return entries;
|
||||||
|
|
||||||
log('LLM classification not implemented - using keyword-based inference');
|
const BATCH_SIZE = 20; // Process in batches to avoid token limits
|
||||||
|
const result = [...entries];
|
||||||
|
let classified = 0;
|
||||||
|
|
||||||
// Return entries as-is with keyword-based types (already set during parsing)
|
// Minimal context for prompt rendering
|
||||||
if (onProgress) {
|
const promptContext: PromptContext = {
|
||||||
onProgress(entries.length, entries.length);
|
mode,
|
||||||
|
pov: 'second',
|
||||||
|
tense: 'present',
|
||||||
|
protagonistName: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
const system = promptService.renderPrompt('lorebook-classifier', promptContext);
|
||||||
|
|
||||||
|
// Process in batches
|
||||||
|
for (let i = 0; i < entries.length; i += BATCH_SIZE) {
|
||||||
|
const batch = entries.slice(i, i + BATCH_SIZE);
|
||||||
|
|
||||||
|
// Build entries JSON for the prompt
|
||||||
|
const entriesJson = JSON.stringify(
|
||||||
|
batch.map((entry, batchIndex) => ({
|
||||||
|
index: batchIndex,
|
||||||
|
name: entry.name,
|
||||||
|
content: entry.description.slice(0, 500), // Limit content length
|
||||||
|
keywords: entry.keywords.slice(0, 10),
|
||||||
|
})),
|
||||||
|
null,
|
||||||
|
2
|
||||||
|
);
|
||||||
|
|
||||||
|
const prompt = promptService.renderUserPrompt('lorebook-classifier', promptContext, {
|
||||||
|
entriesJson,
|
||||||
|
});
|
||||||
|
|
||||||
|
const classifications = await generateStructured({
|
||||||
|
presetId: 'classification',
|
||||||
|
schema: lorebookClassificationResultSchema,
|
||||||
|
system,
|
||||||
|
prompt,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Apply classifications to batch
|
||||||
|
for (const classification of classifications) {
|
||||||
|
const globalIndex = i + classification.index;
|
||||||
|
if (globalIndex < result.length) {
|
||||||
|
result[globalIndex] = {
|
||||||
|
...result[globalIndex],
|
||||||
|
type: classification.type as EntryType,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
classified += batch.length;
|
||||||
|
if (onProgress) {
|
||||||
|
onProgress(classified, entries.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
log('Classified batch', { batch: i / BATCH_SIZE + 1, classified, total: entries.length });
|
||||||
}
|
}
|
||||||
|
|
||||||
return entries;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
function determineInjectionMode(entry: SillyTavernEntry): EntryInjectionMode {
|
function determineInjectionMode(entry: SillyTavernEntry): EntryInjectionMode {
|
||||||
|
|
|
||||||
|
|
@ -2746,34 +2746,6 @@ Respond with ONLY the translated text, no explanations.`,
|
||||||
userContent: `{{content}}`,
|
userContent: `{{content}}`,
|
||||||
};
|
};
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// AGENTIC RETRY PROMPTS
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Prompt sent when the agentic retrieval agent doesn't make tool calls.
|
|
||||||
* Used to encourage the model to continue using tools or finish.
|
|
||||||
*/
|
|
||||||
const agenticRetrievalRetryTemplate: PromptTemplate = {
|
|
||||||
id: 'agentic-retrieval-retry',
|
|
||||||
name: 'Agentic Retrieval Retry',
|
|
||||||
category: 'service',
|
|
||||||
description: 'Prompt sent when agentic retrieval agent stops calling tools',
|
|
||||||
content: 'Please use the available tools to gather relevant context, or call finish_retrieval when you are done.',
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Prompt sent when the lore management agent doesn't make tool calls.
|
|
||||||
* Used to encourage the model to continue using tools or finish.
|
|
||||||
*/
|
|
||||||
const loreManagementRetryTemplate: PromptTemplate = {
|
|
||||||
id: 'lore-management-retry',
|
|
||||||
name: 'Lore Management Retry',
|
|
||||||
category: 'service',
|
|
||||||
description: 'Prompt sent when lore management agent stops calling tools',
|
|
||||||
content: 'Please use the available tools to make any necessary changes, or call finish_lore_management if you are done reviewing the lorebook.',
|
|
||||||
};
|
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// COMBINED PROMPT TEMPLATES
|
// COMBINED PROMPT TEMPLATES
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
@ -2796,8 +2768,6 @@ export const PROMPT_TEMPLATES: PromptTemplate[] = [
|
||||||
loreManagementPromptTemplate,
|
loreManagementPromptTemplate,
|
||||||
interactiveLorebookPromptTemplate,
|
interactiveLorebookPromptTemplate,
|
||||||
agenticRetrievalPromptTemplate,
|
agenticRetrievalPromptTemplate,
|
||||||
agenticRetrievalRetryTemplate,
|
|
||||||
loreManagementRetryTemplate,
|
|
||||||
characterCardImportPromptTemplate,
|
characterCardImportPromptTemplate,
|
||||||
vaultCharacterImportPromptTemplate,
|
vaultCharacterImportPromptTemplate,
|
||||||
imagePromptAnalysisTemplate,
|
imagePromptAnalysisTemplate,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue