22
22

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

VSCodeとローカルLLM(gpt-oss120b)で和訳・RAGする

Last updated at Posted at 2026-01-04

はじめに

英語ドキュメントを読むのはつらい。
DeepLでもGoogle翻訳でも翻訳文章長のリミットがあったり、なんか日本語的に違和感があったりするものです。
そういう制限を解消しつつ、文書検索を簡単に「意図を理解」して提案してくれる環境をVSCode上に作ろう というお話をやっていきます

この記事では触れませんが、セキュリティ文書って結構Red Teaming的なことを書くと、オンラインのAIエージェントは「回答拒否」するので、ローカルLLMを使って回答拒否しないものを作っています。
LLMをうまく選定すれば作れるのでやりたい人はやってみてください。

とはいえ、絶対的にローカルLLMである必要性はないのでオンラインでもよいです。
あくまでも回答拒否しないモデルを使うという点においてローカルLLMが適しているというだけです

環境準備

  • EVO X2の128GBモデルを買う(or 96GB以上のVRAMがあるグラボを買う)
  • Pythonをインストールする(できればuvがあると楽)
  • Obsidianをインストールする
  • ブラウザにObsidian Web Clipperを入れる
  • VSCodeをインストールして、RooCodeプラグインをインストールする

ドキュメントの収集

このあたりのドキュメントをObsidian Web Clipperで収集していく

原本からの翻訳

ChatGPT謹製の翻訳スクリプトはこれ
ドデカいマークダウンファイルを渡すと後半の翻訳がゴミみたいになるので、セクション単位で文章を区切って渡すことで精度を保ったまま翻訳をすることを目論んでいます。

translate.py
# -*- coding: utf-8 -*-
import os,re
import requests
from pathlib import Path
from typing import List

# LMStudioに向けているが、OpenAI互換APIを持っているならOllamaでもOpenAIでもOK
OPENAI_COMPATIBLE_URL = "http://localhost:1234/v1/chat/completions"
MODEL = "gpt-oss-120b"

EXCLUDE_DIRS = {".git", ".venv", "resources"}

SYSTEM_PROMPT = """
あなたは技術文書翻訳者です。
Markdownを日本語に翻訳してください。

やること:
- 日本語のMarkdownを作る

制約:
- Markdown**構文**は一切変更しない
- コードブロックは翻訳しない
- リンクURLは変更しない
- ファイル構造や見出しレベルを保持する
"""

def extract_markdown_content(text: str) -> str:
    """
    ```markdown ... ``` があれば中身を抽出し、
    なければそのまま返す
    """

    # ```markdown ... ``` を丸ごとキャプチャ
    pattern = re.compile(
        r"```markdown\s*\n(?P<content>.*?)\n```",
        re.DOTALL
    )

    match = pattern.search(text)
    if match:
        # markdownブロックがある場合は中身だけ
        return match.group("content").strip()
    else:
        # 中身だけのパターンはそのまま
        return text.strip()

def _scan_existing_files(dst_dir):
    """
    dst_dir 以下にすでに存在するファイル名(相対パス)を set にして返す。
    例: {'README.md', 'subdir/chapter1.md'}
    """
    existing = set()
    for root, _, files in os.walk(dst_dir):
        rel_root = Path(root).relative_to(dst_dir)
        for f in files:
            # dst_dir の相対パスだけ保存すれば src_dir と比較しやすい
            existing.add(str(rel_root / f))
    return existing


def _needs_translation(src_path, dst_path):
    """
    翻訳が必要か判定するヘルパー。
    - dst が無ければ True
    - dst の更新時刻が src より古ければ True
    - それ以外は False(スキップ)
    """
    if not dst_path.exists():
        return True
    # タイムスタンプ比較 (src newer -> 再翻訳)
    # return src_path.stat().st_mtime > dst_path.stat().st_mtime
    return False

# Markdownをセクション単位で分割する
def split_by_heading(md: str) -> List[str]:
    """
    Markdownを見出し単位 (#〜######) で分割する。
    見出し行は各ブロックの先頭に含まれる。
    """
    blocks = re.split(
        r'(?=^#{1,6} )',
        md,
        flags=re.MULTILINE
    )

    # 空ブロック除去(念のため)
    return [b for b in blocks if b.strip()]

def do_translate(text: str) -> str:
    payload = {
        "model": MODEL,
        "messages": [
            {"role": "system", "content": SYSTEM_PROMPT},
            {"role": "user",   "content": text}
        ],
        "temperature": 0.0
    }
    r = requests.post(OPENAI_COMPATIBLE_URL, json=payload)
    return r.json()["choices"][0]["message"]["content"]


def translate_markdown(md: str) -> str:
    """
    Markdown全体を分割 → 翻訳 → 結合
    """
    blocks = split_by_heading(md)
    translated_blocks = []

    for i, block in enumerate(blocks, 1):
        print(f"Translating block {i}/{len(blocks)}...")
        translated = extract_markdown_content(do_translate(block))
        translated_blocks.append(translated)

    # 改行を保ったまま結合
    return "\n\n".join(translated_blocks)

