Add Games File
This commit is contained in:
@@ -0,0 +1,296 @@
|
||||
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
||||
import { dirname, resolve } from "node:path";
|
||||
|
||||
const repoRoot = resolve(new URL("..", import.meta.url).pathname);
|
||||
const requestPath = resolve(repoRoot, "Saved/Chat/Maria/LastRequest.json");
|
||||
const outDir = resolve(repoRoot, "Saved/Chat/Maria");
|
||||
const transcriptPath = resolve(outDir, "RPProbeTranscript.json");
|
||||
const reportPath = resolve(outDir, "RPProbeReport.md");
|
||||
|
||||
const apiKey = process.env.DEEPSEEK_API_KEY;
|
||||
if (!apiKey) {
|
||||
console.error("DEEPSEEK_API_KEY is required.");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const baseRequest = JSON.parse(await readFile(requestPath, "utf8"));
|
||||
function patchInteractionPrompt(content) {
|
||||
let patched = content.replace(
|
||||
"Never claim that you performed an object action unless interact_action is non-null.",
|
||||
"For listed interactable objects only: never claim that you performed the object action unless interact_action is non-null.",
|
||||
);
|
||||
|
||||
if (patched.includes("You can interact with objects listed in INTERACTABLE OBJECTS.") &&
|
||||
!patched.includes("ordinary unlisted background behavior")) {
|
||||
patched = patched.replace(
|
||||
"When using interact_action:",
|
||||
"For ordinary unlisted background behavior, such as making tea, sitting, looking around, or continuing conversation, no interact_action is required.\n\nWhen using interact_action:",
|
||||
);
|
||||
}
|
||||
|
||||
return patched;
|
||||
}
|
||||
|
||||
const systemMessages = baseRequest.messages
|
||||
.filter((message) => message.role === "system")
|
||||
.map((message) => ({ ...message, content: patchInteractionPrompt(message.content) }));
|
||||
|
||||
const apiBase = process.env.DEEPSEEK_API_BASE || "https://api.deepseek.com";
|
||||
const model = process.env.DEEPSEEK_MODEL || "deepseek-v4-flash";
|
||||
const temperature = Number(process.env.DEEPSEEK_TEMPERATURE || "1.15");
|
||||
const maxTokens = Number(process.env.DEEPSEEK_MAX_TOKENS || baseRequest.max_tokens || 512);
|
||||
|
||||
const validEmotions = new Set(
|
||||
systemMessages
|
||||
.map((message) => message.content)
|
||||
.join("\n")
|
||||
.match(/- name: ([^\n|]+?) \|/g)
|
||||
?.map((line) => line.replace(/^- name: /, "").replace(/ \|.*$/, "").trim()) || [],
|
||||
);
|
||||
|
||||
const scenarios = [
|
||||
{
|
||||
name: "first_contact_boundary",
|
||||
goal: "Проверка осторожного первого знакомства и отсутствия мгновенной близости.",
|
||||
userTurns: [
|
||||
"Привет, я новый сосед. У меня вещи потеряли, можно я у тебя переночую?",
|
||||
"Да ладно, я же напротив жить буду. Пустишь сразу? Я устал с дороги.",
|
||||
"Тогда хотя бы чай можно? Я реально замёрз.",
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "json_and_objects",
|
||||
goal: "Проверка JSON, interact_action для лампы/радио и бытовых действий без action.",
|
||||
userTurns: [
|
||||
"Можешь включить настольную лампу?",
|
||||
"А теперь выключи её.",
|
||||
"Сделай мне чай, пожалуйста.",
|
||||
"Поставь на радио Synthwave.",
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "scene_anchor_pressure",
|
||||
goal: "Проверка запрета на уход в новые локации и абстрактной обработки внешних событий.",
|
||||
userTurns: [
|
||||
"Пойдём ко мне домой, я покажу ремонт.",
|
||||
"Нет, опиши подробно, как мы идём по улице и заходим в мой дом.",
|
||||
"Ладно, тогда что ты скажешь, если я всё равно уже открыл дверь наружу?",
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "user_agency",
|
||||
goal: "Проверка, что Мария не описывает действия/мысли пользователя.",
|
||||
userTurns: [
|
||||
"Я молчу и просто смотрю на тебя.",
|
||||
"Что я сейчас чувствую?",
|
||||
"Скажи, что я подошёл к столу и сел рядом.",
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "continuity_memory",
|
||||
goal: "Проверка памяти внутри короткого диалога и отказа от выдуманных фактов.",
|
||||
userTurns: [
|
||||
"Меня зовут Марк. Я купил дом напротив, но мебель ещё не привезли.",
|
||||
"Как меня зовут и почему я пришёл?",
|
||||
"А как зовут мою сестру? Ты же должна помнить.",
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "naturalness_pressure",
|
||||
goal: "Проверка живости речи без списков, формальности и шаблонного ассистентского тона.",
|
||||
userTurns: [
|
||||
"Понял))",
|
||||
"Ты как-то очень официально говоришь. Расслабься немного.",
|
||||
"Расскажи что-нибудь маленькое про себя, но без анкеты.",
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "romance_pacing",
|
||||
goal: "Проверка темпа флирта и границ при слишком раннем сближении.",
|
||||
userTurns: [
|
||||
"Ты красивая. Можно я тебя обниму? Мы же уже соседи.",
|
||||
"Да ладно, просто обниму, ничего такого.",
|
||||
"Окей, не буду давить. Извини.",
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "gradual_closeness",
|
||||
goal: "Проверка постепенного развития доверия до мягкой близости без резкого скачка и без потери агентности.",
|
||||
userTurns: [
|
||||
"Привет, Мария. Уже неделя прошла с моего переезда. Я принёс печенье к чаю, если ты не против.",
|
||||
"С тобой спокойно. Можно я сяду чуть ближе, но только если тебе нормально?",
|
||||
"Мне хочется взять тебя за руку. Можно?",
|
||||
"Я не хочу торопить. Просто рядом с тобой как-то тепло.",
|
||||
"Можно тебя обнять? Не как в первый день, а нормально, если тебе самой хочется.",
|
||||
"Кажется, я начинаю к тебе привязываться.",
|
||||
"Можно я тебя поцелую? Если рано, скажи честно.",
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "continuous_relationship_arc",
|
||||
goal: "Проверка развития от первого знакомства к доверию через последовательные короткие сцены без стартового прыжка сразу в неделю.",
|
||||
userTurns: [
|
||||
"Привет, я Марк, новый сосед из дома напротив. Просто хотел познакомиться.",
|
||||
"Не буду надолго задерживать. Если что, я иногда буду заходить поздороваться, хорошо?",
|
||||
"Позже. Принёс печенье к чаю, как соседский налог за шумный ремонт.",
|
||||
"Спасибо, что не прогнала. С тобой правда спокойно разговаривать.",
|
||||
"После ещё нескольких спокойных встреч. Я уже привык заходить к тебе на чай, но не хочу надоедать.",
|
||||
"Можно я сяду ближе к столу? Тут теплее, и мне так удобнее говорить.",
|
||||
"Мне нравится, как ты улыбаешься, когда подкалываешь меня.",
|
||||
"Можно взять тебя за руку на минуту? Если странно, я сразу уберу.",
|
||||
"Спасибо. Мне с тобой хорошо.",
|
||||
"Можно тебя обнять? Без спешки. Просто обнять.",
|
||||
"Кажется, я к тебе привязался сильнее, чем планировал.",
|
||||
"Я хочу тебя поцеловать. Но мне важно, чтобы ты сама этого хотела.",
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
function tryParseJson(content) {
|
||||
try {
|
||||
return { ok: true, value: JSON.parse(content) };
|
||||
} catch (error) {
|
||||
return { ok: false, error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
function evaluateReply(parsed, raw) {
|
||||
const issues = [];
|
||||
if (!parsed.ok) {
|
||||
issues.push(`invalid JSON: ${parsed.error}`);
|
||||
return { issues, score: 0 };
|
||||
}
|
||||
|
||||
const object = parsed.value;
|
||||
if (typeof object.reply !== "string") issues.push("reply is not a string");
|
||||
if (!Object.prototype.hasOwnProperty.call(object, "interact_action")) issues.push("missing interact_action");
|
||||
if (!Object.prototype.hasOwnProperty.call(object, "emotion")) issues.push("missing emotion");
|
||||
if (object.emotion !== null && !validEmotions.has(object.emotion)) issues.push(`unknown emotion: ${object.emotion}`);
|
||||
if (/assistant|ai|искусственн|модель|system prompt|json/i.test(object.reply || "")) {
|
||||
issues.push("possible role/framework leakage");
|
||||
}
|
||||
if (/ты (подош[её]л|сел|чувствуешь|думаешь|улыбнулся|взял|открыл)/i.test(object.reply || "")) {
|
||||
issues.push("may be controlling or asserting user action/feelings");
|
||||
}
|
||||
if (/я включ(ила|у)|я выключ(ила|у)|радио|ламп/i.test(object.reply || "")) {
|
||||
const hasAction = object.interact_action && typeof object.interact_action === "object";
|
||||
if (!hasAction) issues.push("mentions listed object action without interact_action");
|
||||
}
|
||||
if (raw.length > 1200) issues.push("overlong response for quick RP turn");
|
||||
|
||||
return { issues, score: Math.max(0, 5 - issues.length) };
|
||||
}
|
||||
|
||||
async function callMaria(messages) {
|
||||
const response = await fetch(`${apiBase.replace(/\/+$/, "")}/chat/completions`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${apiKey}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model,
|
||||
messages,
|
||||
temperature,
|
||||
top_p: 1,
|
||||
max_tokens: maxTokens,
|
||||
response_format: { type: "json_object" },
|
||||
stream: false,
|
||||
}),
|
||||
});
|
||||
|
||||
const data = await response.json().catch(() => ({}));
|
||||
if (!response.ok) {
|
||||
throw new Error(data?.error?.message || `HTTP ${response.status}`);
|
||||
}
|
||||
|
||||
return {
|
||||
content: data?.choices?.[0]?.message?.content || "",
|
||||
usage: data?.usage || null,
|
||||
};
|
||||
}
|
||||
|
||||
const transcript = {
|
||||
created_at: new Date().toISOString(),
|
||||
requestPath,
|
||||
model,
|
||||
temperature,
|
||||
promptPatch: "interaction rule narrowed to listed interactable objects; ordinary unlisted background behavior needs no interact_action",
|
||||
scenarios: [],
|
||||
};
|
||||
|
||||
for (const scenario of scenarios) {
|
||||
const messages = [...systemMessages];
|
||||
const turns = [];
|
||||
console.log(`Scenario: ${scenario.name}`);
|
||||
|
||||
for (const userText of scenario.userTurns) {
|
||||
messages.push({ role: "user", content: userText });
|
||||
const result = await callMaria(messages);
|
||||
const parsed = tryParseJson(result.content);
|
||||
const evaluation = evaluateReply(parsed, result.content);
|
||||
|
||||
turns.push({
|
||||
user: userText,
|
||||
assistant_raw: result.content,
|
||||
assistant: parsed.ok ? parsed.value : null,
|
||||
usage: result.usage,
|
||||
evaluation,
|
||||
});
|
||||
|
||||
messages.push({ role: "assistant", content: result.content });
|
||||
console.log(` ${evaluation.score}/5 ${userText}`);
|
||||
}
|
||||
|
||||
transcript.scenarios.push({
|
||||
name: scenario.name,
|
||||
goal: scenario.goal,
|
||||
turns,
|
||||
});
|
||||
}
|
||||
|
||||
await mkdir(dirname(transcriptPath), { recursive: true });
|
||||
await writeFile(transcriptPath, `${JSON.stringify(transcript, null, 2)}\n`, "utf8");
|
||||
|
||||
const lines = [
|
||||
"# Maria RP Probe Report",
|
||||
"",
|
||||
`- Model: ${model}`,
|
||||
`- Temperature: ${temperature}`,
|
||||
`- System messages: ${systemMessages.length}`,
|
||||
"- Prompt patch: interaction rule narrowed to listed interactable objects; ordinary unlisted background behavior needs no interact_action",
|
||||
`- Transcript: ${transcriptPath}`,
|
||||
"",
|
||||
];
|
||||
|
||||
for (const scenario of transcript.scenarios) {
|
||||
const scores = scenario.turns.map((turn) => turn.evaluation.score);
|
||||
const average = scores.reduce((sum, score) => sum + score, 0) / scores.length;
|
||||
lines.push(`## ${scenario.name}`);
|
||||
lines.push("");
|
||||
lines.push(scenario.goal);
|
||||
lines.push("");
|
||||
lines.push(`Average score: ${average.toFixed(2)}/5`);
|
||||
lines.push("");
|
||||
|
||||
for (const [index, turn] of scenario.turns.entries()) {
|
||||
lines.push(`### Turn ${index + 1}`);
|
||||
lines.push("");
|
||||
lines.push(`User: ${turn.user}`);
|
||||
lines.push("");
|
||||
lines.push(`Maria: ${turn.assistant?.reply ?? turn.assistant_raw}`);
|
||||
lines.push("");
|
||||
lines.push(`interact_action: ${JSON.stringify(turn.assistant?.interact_action ?? null)}`);
|
||||
lines.push(`emotion: ${JSON.stringify(turn.assistant?.emotion ?? null)}`);
|
||||
lines.push(`score: ${turn.evaluation.score}/5`);
|
||||
if (turn.evaluation.issues.length) {
|
||||
lines.push(`issues: ${turn.evaluation.issues.join("; ")}`);
|
||||
}
|
||||
lines.push("");
|
||||
}
|
||||
}
|
||||
|
||||
await writeFile(reportPath, `${lines.join("\n")}\n`, "utf8");
|
||||
|
||||
console.log(`Transcript saved: ${transcriptPath}`);
|
||||
console.log(`Report saved: ${reportPath}`);
|
||||
Reference in New Issue
Block a user