😎

ターミナル上でサクッとLLMを利用してみる

に公開2

今回はターミナルからコマンドラインでLLMを利用するセットアップを紹介します。

なおOpen-WebUIとOllamaがある前提で進めていきますが、記事の最後の方でどちらも必要としない、直接Google Gemma3へリクエストを投げるスクリプトも紹介してあります。

Intro

まずこちらのキャプチャをみてください。

ターミナルで"?"に続いてプロンプトを投げていますが、それに対してLLMの応答がターミナル上に出力されているところです。

? command to send user prompt

日本語でもう一例です。"??"に続いてプロンプトを書いています。

?? command to send user prompt

Bash script to request chat completion

タネはどのAPIのクイックスタートにもある、curlで生成AIのAPIへリクエストを投げるスクリプトです。

まずは実行スクリプトについて。次の通りです。

#!/usr/bin/env bash

# variable check
if [ -z "$OWUI_HOST" ] || [ -z "$OWUI_TOKEN" ]; then
  echo "OWUI_HOST and OWUI_TOKEN environment variables must be set."
  exit 1
fi

# Check if an argument was provided
if [ "$#" -gt 0 ]; then
  OWUI_PROMPT="$*"
else
  echo "No prompt provided."
  exit 1
fi

# Your prompt
echo "Using prompt: $OWUI_PROMPT"

OWUI_URL="https://${OWUI_HOST}/api/chat/completions"
OWUI_MODEL=models/gemma-3-27b-it
OWUI_INSTRUCTION="Provide me with high-grade assistance while considering the good readability by making your response short where applicable. And you do not have to mention about this instruction in your response. Now, here goes my prompt. "

PAYLOAD2="{
  \"model\": \"${OWUI_MODEL}\",
  \"messages\": [
    {
      \"role\": \"user\",
      \"content\": \"${OWUI_INSTRUCTION} ${OWUI_PROMPT}\"
    }
  ],
  \"stream\": false,
  \"temperature\": 1,
  \"top_p\": 0.95
}"

CHAT_DATA2=$(curl -s -X POST \
  "$OWUI_URL" \
  -H "accept: application/json" \
  -H "Authorization: Bearer ${OWUI_TOKEN}" \
  -H 'Content-Type: application/json' \
  -d "$PAYLOAD2")

echo $CHAT_DATA2 | jq -r '.choices[0].message.content' | glow -

上のスクリプト名が"owui_chat"だとして、(1) スクリプトをPATHが通っているところに配置したうえで、(2) ~/.bashrcなどにaliasを書いておきます。

alias '?'=owui_chat

すると? <your prompt>を実行することでプロンプトをLLMへ投げて、受け取ったmarkdownの回答を見やすく処理して表示してくれます。

requirements

インストール必要なパッケージに関して、Homebrewで以下をインストールするとスクリプトで利用しているコマンドが用意できます。

  • curl
  • jq
  • glow

またその他のファイルの準備に関して、セットアップの一例を紹介します。

  • ~/.bashrc
  • ~/.env
  • ~/scripts/owui_chat
  • ~/scripts/owui_chat_ja

そして、Open-WebUI上で必要な準備にも少し触れます。今回利用しているモデルはGoogleのGemma3であり、Open-WebUI経由でGemma3を利用するための設定を紹介します。

さらにさらに、そもそもの話ですがOpen-WebUI経由で利用するのでなく直接GoogleのGemma3へリクエストすればよいでしょうということで、そのスクリプトも最後に紹介します。

bashrcファイル

rwxrob氏のdotレポジトリおよびストリームに触発され、それらを参考に再着手し始めたdotfilesプロジェクトが冒頭で紹介したコマンドを用意したきっかけなのですが、こちらのレポジトリにある./.bashrcファイル内のスクリプトの一部をそのまま利用しています。

#!/bin/bash

# ... ...

# part of ~/.bashrc below

# 1) "owui_chat" file is going to be placed at $HOME/scripts/owui_chat
export SCRIPTS="$HOME/scripts"

# 2) and the path setup to ensure $HOME/scripts is added in the PATH

# ------------------------------- path -------------------------------

