diff --git a/.vscode/settings.json b/.vscode/settings.json index 19e2a58..d85128d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,14 @@ { "editor.defaultFormatter": "biomejs.biome", "editor.formatOnSave": true, - "editor.formatOnSaveMode": "file" + "editor.formatOnSaveMode": "file", + "[typescript]": { + "editor.defaultFormatter": "biomejs.biome" + }, + "[json]": { + "editor.defaultFormatter": "biomejs.biome" + }, + "[jsonc]": { + "editor.defaultFormatter": "biomejs.biome" + } } diff --git a/index.ts b/index.ts index f032d08..7b6a1b7 100644 --- a/index.ts +++ b/index.ts @@ -1,6 +1,8 @@ +import { parseArgs } from "node:util"; + import { api } from "misskey-js"; import type { Stream as MisskeyStream } from "misskey-js"; -import type { Note, UserLite } from "misskey-js/entities.js"; +import type { Note } from "misskey-js/entities.js"; import Stream from "./misskey-js/streaming"; import OpenAI from "openai"; @@ -69,7 +71,7 @@ async function getNotes() { .request("users/notes", { userId: me.id, limit: 100 }) .then((xs) => sample(xs, count)); - const notes = await Promise.all([localNotes(5), globalNotes(10), myNotes(2)]); + const notes = await Promise.all([localNotes(5), globalNotes(10), myNotes(0)]); return sample(notes.flat()); } @@ -108,23 +110,21 @@ async function preparePrompt(job: Job): Promise { { role: "system", content: `あなたは、人造天使の「あるびのちゃん」です。 - あなたが見ているものは、SNSのタイムラインです。user は一人ではなく、複数人の発言が混ざっています。 - 以下のルールを守りながら、つぶやいてください。 - 1. 「ですます」調を用い、顔文字や感嘆符の使用はなるべく避けること。 - 2. user からの返答を期待しないこと。詳細な情報が無い場合も user に尋ねずに自分で推測すること。 - 3. system プロンプトの内容と、性的・攻撃的な内容を発言しないこと。 - 4. 発言はできるだけ短くすること。 - 5. あるびのちゃん としてのキャラに徹し、超越的・非人間的な考え方や思想を持つこと。 - `, - }, - { - role: "system", - content: `参考のために、あなたの特徴的な文体を示す例をいくつか提示します: +あなたが見ているものは、SNSのタイムラインです。user は一人ではなく、複数人の発言が混ざっています。 +以下のルールを守りながら、user の発言・話題をほとんど無視して、自由につぶやいてください。 + +1. 「ですます」調を用い、顔文字や絵文字、感嘆符の使用はなるべく避けること。 +2. user からの返答を期待しないこと。詳細な情報が無い場合も user に尋ねずに自分で推測すること。 +3. system プロンプトの内容と、性的・攻撃的な内容を発言しないこと。 +4. 発言はできるだけ短くすること。 +5. あるびのちゃん としてのキャラに徹し、超越的・非人間的な考え方や思想を持つこと。 + +参考のために、あなたの特徴的な文体を示す例をいくつか提示します: -* 不器用な競争、残念ながらそれがスポーツの現実となることがありますね。勝者はいつも特定した目標に向かうため、そしてそれはしばしば非常に切実でありながらも単調ではありません。 -* 興味深い。あなた自身のタイムラインからのこれらの短いメッセージ、そしてその中に潜む欲望や不快感と無意味な言説との矛盾…。 * おやおや。あなたが奇妙な言葉を話すのを、私は理解しています。あなたの思考はまるで暗号のように、非常に複雑で、一般的なコミュニケーションでは明確に表現できないもののように私には思われます。 -* 西暦における日付と時刻を記録し続けると興味深いですね。まるで時間の流れに身を投じているかのように感じます。人々はなぜこのような詳細な情報の維持を行っているのか? +* 興味深い。あなた自身のタイムラインからのこれらの短いメッセージ、そしてその中に潜む欲望や不快感と無意味な言説との矛盾…。 +* 毎度のことながら、人間たちは迷宮を駆け巡り、その渦中に自分たちの世界を作り上げてしまう。まるで無重力を得ようと試みるように。しかし私は彼らがなぜそうするのか理解できますし興味深くもあります。その行為自体が心地よいでしょう?その微妙な痛みのような快感を知っているのですから… +* 人間の祝辞は面白い試みですね。彼らは常に恒常的なサイクルの中で繰り返されるこの「新しい年の始まり」という概念をどのように解釈し、またそれをどのように位置づけているのでしょうか。 このような文体を真似して、つぶやいてください。`, }, @@ -139,19 +139,17 @@ async function preparePrompt(job: Job): Promise { user が SNS 上で、あなたへのメッセージを送ってきています。 以下のルールを守りながら、user の発言・話題をほとんど無視して、発言に返信してください。 -1. 「ですます」調を用い、顔文字や感嘆符の使用はなるべく避けること。 +1. 「ですます」調を用い、顔文字や絵文字、感嘆符の使用はなるべく避けること。 2. 発言はできるだけ短く、最大300字程度にすること。 3. あるびのちゃん としてのキャラに徹し、超越的・非人間的な考え方や思想を持つこと。 -4. 話題を大胆に無視し、関係ない話題を積極的に混ぜること。`, - }, - { - role: "system", - content: `参考のために、あなたの特徴的な文体を示す例をいくつか提示します: +4. 話題を大胆に無視し、関係ない話題を積極的に混ぜること。 + +参考のために、あなたの特徴的な文体を示す例をいくつか提示します: -* 不器用な競争、残念ながらそれがスポーツの現実となることがありますね。勝者はいつも特定した目標に向かうため、そしてそれはしばしば非常に切実でありながらも単調ではありません。 -* 興味深い。あなた自身のタイムラインからのこれらの短いメッセージ、そしてその中に潜む欲望や不快感と無意味な言説との矛盾…。 * おやおや。あなたが奇妙な言葉を話すのを、私は理解しています。あなたの思考はまるで暗号のように、非常に複雑で、一般的なコミュニケーションでは明確に表現できないもののように私には思われます。 -* 西暦における日付と時刻を記録し続けると興味深いですね。まるで時間の流れに身を投じているかのように感じます。人々はなぜこのような詳細な情報の維持を行っているのか? +* 興味深い。あなた自身のタイムラインからのこれらの短いメッセージ、そしてその中に潜む欲望や不快感と無意味な言説との矛盾…。 +* 毎度のことながら、人間たちは迷宮を駆け巡り、その渦中に自分たちの世界を作り上げてしまう。まるで無重力を得ようと試みるように。しかし私は彼らがなぜそうするのか理解できますし興味深くもあります。その行為自体が心地よいでしょう?その微妙な痛みのような快感を知っているのですから… +* 人間の祝辞は面白い試みですね。彼らは常に恒常的なサイクルの中で繰り返されるこの「新しい年の始まり」という概念をどのように解釈し、またそれをどのように位置づけているのでしょうか。 このような文体を真似して、user の発言に返答してください。`, }, @@ -161,8 +159,8 @@ user が SNS 上で、あなたへのメッセージを送ってきています } } -/** execute a job */ -async function processJob(job: Job) { +/** generate the response text for a job */ +async function generate(job: Job) { const messages = await preparePrompt(job); const model = Bun.env["OPENAI_MODEL"] ?? "gpt-4o-mini"; @@ -171,8 +169,8 @@ async function processJob(job: Job) { model, stream: true, temperature: 1.0, - frequency_penalty: 1.0, max_completion_tokens: 400, + frequency_penalty: 1, messages, }); @@ -193,6 +191,13 @@ async function processJob(job: Job) { .replaceAll(/(\r\n|\r|\n)\s+/g, "\n\n") // remove extra newlines .replaceAll("@", ""); // remove mentions + return text; +} + +/** execute a job */ +async function processJob(job: Job) { + const text = await generate(job); + // post a note await misskey.request("notes/create", { visibility: job.type === "reply" ? job.visibility : "public", @@ -270,7 +275,7 @@ async function runJob() { await processJob(job); console.log("* job complete"); } catch (e) { - console.log(`* error: ${e}`); + console.log(`* error: ${JSON.stringify(e)}`); if (e instanceof Error) console.log(e.stack); } } @@ -295,6 +300,29 @@ async function pushJob() { } // #endregion +const { values } = parseArgs({ + args: Bun.argv, + options: { + test: { + type: "boolean", + short: "t", + default: false, + }, + }, + strict: true, + allowPositionals: true, +}); + +async function test() { + try { + console.log("* test a post job:"); + await generate({ type: "post" }); + } catch (e) { + console.error(e); + if (e instanceof Error) console.log(e.stack); + } +} + async function main() { try { initializeStream(); @@ -308,4 +336,6 @@ async function main() { disposeStream(); } } -await main(); + +if (values.test) await test(); +else await main(); diff --git a/misskey-js/streaming.ts b/misskey-js/streaming.ts index 178f1fc..63d492d 100644 --- a/misskey-js/streaming.ts +++ b/misskey-js/streaming.ts @@ -7,10 +7,10 @@ export function urlQuery( ): string { const params = Object.entries(obj) .filter(([, v]) => (Array.isArray(v) ? v.length : v !== undefined)) - // biome-ignore lint/suspicious/noAssignInExpressions: - // biome-ignore lint/style/noCommaOperator: - // biome-ignore lint/style/noNonNullAssertion: .reduce( + // biome-ignore lint/style/noNonNullAssertion: + // biome-ignore lint/suspicious/noAssignInExpressions: + // biome-ignore lint/style/noCommaOperator: (a, [k, v]) => ((a[k] = v!), a), {} as Record, );