def process_dir(src_dir):
    src_dir = Path(src_dir)               # 入力は文字列でもPathでも OK
    dst_dir = Path(f"{src_dir}(JA)")      # 例: Document(JA)

    # --------------------------------------------------------------
    # (1) すでに翻訳済みのファイルをスキャンしておく
    # --------------------------------------------------------------
    existing_files = _scan_existing_files(dst_dir)
    # ここでは `existing_files` はデバッグやログ出力にも使える
    # print("Already translated:", sorted(existing_files))

    # --------------------------------------------------------------
    # (2) src ディレクトリを走査し、必要なら翻訳 or コピー
    # --------------------------------------------------------------
    for root, dirs, files in os.walk(src_dir):
        # 除外ディレクトリがあれば除く(元コードと同様)
        dirs[:] = [d for d in dirs if d not in EXCLUDE_DIRS]

        rel_root = Path(root).relative_to(src_dir)   # src からの相対パス
        out_root = dst_dir / rel_root                # 同じ構造で出力先を作る
        out_root.mkdir(parents=True, exist_ok=True)

        for f in files:
            src_path = Path(root) / f
            dst_path = out_root / f

            # ------------------------------------------------------
            # (A) 翻訳対象かどうか判定
            # ------------------------------------------------------
            if f.lower().endswith(".md"):
                # ① ファイルがすでに存在するか?
                #   → 存在し、かつ src が古い場合はスキップ
                if not _needs_translation(src_path, dst_path):
                    continue

                # ② 翻訳実行
                print(f"Translating: {src_path}")
                text = src_path.read_text(encoding="utf-8")
                ja   = translate_markdown(text)
                dst_path.write_text(ja, encoding="utf-8")

if __name__ == "__main__":
    # 翻訳対象のディレクトリを相対パスで指定する
    process_dir("DocumentDirectory")

RooCodeで概要をまとめてもらう

プロンプト ドキュメント群の概要をまとめてください

RooCode(Askモード)の回答

ドキュメント群の概要

ファイル 主な章・セクション 要点
Adventures in Shellcode Obfuscation Part 8 - Shellcode as UUIDs.md Converting Shellcode to UUIDs
Converting UUIDs to Shellcode
Additional Reading / Try It Yourself
シェルコードを 16 バイト単位の UUID に変換し、UuidFromStringA を使って逆変換する手順とサンプルコードを解説。UUID が 128‑bit ラベルであることから、EDR のヒューリスティック検知を回避しやすい点が強調されている。
EDR Evasion How Attackers Evade EDR & How to Detect Them.md Attacker tactics (Blinding, Blending, Hiding)
CISA red‑team assessment
Impact on threat detection
Corelight Open NDR
EDR の 3 大回避手法を概観し、実際の CISA 評価で「EDR に過度に依存」した組織が検知失敗した事例を紹介。ネットワーク可視化 (NDR) が EDR 補完として有効と結論付けている。
Ghosts in the Endpoint: How Attackers Evade Modern EDR Solutions.md Major EDR evasion techniques
Living‑off‑the‑Land (LOLBins)
Code injection & process hollowing
User‑mode hook bypass
In‑memory evasion & obfuscation
EDR tampering / disabling
各回避カテゴリを実例とともに詳細解説。LOLBins の利用やプロセスハローイング、フック除去・直接システムコール、メモリ上だけで完結するファイルレス手法などが網羅されている。
How Attackers Bypass EDR: Techniques and Countermeasures.md Obfuscation (Recompiling, Encoding/Encrypting)
Malicious Action Avoidance / Living‑off‑the‑Land
Bypassing detection (Unhooked processes, Kernel‑level ops)
Key takeaways
攻撃側がまず「見た目」を変える(リコンパイル・暗号化)ことから始め、次に正規ツールの濫用やフック回避手法へと移行する流れを示す。Unhooked プロセス生成やカーネルレベルでの実装が高度な回避策として位置付けられる。
Introduction to EDR Evasion: API Hooking.md API hooking の仕組み
Direct system‑call 回避
APC Queue Injection
Shellcode loader 手順
EDR が API フックで監視する対象と、攻撃側がフックを回避するために ntdll のシステムコールを直接呼び出す手法を解説。さらに APC キュー注入やシェルコードローダーの 4 ステップ(メモリ確保・書き込み・保護変更・実行)を具体例で示す。

全体的なまとめ

以上で、6 件のマークダウン文書の主要構成と相互関係をまとめました。これにより、各ドキュメントがどの回避技術を扱い、全体としては「コード変形 → 正規ツール濫用 → フック回避 → メモリ上実行」の流れで EDR を迂回していることが把握できます。

概要を理解したうえで翻訳先を読む

Obsidianで読んでみる

image.png

まとめ

  • 翻訳自動化(translate.py): 全マークダウンをオフラインで日本語化できます
  • 知識蓄積・拡張:翻訳済み文書をベースに自分だけのナレッジをまとめた「ノート」を作成できます
  • この流れで、英語原典の読解コストを削減しつつ、VS Code とローカル LLM を組み合わせた「翻訳+検索」環境が構築できます。

おわりに

この方法であれば基本的にトークン数の縛りや翻訳回数の上限がありません。
なので、検索や要約を何回も繰り返したり、自動的にレポートを書かせること、検索結果を利用して次は何をすべきかなどのTODOリストを作らせる、RooCodeの本来の使い道であるコード(PoC)を書かせる ということも可能です。

それじゃ今回はここまで。

22
22
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
22
22

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?