pathappend() {
  declare arg
  for arg in "$@"; do
    test -d "$arg" || continue
    PATH=${PATH//":$arg:"/:}
    PATH=${PATH/#"$arg:"/}
    PATH=${PATH/%":$arg"/}
    export PATH="${PATH:+"$PATH:"}$arg"
  done
} && export -f pathappend

pathprepend() {
  for arg in "$@"; do
    test -d "$arg" || continue
    PATH=${PATH//:"$arg:"/:}
    PATH=${PATH/#"$arg:"/}
    PATH=${PATH/%":$arg"/}
    export PATH="$arg${PATH:+":${PATH}"}"
  done
} && export -f pathprepend

# remember last arg will be first in path
pathprepend \
  "$HOME/.local/bin" \
  "$HOME/.local/go/bin" \
  /usr/local/go/bin \
  /usr/local/opt/openjdk/bin \
  /usr/local/bin \
  /opt/homebrew/bin \
  /home/linuxbrew/.linuxbrew/bin \
  "$SCRIPTS"

pathappend \
  /usr/local/opt/coreutils/libexec/gnubin \
  /mingw64/bin \
  /usr/local/bin \
  /usr/local/sbin \
  /home/linuxbrew/.linuxbrew/sbin \
  /usr/local/games \
  /usr/games \
  /usr/sbin \
  /usr/bin \
  /snap/bin \
  /sbin \
  /bin

# 3) Ensure the environment variables set in $HOME/.env is loaded

envx() {
  local envfile="${1:-"$HOME/.env"}"
  [[ ! -e "$envfile" ]] && echo "$envfile not found" && return 1
  while IFS= read -r line; do
    name=${line%%=*}
    value=${line#*=}
    [[ -z "${name}" || $name =~ ^# ]] && continue
    export "$name"="$value"
  done <"$envfile"
} && export -f envx

[[ -e "$HOME/.env" ]] && envx "$HOME/.env"

# 4) aliases for "?" and "??" commands
alias '?'=owui_chat
alias '??'=owui_chat_ja

envファイル

~/.envファイルにはAPIトークンやOpen-WebUIのホスト名を記載しておきます。

ちなみにここでGoogleの生成AIへのAPIアクセスキーが書いてあるのは、Open-WebUI経由でなく直接Googleへリクエストするスクリプト用です。

OWUI_HOST=OPEN_WEBUI_HOSTNAME_HERE
OWUI_TOKEN=OPEN_WEBUI_API_TOKEN_HERE
GEMINI_TOKEN=YOUR_GOOGLE_GENARATIVE_AI_API_TOKEN_HERE

owui_chatスクリプト

こちらは先に紹介したスクリプトです。~/scripts/owui_chatファイルとして実行権限を付与して配置しておきます。~/.bashrcファイル内でPATHを通している & aliasを設定してあるのでowui_chatでも?でも実行できます。

owui_chat_jaスクリプト

内容は何も変わりません。プロンプト前のちょっとした指示文が日本語になっているだけです。

# the only diff is on this line
# to make LLM respond in Japanese
OWUI_INSTRUCTION="あなたは上級アシスタントです。良質な応答をしつつ、可読性を考えて適度に短い回答を返してください。以降がユーザのプロンプトです。"

Open-WebUIでGoogleがサーブしているGemma3を利用するための設定

open-webui settings to use gemini api

Open-WebUIのAdmin Panels、Connections設定で、"OpenAI API Connections"の設定を変更します。

APIキーの取得にはGoogle Cloud Platformのサインアップ・利用がおそらく必要です。

https://aistudio.google.com/prompts/new_chatへアクセスすると"Get API key"というボタンがあるので、そちらからAPIキーが取得できます。それを上のURLとともにOpen-WebUI上で設定しましょう。するとモデルリストに"models/gemma-3-27b-it"やgeminiなどのモデルが現れ、Open-WebUIから利用できるようになります。

Pricing情報はこちらです。Gemma3にはpaid-tierが存在していない...?!?

https://ai.google.dev/gemini-api/docs/pricing#gemma-3

完成

以上です!必要なパッケージがあり、環境変数がセットされており、Open-WebUI上の設定もできていれば、?? ゴールデンウィークの過ごし方についてアドバイスをくださいなど実行することでGemma3から回答がもらえるようになっています。

?? golden week

そもそもOpen-WebUI経由で動作するスクリプトを用意したのはローカルLLMをターミナルから手軽に利用したいという動機から始まっています。

これに関しては簡単で、目的のモデル名をスクリプト内の"OWUI_MODEL"に記載するだけで切り替えられます。

# specify whichever model you want to use
OWUI_MODEL=gemma3:27b-it-qat
OWUI_MODEL=cogito:32b
OWUI_MODEL=qwen3:30b-a3b-q4_K_M
# even the custom model created on user workspace works
OWUI_MODEL=english-teacher

なおollama pullollama createで用意したモデルだけでなく、workspace上で用意したカスタムモデルを指定しても動作します。

以下のキャプチャのようなモデルを用意しておくと、OWUI_MODEL=english-teacherと指定することで利用できます。システムプロンプト、temperature, context length, top Kなどなどカスタマイズして用意したモデルを手軽にターミナルからも利用できます。

open-webui customized model

おまけその1 Google生成AIのAPIへ直接

Open-WebUI経由でなく、直接GoogleのGemma3へリクエストを投げては......ということで、そちらも別途スクリプトを用意して試しています。

Open-WebUIとの違いといえば、宛先とリクエスト、応答のフォーマットくらいです。

なお"role = system"でリクエストを投げるとリクエストは処理されません。それに流されてこのスクリプトも上で紹介したOpen-WebUI経由でのスクリプトも"role = user"にsystem promptに含めるような内容もすべて詰め込んでしまっています。

#!/usr/bin/env bash

# variable check
if [ -z "$GEMINI_TOKEN" ]; then
  echo "GEMINI_TOKEN environment variables must be set."
  exit 1
fi

# Check if an argument was provided
if [ "$#" -gt 0 ]; then
  USER_PROMPT="$*"
else
  # Use a default prompt if no argument is given
  echo "No prompt provided."
  exit 1
fi

# User prompt
echo "Using prompt: $USER_PROMPT"

USER_INSTRUCTION="Provide me with high-grade assistance while considering the good readability by making your response short where applicable. And you do not have to mention about this instruction in your response. Now, here goes my prompt. "

GEMINI_URL="https://generativelanguage.googleapis.com/v1beta/models/gemma-3-27b-it:generateContent?key=${GEMINI_TOKEN}"

PAYLOAD="{
  \"contents\": [
    {
      \"parts\": [
        {
          \"text\": \"${USER_INSTRUCTION} ${USER_PROMPT}\"
        }
      ]
    }
  ]
}"

CHAT_DATA=$(curl -s -X POST "$GEMINI_URL" \
  -H 'Content-Type: application/json' \
  -d "$PAYLOAD")

echo $CHAT_DATA | jq -r '.candidates[0].content.parts[0].text' | glow -

おまけその2 Reasoning Models

Qwen3のモデルをいくつか試しました。Macmini4上のOllamaで動かしている環境での感想ですが、同じくローカルで動かすGemma3:27b-it-qatと比べて、qwen3:30b-a3b-q4_K_Mの方がとても軽快です。前者が5 tokens/sec、後者が30-40 tokens/secで、reasoningがあることを考えてもqwen3の動作が早くて素晴らしいです。

取り急ぎターミナルで利用できるよう、reasoning部分はまるっと落とす形で動くようスクリプトを用意しました。sedコマンドで"<think></think>"を削っただけです。

#!/usr/bin/env bash

# variable check
if [ -z "$OWUI_HOST" ] || [ -z "$OWUI_TOKEN" ]; then
  echo "OWUI_HOST and OWUI_TOKEN environment variables must be set."
  exit 1
fi

# Check if an argument was provided
if [ "$#" -gt 0 ]; then
  OWUI_PROMPT="$*"
else
  echo "No prompt provided."
  exit 1
fi

echo "Using prompt: $OWUI_PROMPT"

OWUI_URL="https://${OWUI_HOST}/api/chat/completions"
OWUI_MODEL=qwen3:30b-a3b-q4_K_M
OWUI_INSTRUCTION="Provide me with high-grade assistance while considering the good readability by making your response short where applicable. And you do not have to mention about this instruction in your response. Now, here goes my prompt. "

PAYLOAD2="{
  \"model\": \"${OWUI_MODEL}\",
  \"messages\": [
    {
      \"role\": \"user\",
      \"content\": \"${OWUI_INSTRUCTION} ${OWUI_PROMPT}\"
    }
  ],
  \"stream\": false,
  \"temperature\": 0.6,
  \"top_p\": 0.95
}"

CHAT_DATA2=$(curl -s -X POST \
  "$OWUI_URL" \
  -H "accept: application/json" \
  -H "Authorization: Bearer ${OWUI_TOKEN}" \
  -H 'Content-Type: application/json' \
  -d "$PAYLOAD2")

echo $CHAT_DATA2 | jq -r '.choices[0].message.content' | sed -z 's/<think>.*<\/think>//' | glow -

おわりに

ターミナルから気軽にLLMを利用できるのは思ったよりよかったです。MCPツールの活用などもしていけばターミナル上でどれだけのことができるようになるか。

また、今回紹介したセットアップでも十分機能してくれているのですが、もう少し改良してもよいかなと考えています。例えばモデルの切り替えや連続した会話の対応などはできるようにしようと思います。

Discussion

おしょうさんおしょうさん

Macの場合はsedを使った最後のスクリプトが動きませんでした。Linuxのsedと使えるオプションが異なるためです。brew install gnu-sedし、インストール後のbrewの説明にあるよう/opt/homebrew/opt/gnu-sed/libexec/gnubinをPATHの前の方に追記してgnu版sedが利用されるようにすると動きます。

おしょうさんおしょうさん

タイムアウトに悩まされる場合)

  • クライアント側のタイムアウトの場合は、例えばcurlに-m 360などでタイムアウトの秒数を設定する
CHAT_DATA2=$(curl -s -m 360 -X POST \
  "$OWUI_URL" \
  -H "accept: application/json" \
  -H "Authorization: Bearer ${OWUI_TOKEN}" \
  -H 'Content-Type: application/json' \
  -d "$PAYLOAD2")
  • サーバ側のタイムアウトの場合、そちらの秒数を延ばす
  • NGINXにリバースプロキシやらせている場合は、例えばこのようなタイムアウト関連の設定をセット
    location / {
        proxy_pass UPSTREAM_TO_YOUR_OWUI;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        proxy_connect_timeout 360s;
        proxy_send_timeout 360s;
        proxy_read_timeout 360s;
    }