Add Games File

This commit is contained in:
Ivan Anikin
2026-05-19 13:42:41 +07:00
parent 7908479e0c
commit 12e7e22c1a
3672 changed files with 125922 additions and 0 deletions
+296
View File
@@ -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}`);