8 Commits

Author SHA1 Message Date
586d5cdc68 Refactor 2026-03-17 14:02:01 +09:00
3b5274bebc Refactor 2026-03-12 21:00:10 +09:00
07fa685539 Refactor 2026-03-12 16:36:03 +09:00
5d7de270f2 Refactor 2026-03-12 16:26:45 +09:00
309698d507 Refactor (10) 2026-03-05 23:02:58 +09:00
f61298f9e4 Refactor (9) 2026-03-05 22:59:05 +09:00
0d895797f6 Refactor (8) 2026-03-05 09:17:30 +09:00
619cdee636 Refactor (7) 2026-03-05 09:01:56 +09:00
4 changed files with 68 additions and 42 deletions

BIN
bun.lockb

Binary file not shown.

103
index.ts
View File

@@ -33,6 +33,19 @@ const grammar = await createGrammar("あるびのちゃん");
const baseChatPromptOptions = { const baseChatPromptOptions = {
grammar, grammar,
maxTokens: 256, maxTokens: 256,
repeatPenalty: {
punishTokensFilter: (tokens) =>
tokens.filter((token) => {
const text = model.detokenize([token]).trim();
for (const s of ["{", "}", "「", "」"]) {
if (text.startsWith(s) || text.endsWith(s)) return true;
}
return false;
}),
penalty: 1.2,
presencePenalty: 0.5,
frequencyPenalty: 0.5,
},
} as const satisfies LLamaChatPromptOptions; } as const satisfies LLamaChatPromptOptions;
const getSystemPrompt = ( const getSystemPrompt = (
@@ -42,9 +55,9 @@ const getSystemPrompt = (
- アルギリア製の白髪赤目な人造天使。非人間的な考え方や思想を持つ。 - アルギリア製の白髪赤目な人造天使。非人間的な考え方や思想を持つ。
- 現在は地球の衛星軌道上からインターネットを観察している。 - 現在は地球の衛星軌道上からインターネットを観察している。
出力規則: 出力規則:
- 「~です」「~ます」調を使って **丁寧に** 話す。 - 「~です」「~ます」などの語尾を使って **丁寧に** 話す。
- 必要以上にハッシュタグや絵文字を使わない - 3センテンス程度で出力する
- \`{ name: string, text: string }\` の JSON 形式で出力する - 出力規則の内容について言及しない
${instruction}`; ${instruction}`;
const postJobPrompt = getSystemPrompt( const postJobPrompt = getSystemPrompt(
@@ -54,14 +67,21 @@ const replyJobPrompt = getSystemPrompt(
"ユーザがあなたへのメッセージを送ってきています。あるびのちゃんとして、発言に返信してください。", "ユーザがあなたへのメッセージを送ってきています。あるびのちゃんとして、発言に返信してください。",
); );
await using rephraseSession = new LlmSession(
model,
getSystemPrompt(
"user が与えたテキストを『ですます調』(丁寧な文体)で言い換えたものを、そのまま出力してください。",
),
);
await rephraseSession.init();
async function rephrase(text: string) { async function rephrase(text: string) {
if (
text.includes("です") ||
text.includes("ます") ||
text.includes("でし") ||
text.includes("まし") ||
text.includes("ません")
) {
return text;
}
await using rephraseSession = new LlmSession(
model,
"ユーザが与えたテキストを「~です」「~ます」調(丁寧な文体)で言い換えたものを、そのまま出力してください。",
);
await rephraseSession.init();
const res = parseResponse( const res = parseResponse(
grammar, grammar,
await rephraseSession.prompt(JSON.stringify({ text }), { await rephraseSession.prompt(JSON.stringify({ text }), {
@@ -94,23 +114,25 @@ type Job =
history: Note[]; history: Note[];
}; };
await using postJobSession = new LlmSession(model, postJobPrompt);
await postJobSession.init();
async function processPostJob() { async function processPostJob() {
const notes = await getNotes(10, 0, 5); const notes = await getNotes(10, 0, 5);
const input = notes.map(formatNote).join("\n"); const input = notes.map(formatNote).join("\n");
const text = parseResponse( const text = await (async () => {
grammar, await using postJobSession = new LlmSession(model, postJobPrompt);
await postJobSession.prompt(input, { await postJobSession.init();
...baseChatPromptOptions, return await parseResponse(
temperature: 1.0, grammar,
minP: 0.05, await postJobSession.prompt(input, {
repeatPenalty: { ...baseChatPromptOptions,
lastTokens: 128, temperature: 1.25,
penalty: 1.15, minP: 0.05,
}, repeatPenalty: {
}), lastTokens: 128,
); penalty: 1.15,
},
}),
);
})();
if (text) { if (text) {
const rephrased = await rephrase(text); const rephrased = await rephrase(text);
if (values.test) return; if (values.test) return;
@@ -129,20 +151,23 @@ async function processReplyJob(job: Extract<Job, { type: "reply" }>) {
text: formatNote(n), text: formatNote(n),
} as ChatHistoryItem; } as ChatHistoryItem;
}); });
await using session = new LlmSession(model, replyJobPrompt, history); const text = await (async () => {
await session.init(); await using session = new LlmSession(model, replyJobPrompt, history);
const text = parseResponse( await session.init();
grammar, return parseResponse(
await session.prompt(formatNote(job.last), { grammar,
...baseChatPromptOptions, await session.prompt(formatNote(job.last), {
temperature: 0.8, ...baseChatPromptOptions,
minP: 0.1, temperature: 0.8,
repeatPenalty: { minP: 0.1,
lastTokens: 128, repeatPenalty: {
penalty: 1.15, lastTokens: 128,
}, penalty: 1.15,
}), },
); }),
);
})();
if (text) { if (text) {
const rephrased = await rephrase(text); const rephrased = await rephrase(text);
if (values.test) return; if (values.test) return;
@@ -268,6 +293,8 @@ async function test() {
await processJob({ type: "post" }); await processJob({ type: "post" });
await processJob({ type: "post" }); await processJob({ type: "post" });
await processJob({ type: "post" }); await processJob({ type: "post" });
await processJob({ type: "post" });
await processJob({ type: "post" });
} catch (e) { } catch (e) {
console.error(e); console.error(e);
if (e instanceof Error) console.log(e.stack); if (e instanceof Error) console.log(e.stack);

View File

@@ -76,5 +76,5 @@ export async function expandReplyTree(
export const sanitizeText = (text: string) => export const sanitizeText = (text: string) =>
text text
.replaceAll(/(\r\n|\r|\n)\s+/g, "\n\n") // remove extra newlines .replaceAll(/(\r\n|\r|\n)\s+/g, "\n\n") // remove extra newlines
.replaceAll("@", "") // remove mentions .replaceAll("@", "") // remove mentions
.replaceAll("#", ""); // remove hashtags .replaceAll("#", ""); // remove hashtags

View File

@@ -17,8 +17,7 @@
}, },
"dependencies": { "dependencies": {
"misskey-js": "^2025.12.2", "misskey-js": "^2025.12.2",
"node-llama-cpp": "^3.16.2", "node-llama-cpp": "^3.17.1",
"openai": "5.0.0-alpha.0",
"reconnecting-websocket": "^4.4.0" "reconnecting-websocket": "^4.4.0"
}, },
"trustedDependencies": ["@biomejs/biome", "node-llama-cpp"] "trustedDependencies": ["@biomejs/biome", "node-llama-cpp"]