
TL;DR
Gemma4 E2Bは軽量かつ出力の日本語の質が良く、ローカルLLMとして非常に優れています。しかし、プロンプトだけでJSON出力を強制しても安定しませんでした。response_formatを使用することで、JSON出力を安定して得ることができるようになります。
また、E2Bを使ってニュース記事の要約を作成する際に、重複を削除する方法についても紹介します。
はじめに
少し前から、AIのリリースやニュースなどをウォッチして、記事のサマリーを作成してgithub pagesで公開するようにしていました。
当初作成したときはサマリーにGeminiを利用しており、それでも別に不自由はなかったのですが、サマリーの生成が遅いのと、OpenClawをはじめたりしてローカルでできるのも良いな、と思いGemma4が出たときに飛びついてみたのです。
LM StudioでGemma4 E4Bをちょっとためしてみたところ、サマリーには十分な品質でした。また、E2Bも思ったほどメモリサイズは変わらないのですが、出力はかなり速いですし、私のプロンプトの場合はなぜかE4Bよりサマリーが安定している感じでした。たまに英語でサマリーを出力したりして、再実行することもありますが、それを考えても十分に速いので、E2BをローカルLLMとして利用することにしました。
JSON出力を強制する
ところが、軽量モデルだとプロンプトだけでは出力が安定しません。JSONが崩れたり、JSON以外のテキストが混ざったりしてしまいます。LM Studioは、OpenAIのAPIと同様にresponse_formatを指定することができます。これを使用することで、JSON出力を安定して得ることができるようになります。
実際の呼び出しはこのような形です。
SUMMARY_FILTER_JSON_SCHEMA = {
"name": "summary_filter_result",
"schema": {
"type": "object",
"properties": {
"summary": {"type": "string"},
"is_ai_related": {"type": "boolean"},
"reason": {"type": "string"},
},
"required": ["summary", "is_ai_related", "reason"],
"additionalProperties": False,
},
}
# ... 省略 ...
if json_schema:
request_kwargs["response_format"] = {
"type": "json_schema",
"json_schema": json_schema,
}
# ... 省略 ...
resp = client.chat.completions.create(**request_kwargs)
ニュースサイトなどはAIに関係ないニュースも混じっているので、サマリーの作成と同時にAIに関連するかどうかの判定も行っています。response_formatを指定すると確実にJSONに出力されるようになります。
重複の削除
LLM開発ベンダーの発表はすぐにニュースサイトやインフルエンサーのblogなどに引用されます。そのような重複ニュースを削除するために、サマリーの内容をベクトル化して、記事同士の類似度を計算し、スレッショルド以上のものを重複とみなして削除する、という方法をとっています。 実際は削除するのではなく、重複マーカーを付けているので表示することもできます。
この処理には、軽量なStaticEmbeddingモデルを利用しています。これはSentence Transformerのモデルですが、めちゃくちゃ速いのでRAGのEmbeddingもこれでいいんじゃないのと思ったりします。今度試してみよう。
実際のコードはこのような形です。
# similaritiesは記事同士の類似度(コサイン距離)の行列
def collect_clusters(similarities: np.ndarray, threshold: float) -> dict[int, list[int]]:
size = similarities.shape[0]
# UnionFindは、類似度がthreshold以上のものを推移的に同じクラスタとして登録する
union_find = UnionFind(size)
for i in range(size):
for j in range(i + 1, size):
if similarities[i, j] >= threshold:
union_find.union(i, j)
clusters: dict[int, list[int]] = defaultdict(list)
for index in range(size):
clusters[union_find.find(index)].append(index)
return clusters
類似度が近いものを同じクラスタとして、(上記のコードにはないですが)サマリーが一番長いものを代表記事として、他の記事には重複マーカーを付ける、という処理をしています。これで、重複ニュースを削除することができるようになります。
実際に記事を眺めてみても、サマリーは少し簡素すぎる気がしますが、ざっとみて後で見る記事がブックマークできれば良いので、通勤途中などで見る分には十分な品質になっていると思います。 LM Studioはデフォルトで4並列でモデルを呼び出せるので、サマリーの生成時間も十分に速いです。
まとめ
Gemma4 E2Bは軽量かつ出力の日本語の質が良いモデルですが、軽量モデルのためプロンプトで意を汲んでくれないこともあります。構造化出力を使うことで出力を安定させることができました。 また、その出力をSentence Transformerを使った類似度計算で重複ニュースを削除してみました。サマリー出力はこのような後処理にも十分に利用できるものができていると思います。