Home About Contact
macOS , Gemma , Note Taking

Gemma4 E4B を使用して page | drip する

AppleScript を使って Safari から情報を得るに 書いたように page コマンドをつかって Safari に表示中のテキストを得ることができるようになった。 さらにこれを page | drip して そのテキストの要約を得ることを考える。

なお、Apple's Foundation Model (AFM) を使った drip コマンドについては macOS 26 ターミナルで Safari に表示しているページを要約するコマンドラインツール page | drip をつくった に書いた。 ここでは、AFM ではなく、gemma-4-E4B-it-litert-lm と kotlin を使った方法を紹介します。

環境:

$ java -version
openjdk version "21.0.12" 2026-07-21

$ kotlin -version
Kotlin version 2.4.10-release-377 (JRE 21.0.12)

プロジェクト構成:

.
├── drip.main.kts
└── models
    └── gemma-4-E4B-it.litertlm

gemma-4-E4B-it.litertlm は こちら gemma-4-E4B-it-litert-lm からダウンロードして ./models/ 以下に配置してあります。

コード drip.main.kts :

#!/usr/bin/env kotlin

@file:Repository("https://repo1.maven.org/maven2")
@file:Repository("https://maven.google.com")
@file:DependsOn("com.google.ai.edge.litertlm:litertlm-jvm:0.15.0")
@file:DependsOn("org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.10.2")

import com.google.ai.edge.litertlm.*
import kotlinx.coroutines.runBlocking
import kotlin.system.exitProcess
import java.io.File

private val SYSTEM_INSTRUCTION = """
    You analyze notes and extract structured metadata.

    Write the title, description, and keywords in the same language as the input note.
    If the note mixes languages, use the dominant language of its prose, ignoring code,
    URLs, and proper nouns. Never translate into a different language.

    - title: a concise title for the note
    - description: a short summary of the note's content, 1-3 sentences
    - keywords: 3-6 salient terms from the note

    Output only a JSON object in exactly this shape, with no explanation and no code fences.
    The JSON keys stay in English exactly as shown; only the values follow the note's language.
    {"title": "...", "description": "...", "keywords": ["...", "..."]}
""".trimIndent()

runBlocking {
    Engine.setNativeMinLogSeverity(LogSeverity.ERROR)

    val input = generateSequence(::readlnOrNull).joinToString("\n")
    if (input.isBlank()) {
        System.err.println("Error: No input provided")
        exitProcess(1)
    }

    val engineConfig = EngineConfig(
        modelPath = File("./models/gemma-4-E4B-it.litertlm").canonicalFile.absolutePath,
        backend = Backend.GPU(),
    )

    Engine(engineConfig).use { engine ->
        engine.initialize()
        val conversationConfig = ConversationConfig(
            systemInstruction = Contents.of(SYSTEM_INSTRUCTION),
            samplerConfig = SamplerConfig(topK = 64, topP = 0.95, temperature = 0.2),
        )
        engine.createConversation(conversationConfig).use { conversation ->
            val response = conversation.sendMessage(input)
            val result = response.contents.contents
                .filterIsInstance<Content.Text>()
                .joinToString("") { it.text }
            println(result)
        }
    }
}

実行:

$ $ page | kotlin drip.main.kts | jq .
{
  "title": "macOS 26 ターミナルで Safari のページを要約するコマンドラインツール page | drip の開発",
  "description": "macOS 26でApple IntelligenceのFoundation Modelを活用し、Safariで開いているページのテキストを要約するコマンドラインツール「drip」を開発した。AppleScriptを利用した「page」コマンドとパイプでつなぐことで、ターミナルから手軽に要約結果を得られるようにしている。",
  "keywords": [
    "macOS 26",
    "Apple Intelligence",
    "Foundation Model",
    "コマンドラインツール",
    "Safari",
    "要約"
  ]
}

Safari で https://note.com/mindboard/n/ndc7e0755de6d のページを開いていた場合の例

AFM の場合、内容によっては処理を拒否する場合があるので Gemma4 が助けになるかもしれない。 Gemma4 E4B であれば ファイルサイズが 3.6GB なので、おそらく 16GB Macbook Air などでも問題なく動かせる。 この方法でも5秒程度で回答が得られるので、実用になる。 常用するのであれば、fat jar にビルドしてそれを使うべき。