Featured image of post MisskeyのDeepL翻訳APIをいじって遊ぶ

MisskeyのDeepL翻訳APIをいじって遊ぶ

目次

Misskey Advent Calendar 2025

この記事は、Misskey Advent Calendar 2025の21日目の記事です!

今年も参加できて嬉しいです。

過去のアドカレ記事はこちら↓

はじめに

Misskeyの翻訳機能をカスタマイズして遊んだ記録です。

Misskeyはオープンソースの分散型SNSで、DeepL翻訳APIを利用した翻訳機能が組み込まれています。インスタンス管理者は、コントロールパネルからDeepLのAPIキーを設定することで、ユーザーが投稿を翻訳できるようになります。

今回は、この翻訳機能を独自の翻訳サービスに置き換えたり、そもそも翻訳以外の処理を行うように改造したりして遊んでみようと思います。

Misskey翻訳APIの仕様

Misskey APIの仕様に関するドキュメントは、各インスタンスの /api-doc パスで参照できます。翻訳APIのエンドポイントは notes/translate です。

Misskey API

リクエストボディはJSON形式で、以下のような構造になっています。noteIdは翻訳対象の投稿のID、targetLangは翻訳先の言語コードです。

{
  noteId: string,
  targetLang: string,
}

レスポンスは以下のような構造です。sourceLangは翻訳元の言語コード、textは翻訳後のテキストです。

{
  sourceLang: string,
  text: string,
}

この仕様に従って、独自の翻訳サービスを構築すれば、Misskeyの翻訳機能を自由にカスタマイズできるというわけです。

独自翻訳サービスの構築

さっそく、独自の翻訳サービスを構築してみます。

ところで、私のMisskeyインスタンスが動いているマシンには、なぜかGPU (NVIDIA GeForce RTX 2080 Ti)が搭載されています。

せっかくなので、このGPUを活用してローカルで動作する翻訳モデルを使い、Misskeyの翻訳機能を実装してみたいと思います。

翻訳モデルの選定

ローカルで動作する日本語に特化した翻訳LLMとしては、PLaMo 2 translateあたりが有名かなと思います。これをそのまま動かそうかなと思ったのですが、Transformersを使いたくないBF16 10BではVRAMが足りないのでGGUF形式で量子化されたものを使います。

LLMを動かす

GGUF形式のLLMはllama.cppで簡単に動かせます。今回は翻訳APIとして動作させたいので、Pythonバインディングであるllama-cpp-pythonを使います。

超適当にLLM部分のコードを書いてみるとこんな感じです。本当に適当ですが、試作としては十分でしょう。

# translator.py
from llama_cpp import ChatCompletionRequestUserMessage, Llama

class Translator:
    def __init__(self, model_path: str):
        self.llm = Llama(model_path, n_gpu_layers=-1)

    def translate(self, text: str) -> str:
        res = self.llm.create_chat_completion(
                ChatCompletionRequestUserMessage(
                    role="user",
                    content=text,
                )
            ],
            stream=False,
        )
        return res["choices"][0]["message"]["content"].strip()

if __name__ == "__main__":
    translator = Translator("models/Plamo-2-Translate-Q5_K_M.gguf")
    input_text = "Misskeyはセルフホスト可能なオープンソースの分散型SNSプラットフォームです。"
    output_text = translator.translate(input_text)
    print("Input:", input_text)  # -> Misskey is a self-hostable, open-source decentralized social networking platform.

APIサーバーの構築

翻訳モデルが動作するようになったので、次にAPIサーバーを構築します。PythonでWebサーバを構築するときはFastAPIがデファクトになっているらしいので、これを使います。

こちらも超適当に実装してみると、以下のようになります。

# main.py
from typing import Optional
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel

class TranslateRequest(BaseModel):
    noteId: str
    targetLang: str
    i: Optional[str] = None

class TranslateResponse(BaseModel):
    text: str
    sourceLang: str

app = FastAPI()

app.add_middleware(
    CORSMiddleware,
    allow_origins=["htttps://team.matechan.com"],
    allow_methods=["POST"],
)

