Zenn
👣

Ollama + LangChainで画像を入力する方法【Gemma3 27B】

2025/03/13に公開
4

はじめに

Gemma3がGoogleからリリースされたようです。
色々な方が評価をしているので、あまりやることはないかなと思っていたのですが、どうやら画像も入力できるよう。

https://x.com/7shi/status/1899762727238988144

https://x.com/AiXsatoshi/status/1899816287527948573

私は、基本的にLangChainを利用しているので、LangChainで画像の読み取りも含めて、Gemma3を動かせたらとおもい、試した内容を記事にします。

また、Gemma3に関しては下記の記事で詳しく説明してくださっています。
https://zenn.dev/schroneko/articles/try-google-gemma-3

準備

Ollamaの導入

まずは、OllamaでGemma3を利用できるようにします。

Ollamaの導入方法などは様々記事があるのでそちらを参照してください。
基本的には、下記の公式のページからインストールをするだけです。
https://ollama.com/download/windows

Gemma3を導入

下記にて、様々なモデルパラメータのGemma3が選べます。
https://ollama.com/library/gemma3/tags

今回の記事では、「gemma3:27b」を利用します。

コンソールにて、下記のコマンドを入力してください。

ollama pull gemma3:27b

これで、導入は完了です。

LangChainを利用してOllamaからGemma3を利用

基本的には下記の公式記事を参考にして実装しています。
https://python.langchain.com/docs/integrations/chat/ollama/

普通にテキストでの利用

コード

Ollamaが起動していることを確認してください。
その後、下記のコードで実行できます。

from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_ollama import ChatOllama

messages = [
    ("system", "あなたは日本語を話す優秀なアシスタントです。回答には必ず日本語で答えてください。また考える過程も出力してください。"),
    ("human", "{user_input}")
]

query = ChatPromptTemplate.from_messages(messages)

model = ChatOllama(
    model="gemma3:27b",
    max_tokens=4096,
    temperature=0.2,
    top_p=0.9
    )

chain = query | model | StrOutputParser()

output = chain.invoke({"user_input": "日本で一番高い山は何ですか?"})
print(output)

ここでは、「日本で一番高い山は何ですか?」と質問をしています。
システムプロンプトは適当です。

ChatOllamaクラスを利用して、起動中のOllamaに保存されているモデルを読み込み、LangChainで読み込むことができます。
あとは、普通のLangChainの書き方でOKです。

ちなみに、LangChainに関しては、下記でも記事を書いております。
https://zenn.dev/asap/articles/aa587ad8d76105

実行結果

下記が実行結果です。


はい、承知いたしました。日本で一番高い山についてお答えします。

考える過程:

日本で一番高い山を尋ねられているので、日本の山々の中で標高が最も高いものを特定する必要があります。小学校で学習する地理の知識から、富士山が日本で最も高い山であるとすぐに思い浮かびます。念のため、他の山と比較して確認してみましたが、やはり富士山が一番高いことがわかりました。

回答:

日本で一番高い山は、富士山です。標高は3,776.24mです。


なんか、「考える過程」が面白いですね。無理やり捻り出したみたいなw

画像を読み込む方法

読み込む画像

下記の画像を読み込もうと思います。AIで生成した画像です。

上記の画像をinputs/image.pngのパスに格納します。

コード

下記のコードで実行ができます。

from langchain_core.prompts import ChatPromptTemplate
from langchain_core.messages import HumanMessage, SystemMessage
from langchain_core.output_parsers import StrOutputParser
from langchain_ollama import ChatOllama

import base64
from io import BytesIO
from PIL import Image

def convert_to_base64(pil_image):
    """
    Convert PIL images to Base64 encoded strings

    :param pil_image: PIL image
    :return: Base64 string
    """
    buffered = BytesIO()
    pil_image.save(buffered, format="PNG")  # You can change the format if needed
    img_str = base64.b64encode(buffered.getvalue()).decode("utf-8")
    return img_str

def prompt_func(data):
    user_input = data["user_input"]
    image = data["image"]

    message = [
        SystemMessage(content= "あなたは日本語を話す優秀なアシスタントです。回答には必ず日本語で答えてください。また考える過程も出力してください。"),
        HumanMessage(
            content=[
                {
                    "type": "text", 
                    "text": f"{user_input}"
                },
                {
                    'type': 'image_url',
                    'image_url': f'data:image/png;base64,{image}',
                },
            ]
        )
    ]

    return message  

model = ChatOllama(
    model="gemma3:27b",
    max_tokens=4096,
    temperature=0.2,
    top_p=0.9
    )

chain = prompt_func | model | StrOutputParser()

file_path = "inputs/images.png"
pil_image = Image.open(file_path)
image_b64 = convert_to_base64(pil_image)

#下記でも実行可能
#output = chain.invoke(
#    {"user_input": "画像には何が映っているか説明してください。", "image": image_b64}
#)

#テキストが少しずつ生成されるようにするために、streamを使う
for chunk in chain.stream({"user_input": "画像に何が映っているか説明してください。", "image": image_b64}):
    print(chunk, end="", flush=True)

上記では、一旦画像をbase64に変換した上で、それをモデルに入力する形で画像を読み込ませています。
基本的にモデルがgeminiなど他のものになっても、同じように処理可能です。
(モデルが変わってもコードを変える必要がないところがLangChainのいいところです)

また、関数はchainで繋げる際に、他に繋いだものがRannableである場合、勝手に関数もRannableに変換して繋げてくれます。便利ですね。
ちなみに丁寧に書くと下記です。RunnableLambdaでラップするとRunnable化できます。

chain = RunnableLambda(prompt_func) | model | StrOutputParser()

また、出力にちょっと時間かかるなと思ったので、stream出力にしています。

for chunk in chain.stream({"user_input": "画像に何が映っているか説明してください。", "image": image_b64}):
    print(chunk, end="", flush=True)

モデルには{"user_input": "画像に何が映っているか説明してください。", "image": image_b64}(ユーザの質問文と画像のbase64)が入力として与えられ、chainの最初であるprompt_funcで処理されてプロンプトを完成させ、それがモデルに入力され、出力が1tokenずつ出力されるようになっています。

実行結果

下記が実行結果です。


はい、承知いたしました。画像に映っているものを説明します。

思考過程:

  1. 全体的な印象: 画像は、窓辺に立つ若い女性のポートレート写真です。
  2. 人物: 女性は、長い茶色の髪で、白いセーターを着ています。顔立ちは美しく、少し物憂げな表情をしています。
  3. 背景: 背景は、白いカーテンと窓枠で、光が差し込んでいます。
  4. 雰囲気: 全体的に、柔らかく、穏やかな雰囲気です。

説明:

画像には、窓辺に立つ若い女性が写っています。彼女は長い茶色の髪を持ち、白いセーターを着ています。少し物憂げな表情をしており、柔らかい光が彼女を照らしています。背景には白いカーテンと窓枠が見え、穏やかな雰囲気を醸し出しています。


ちゃんと、画像の特徴を踏まえて回答ができています。

まとめ

Ollamaに限らず、LangChainを利用すると、上記のような形で画像を読み込ませることができます。

Github

https://github.com/personabb/ollama_sample

おすすめ書籍

LangChainとLangGraphによるRAG・AIエージェント[実践]入門
ChatGPT/LangChainによるチャットシステム構築[実践]入門

今回は、さまざまなモデルを統一的に利用するために、LangChainを利用しています。
langchainに関しては、こちらの書籍を読めば大体のことはできるようになりますので、おすすめです。

4

Discussion

ログインするとコメントできます