translator = Translator("models/Plamo-2-Translate-Q5_K_M.gguf")

@app.post("/translate")
async def translate(request: TranslateRequest) -> TranslateResponse:
    note = ...  # Misskey APIから投稿内容を取得する関数(省略)

    translated_text = translator.translate(note["text"])

    return TranslateResponse(
        text=translated_text,
        sourceLang="ja",
    )

Misskeyから飛んでくるのは投稿IDなので、実際には投稿内容をMisskey APIから取得してから翻訳する必要がありますが、今回は省略します。また、翻訳リクエストにはリクエストを飛ばしたユーザのAPIキーが含まれるため、それを使ってノートの内容を取得します。

一つ注意点として、CORSの設定で自分のMisskeyインスタンスのホスト名のみを許可しておきます。こうしないと、ブラウザからのリクエストがブロックされてしまいます。

この状態で起動すれば、自作の翻訳APIサーバがローカルで動作します。

翻訳サービスの公開

ローカルで動かしているだけではユーザのブラウザからアクセスできないので、構築した翻訳サービスをインターネットに公開します。

MisskeyをCloudflare Tunnelで公開している場合、マシンですでにcloudflaredが動いていると思うので、同じトンネルを使うのが楽ですね。

Cloudflare Tunnelの設定から、画像のようにサブドメインを追加して、翻訳サービス用のパスを設定します。

トンネル

Cloudflareでのリダイレクト設定

Cloudflareで管理しているサイトであれば、任意のURLに対してリダイレクトを設定できます。

私が運営しているMisskeyインスタンス Momogumi もCloudflareで管理しているため、Cloudflareのリダイレクト機能を使って、Misskeyの翻訳APIエンドポイントへのリクエストを別のURLに転送することができます。

Cloudflareのダッシュボードから、対象のドメインを選択し、「ルール」→「概要」から「リダイレクトルール」を追加します。

私の場合、Misskeyを運用しているホスト名がteam.matechan.com、オリジナルの翻訳エンドポイントのパスは/api/notes/translateなので、カスタムフィルタを以下のように設定します。

カスタムフィルタの設定

実行内容は以下のように設定します。

実行内容

この設定により、https://team.matechan.com/api/notes/translate へのリクエストが https://translate.team.matechan.com/translate にリダイレクトされるようになります。

Misskey側での設定

最後に、Misskeyのコントロールパネル→「外部サービス」から、DeepL Translationの設定を開きます。

DeepL Auth Keyは使わないですが、空欄にしておくとそもそも翻訳機能が無効化されてしまうため、適当な文字列を入れておきます。

コントロールパネルのDeepL翻訳設定

動作確認

以上で設定は完了です。Misskeyの投稿の翻訳ボタンを押して、翻訳が動作するか確認してみましょう。

おまけ

翻訳APIを改造して、翻訳以外の処理を行うこともできます。ユーザのAPIキーを使って投稿内容を取得できるので、例えば以下のようなことが可能です。

  • ユーザの過去の投稿を含めてコンテキストを構築し、要約を返す
  • 投稿内容に基づいて自動返信メッセージを生成する
  • 投稿内容を解析して感情分析を行い、その結果を返す
  • 投稿内容を別の形式(例:箇条書き、詩、コードスニペットなど)に変換する
  • 投稿内容に関連する外部情報(例:ニュース記事、ウィキペディアの抜粋など)を取得して返す

…などなど、アイデア次第で様々な応用が考えられます。

あともう一つおまけとして、翻訳結果のテキストもMisskeyのMFMで装飾できるので、翻訳の出力結果をコピーしやすくするためにコードブロックで囲む、みたいなこともできます。

おわりに

今回は、Misskeyの翻訳機能をカスタマイズして独自の翻訳サービスを構築し、Cloudflare Tunnelとリダイレクト設定を使って公開する方法を紹介しました。

現状、超適当な実装に留まっているので、いずれちゃんと作って常用できるようにしたいですね。

以上です。

Hugo で構築されています。
テーマ StackJimmy によって設計されています。