はじめに
前回の記事で提示した仕様に基づき、LLM連携による自然文ログ分析環境を構築してみました。
* 問い合わせできること
authlog / syslog 件数取得
日別 / 時間別の推移
ホスト別分析
期間指定(例:過去7日、昨日など)
SQL確認付きの分析
* 問い合わせできないこと
任意SQL実行
テーブル変更
JOIN / サブクエリ自由実行
未定義ログの解析
既存分析基盤(そのまま使う)
ここはすでにある前提です。
Trino
Iceberg
Hive Metastore
HDFS
authlog_iceberg / syslog_iceberg
新規 Docker ホスト
ここに以下を載せます。
Open WebUI
FastAPI(自然文ログ分析API)
docker-compose
→こちらについてはコンテナ稼働ホストに事前に導入してください。(本手順に従って導入でも構いません。)
役割:
- OpenWebUI
ブラウザUI
自然文の受付
- FastAPI
自然文からパターンに沿ったSQLを選択し、Trino問い合わせ
結果整形
新規 Ollama ホスト
ここに以下を載せます。
Ollama
→こちらも事前に導入してください。(本手順に従って導入でも構いません。)
NVIDIA GPU搭載ホスト推奨(ドライバ、CUDAは別途事前に導入すること。)
役割:
LLM 実行専用
最終構成図
1. ollmaホスト構築
1-1. OS更新
Ubuntu 系を前提にします。
既にollamaを入れてる場合1-4の確認から行うこと。
sudo apt update && sudo apt -y upgrade
sudo reboot
1-2. 基本ツール導入
sudo apt install -y curl git jq vim htop
1-3. Ollama 導入
Ollama の Linux インストールスクリプトで入れます。
curl -fsSL https://ollama.com/install.sh | sh
インストール後確認:
ollama --version
1-4. Ollama サービス確認
systemctl status ollama
起動していなければ:
sudo systemctl enable --now ollama
確認:
curl http://127.0.0.1:11434/api/tags
返ればOKです。
1-5. 外部接続を許可する
デフォルトだとローカルだけで待つことがあるので、
他ホスト(openwebui/APIコンテナ)から接続できるようにします。
systemd override を作成
sudo systemctl edit ollama
以下を入れます。
[Service]
Environment="OLLAMA_HOST=0.0.0.0:11434"
保存後反映:
sudo systemctl daemon-reload
sudo systemctl restart ollama
確認:
ss -lntp | grep 11434
0.0.0.0:11434 で待っていればOKです。
1-6. ファイアウォール開放(必要なら)
UFW使っているなら:
sudo ufw allow 11434/tcp
sudo ufw status
1-7. モデル取得
本システムでは軽量のgemma3モデルを使います。
ollama pull gemma3
確認:
ollama list
以下のようにリストが出ること。
NAME ID SIZE
gemma3 xxxxx ...
1-8. 別ホストから疎通確認
dockerコンテナ稼働ホストから確認します。
curl http://<ollmaホストIP>:11434/api/tags | jq
ここでモデル一覧が見えれば、Ollama 側は完成です。
2. Docker ホスト構築(Open WebUI + FastAPI)
ここからdockerコンテナ稼働ホストの作業です。
既にdocker-composeまで入れてる場合は2-5までスキップすること。
2-1. OS更新
sudo apt update && sudo apt -y upgrade
sudo reboot
2-2. 基本ツール導入
sudo apt install -y curl git jq vim htop ca-certificates gnupg lsb-release
2-3. Docker 導入
古いもの削除(必要なら)
sudo apt remove -y docker docker-engine docker.io containerd runc || true
Docker GPGキー
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | \
sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
Docker リポジトリ追加
echo \
"deb [arch=$(dpkg --print-architecture) \
signed-by=/etc/apt/keyrings/docker.gpg] \
https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
インストール
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
確認:
sudo docker --version
sudo docker compose version
2-4. Docker を sudo なしで使えるようにする
sudo usermod -aG docker $USER
sudo newgrp docker
確認:
docker ps
2-5. 配置ディレクトリ作成
例として提示しているためパスはどこでもよい。
sudo mkdir -p /opt/llm-log-ui
sudo chown -R $USER:$USER /opt/llm-log-ui
cd /opt/llm-log-ui
3. Open WebUI + FastAPI 一式配置
ここからファイルを作ります。
3-1. ディレクトリ構成
cd /opt/llm-log-ui
mkdir -p app
touch docker-compose.yml .env requirements.txt Dockerfile
touch app/main.py app/sql_templates.py app/parser.py app/trino_client.py app/summarizer.py app/ollma_client.py
3-2. .env
接続先設定
cat > /opt/llm-log-ui/.env <<'EOF'
WEBUI_SECRET_KEY=your-super-secre
TRINO_HOST=<Trino1 IP>
TRINO_PORT=8080
TRINO_USER=llm_reader
TRINO_CATALOG=iceberg
TRINO_SCHEMA=logs
TZ=Asia/Tokyo
OLLAMA_BASE_URL=http://<ollma IP>:11434
OLLAMA_MODEL=gemma3
EOF
3-3. requirements.txt
cat > /opt/llm-log-ui/requirements.txt <<'EOF'
fastapi
uvicorn[standard]
pydantic
trino
requests
EOF
3-4. Dockerfile
cat > /opt/llm-log-ui/Dockerfile <<'EOF'
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt /app/requirements.txt
RUN pip install --no-cache-dir -r /app/requirements.txt
COPY app /app/app
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
EOF
3-5. docker-compose.yml
cat > /opt/llm-log-ui/docker-compose.yml <<'EOF'
version: "3.9"
services:
open-webui:
image: ghcr.io/open-webui/open-webui:main
container_name: open-webui
restart: unless-stopped
ports:
- "3010:8080"
volumes:
- open-webui-data:/app/backend/data
environment:
- WEBUI_SECRET_KEY=${WEBUI_SECRET_KEY}
networks:
- llm-net
llm-log-api:
build:
context: .
dockerfile: Dockerfile
container_name: llm-log-api
restart: unless-stopped
# 本番利用はport部分をマスクし以下推奨
#expose:
#- "8000"
ports:
- "8000:8000"
networks:
- llm-net
env_file:
- .env
volumes:
open-webui-data:
networks:
llm-net:
EOF
4. FastAPI のログ分析APIを作る
4-1. app/sql_templates.py
cat > /opt/llm-log-ui/app/sql_templates.py <<'EOF'
TEMPLATES = {
# 既存: SSHログイン成功系
"auth_success_hosts_top": """
SELECT
host,
COUNT(*) AS cnt
FROM iceberg.logs.authlog_iceberg
WHERE dt BETWEEN DATE '{date_from}' AND DATE '{date_to}'
AND (
msg LIKE '%Accepted password%'
OR msg LIKE '%Accepted publickey%'
OR msg LIKE '%Accepted keyboard-interactive/pam%'
)
{host_filter}
GROUP BY 1
ORDER BY cnt DESC
LIMIT {limit_n}
""",
"auth_success_users_top": """
SELECT
regexp_extract(msg, 'for ([^ ]+)', 1) AS user_name,
COUNT(*) AS cnt
FROM iceberg.logs.authlog_iceberg
WHERE dt BETWEEN DATE '{date_from}' AND DATE '{date_to}'
AND (
msg LIKE '%Accepted password%'
OR msg LIKE '%Accepted publickey%'
OR msg LIKE '%Accepted keyboard-interactive/pam%'
)
{host_filter}
GROUP BY 1
ORDER BY cnt DESC
LIMIT {limit_n}
""",
"auth_success_timeseries": """
SELECT
CAST(date_trunc('hour', at_timezone(ts, 'Asia/Tokyo')) AS timestamp) AS time,
COUNT(*) AS cnt
FROM iceberg.logs.authlog_iceberg
WHERE dt BETWEEN DATE '{date_from}' AND DATE '{date_to}'
AND (
msg LIKE '%Accepted password%'
OR msg LIKE '%Accepted publickey%'
OR msg LIKE '%Accepted keyboard-interactive/pam%'
)
{host_filter}
GROUP BY 1
ORDER BY 1
""",
"auth_success_logs_recent": """
SELECT
ts,
host,
program,
msg
FROM iceberg.logs.authlog_iceberg
WHERE dt BETWEEN DATE '{date_from}' AND DATE '{date_to}'
AND (
msg LIKE '%Accepted password%'
OR msg LIKE '%Accepted publickey%'
OR msg LIKE '%Accepted keyboard-interactive/pam%'
)
{host_filter}
ORDER BY ts DESC
LIMIT {limit_n}
""",
# 追加: authlog 件数系
"auth_count_total": """
SELECT
COUNT(*) AS cnt
FROM iceberg.logs.authlog_iceberg
WHERE dt BETWEEN DATE '{date_from}' AND DATE '{date_to}'
{host_filter}
""",
"auth_count_timeseries_all": """
SELECT
CAST(date_trunc('hour', at_timezone(ts, 'Asia/Tokyo')) AS timestamp) AS time,
COUNT(*) AS cnt
FROM iceberg.logs.authlog_iceberg
WHERE dt BETWEEN DATE '{date_from}' AND DATE '{date_to}'
{host_filter}
GROUP BY 1
ORDER BY 1
""",
"auth_count_hosts_top": """
SELECT
host,
COUNT(*) AS cnt
FROM iceberg.logs.authlog_iceberg
WHERE dt BETWEEN DATE '{date_from}' AND DATE '{date_to}'
GROUP BY 1
ORDER BY cnt DESC
LIMIT {limit_n}
""",
# 追加: syslog 件数系
"syslog_count_total": """
SELECT
COUNT(*) AS cnt
FROM iceberg.logs.syslog_iceberg
WHERE dt BETWEEN DATE '{date_from}' AND DATE '{date_to}'
{host_filter}
""",
"syslog_count_timeseries": """
SELECT
CAST(date_trunc('hour', at_timezone(ts, 'Asia/Tokyo')) AS timestamp) AS time,
COUNT(*) AS cnt
FROM iceberg.logs.syslog_iceberg
WHERE dt BETWEEN DATE '{date_from}' AND DATE '{date_to}'
{host_filter}
GROUP BY 1
ORDER BY 1
""",
"syslog_count_hosts_top": """
SELECT
host,
COUNT(*) AS cnt
FROM iceberg.logs.syslog_iceberg
WHERE dt BETWEEN DATE '{date_from}' AND DATE '{date_to}'
GROUP BY 1
ORDER BY cnt DESC
LIMIT {limit_n}
"""
}
EOF
4-2. app/parser.py
cat > /opt/llm-log-ui/app/parser.py <<'EOF'
import re
from datetime import date, datetime, timedelta
KNOWN_HOSTS = [
"gateway1", "worker1", "worker2",
"master1", "master2", "master3",
"hive1", "hive2", "zeppelin1",
"log1", "kafka1", "kafka2",
"ope1"
]
ALLOWED_TEMPLATES = {
# SSH成功系
"auth_success_hosts_top",
"auth_success_users_top",
"auth_success_timeseries",
"auth_success_logs_recent",
# authlog 件数系
"auth_count_total",
"auth_count_timeseries_all",
"auth_count_hosts_top",
# syslog 件数系
"syslog_count_total",
"syslog_count_timeseries",
"syslog_count_hosts_top",
}
def detect_host(question: str):
q = question.lower()
for host in KNOWN_HOSTS:
if host.lower() in q:
return host
return None
def normalize_date_str(s: str) -> str | None:
s = s.strip()
# YYYY-MM-DD / YYYY/MM/DD
for fmt in ("%Y-%m-%d", "%Y/%m/%d"):
try:
return datetime.strptime(s, fmt).date().isoformat()
except ValueError:
pass
# MM-DD / MM/DD -> 今年扱い
for fmt in ("%m-%d", "%m/%d"):
try:
d = datetime.strptime(s, fmt).date()
return d.replace(year=date.today().year).isoformat()
except ValueError:
pass
return None
def detect_date_range(question: str):
today = date.today()
yesterday = today - timedelta(days=1)
# デフォルト
date_from = yesterday.isoformat()
date_to = yesterday.isoformat()
q = question
# 相対日付
if "今日" in q:
return today.isoformat(), today.isoformat()
if "昨日" in q:
return yesterday.isoformat(), yesterday.isoformat()
if "一昨日" in q:
d = today - timedelta(days=2)
return d.isoformat(), d.isoformat()
if "直近24時間" in q:
return yesterday.isoformat(), today.isoformat()
if "過去7日" in q:
d = today - timedelta(days=6)
return d.isoformat(), today.isoformat()
if "過去30日" in q:
d = today - timedelta(days=29)
return d.isoformat(), today.isoformat()
if "今週" in q:
start = today - timedelta(days=today.weekday())
return start.isoformat(), today.isoformat()
if "先週" in q:
this_week_start = today - timedelta(days=today.weekday())
last_week_start = this_week_start - timedelta(days=7)
last_week_end = this_week_start - timedelta(days=1)
return last_week_start.isoformat(), last_week_end.isoformat()
if "今月" in q:
start = today.replace(day=1)
return start.isoformat(), today.isoformat()
if "先月" in q:
first_this_month = today.replace(day=1)
last_month_end = first_this_month - timedelta(days=1)
last_month_start = last_month_end.replace(day=1)
return last_month_start.isoformat(), last_month_end.isoformat()
# 明示日付を抽出
matches = re.findall(r"\d{4}[-/]\d{1,2}[-/]\d{1,2}|\d{1,2}[-/]\d{1,2}", q)
dates = [normalize_date_str(m) for m in matches]
dates = [d for d in dates if d]
# 範囲指定キーワード
if any(sep in q for sep in ["から", "〜", "~", "to", "TO", "-", "—"]):
if len(dates) >= 2:
return dates[0], dates[1]
if len(dates) >= 2:
return dates[0], dates[1]
if len(dates) == 1:
return dates[0], dates[0]
return date_from, date_to
def clamp_limit(question: str, default: int = 10) -> int:
if "上位5" in question or "5件" in question:
return 5
if "20件" in question:
return 20
if "50件" in question:
return 50
return default
def parse_question(question: str) -> dict:
q = question.lower()
host = detect_host(question)
date_from, date_to = detect_date_range(question)
result = {
"template": "auth_count_total",
"date_from": date_from,
"date_to": date_to,
"host": host,
"limit_n": clamp_limit(question, 10)
}
# 件数推移
if "件数推移" in question or "推移" in question or "時系列" in question:
if "syslog" in q:
result["template"] = "syslog_count_timeseries"
return result
if "authlog" in q:
result["template"] = "auth_count_timeseries_all"
return result
if "ssh" in q or "ログイン成功" in question:
result["template"] = "auth_success_timeseries"
return result
# 最新ログ / 生ログ
if "最新ログ" in question or "ログを20件" in question or "20件見せて" in question:
if "ssh" in q or "ログイン成功" in question:
result["template"] = "auth_success_logs_recent"
result["limit_n"] = clamp_limit(question, 20)
return result
# ユーザー上位
if "ユーザー" in question and ("成功" in question or "ログイン" in question):
result["template"] = "auth_success_users_top"
return result
# ホスト上位 / 多いホスト
if "ホスト上位" in question or "多いホスト" in question or ("上位" in question and host is None):
result["limit_n"] = clamp_limit(question, 10)
if "syslog" in q:
result["template"] = "syslog_count_hosts_top"
return result
if "authlog" in q:
result["template"] = "auth_count_hosts_top"
return result
if "ssh" in q or "ログイン成功" in question:
result["template"] = "auth_success_hosts_top"
return result
# 単純件数
if "件数" in question or "何件" in question:
if "syslog" in q:
result["template"] = "syslog_count_total"
return result
if "authlog" in q:
result["template"] = "auth_count_total"
return result
if "ssh" in q or "ログイン成功" in question:
# SSH成功の総件数テンプレはまだ無いので timeseries / host top に寄せない
result["template"] = "auth_success_hosts_top"
return result
# SSH成功系
if "ssh" in q or "ログイン成功" in question:
result["template"] = "auth_success_hosts_top"
return result
return result
EOF
4-3. app/trino_client.py
cat > /opt/llm-log-ui/app/trino_client.py <<'EOF'
import os
import trino
def run_trino(sql: str):
conn = trino.dbapi.connect(
host=os.environ["TRINO_HOST"],
port=int(os.environ.get("TRINO_PORT", "8080")),
user=os.environ["TRINO_USER"],
catalog=os.environ.get("TRINO_CATALOG", "iceberg"),
schema=os.environ.get("TRINO_SCHEMA", "logs"),
)
cur = conn.cursor()
cur.execute(sql)
rows = cur.fetchall()
cols = [c[0] for c in cur.description]
return cols, rows
EOF
4-4. app/summarizer.py
cat > /opt/llm-log-ui/app/summarizer.py <<'EOF'
from app.ollama_client import chat_ollama
def summarize(question: str, cols, rows, parsed=None) -> str:
if not rows:
return "該当データは見つかりませんでした。"
template = None
if parsed and isinstance(parsed, dict):
template = parsed.get("template")
if template == "auth_count_total":
cnt = rows[0][0]
return f"authlog件数は {cnt:,} 件です。"
if template == "syslog_count_total":
cnt = rows[0][0]
return f"syslog件数は {cnt:,} 件です。"
if template == "auth_count_timeseries_all":
return f"直近24時間のauthlog件数推移を確認できます。データ点数は {len(rows)} 件です。"
if template == "syslog_count_timeseries":
return f"直近24時間のsyslog件数推移を確認できます。データ点数は {len(rows)} 件です。"
if template in {"auth_count_hosts_top", "syslog_count_hosts_top"}:
lines = []
for host, cnt in rows[:5]:
lines.append(f"- {host}: {cnt:,} 件")
return "上位ホストは次の通りです。\n" + "\n".join(lines)
if template == "auth_success_hosts_top":
lines = []
for host, cnt in rows[:5]:
lines.append(f"- {host}: {cnt:,} 件")
return "SSHログイン成功が多いホストは次の通りです。\n" + "\n".join(lines)
if template == "auth_success_users_top":
lines = []
for user, cnt in rows[:5]:
lines.append(f"- {user}: {cnt:,} 件")
return "SSHログイン成功が多いユーザーは次の通りです。\n" + "\n".join(lines)
lines = []
for row in rows[:20]:
parts = [f"{c}={v}" for c, v in zip(cols, row)]
lines.append("- " + ", ".join(parts))
result_text = "\n".join(lines)
system_prompt = """
あなたはログ分析結果を要約するアシスタントです。
数値は必ず入力に忠実に使ってください。
入力にない数値を作ってはいけません。
""".strip()
user_prompt = f"""
質問:
{question}
検索結果:
{result_text}
2〜5行で要約してください。
""".strip()
try:
return chat_ollama(system_prompt=system_prompt, user_prompt=user_prompt, temperature=0.0)
except Exception:
return result_text
EOF
4-5. app/main.py
cat > /opt/llm-log-ui/app/main.py <<'EOF'
from fastapi import FastAPI
from pydantic import BaseModel, Field
from app.parser import parse_question
from app.sql_templates import TEMPLATES
from app.trino_client import run_trino
from app.summarizer import summarize
import traceback
app = FastAPI(
title="LLM Log Analysis Tool Server",
description="authlog/syslog analysis tool server",
version="1.0.0"
)
class AskRequest(BaseModel):
question: str = Field(
...,
description="日本語の自然文でログ分析依頼を書く"
)
def build_host_filter(host: str | None) -> str:
if not host:
return ""
return f"AND host = '{host}'"
@app.get("/health")
def health():
return {"status": "ok"}
@app.post("/ask")
def ask(req: AskRequest):
try:
parsed = parse_question(req.question)
sql = TEMPLATES[parsed["template"]].format(
date_from=parsed["date_from"],
date_to=parsed["date_to"],
host_filter=build_host_filter(parsed["host"]),
limit_n=parsed["limit_n"],
)
cols, rows = run_trino(sql)
answer = summarize(req.question, cols, rows, parsed)
# 件数系はシンプル返却(Open WebUI対策)
if parsed["template"] in {
"auth_count_total",
"syslog_count_total",
"auth_count_timeseries_all",
"syslog_count_timeseries",
"auth_count_hosts_top",
"syslog_count_hosts_top",
}:
return {
"answer": answer,
"sql": sql.strip(),
"columns": cols,
"rows": rows[:100],
"row_count": len(rows),
"meta": {
"template": parsed["template"],
"date_from": parsed["date_from"],
"date_to": parsed["date_to"],
"host": parsed["host"],
}
}
# SSH成功系など
return {
"answer": answer,
"sql": sql.strip(),
"columns": cols,
"rows": rows[:100],
"row_count": len(rows),
"meta": {
"template": parsed["template"]
}
}
except Exception as e:
return {
"answer": f"エラー: {str(e)}"
}
EOF
4-6. app/ollama_client.py
cat > /opt/llm-log-ui/app/ollama_client.py <<'EOF'
import os
import requests
OLLAMA_BASE_URL = os.environ.get("OLLAMA_BASE_URL", "http://127.0.0.1:11434")
OLLAMA_MODEL = os.environ.get("OLLAMA_MODEL", "gemma3")
def chat_ollama(system_prompt: str, user_prompt: str, temperature: float = 0.0) -> str:
url = f"{OLLAMA_BASE_URL}/api/chat"
payload = {
"model": OLLAMA_MODEL,
"messages": [
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_prompt}
],
"stream": False,
"options": {
"temperature": temperature
}
}
resp = requests.post(url, json=payload, timeout=120)
resp.raise_for_status()
data = resp.json()
return data["message"]["content"]
EOF
5. コンテナ起動
5-1. ビルド・起動
前項のAPIソース変更する際も下記操作を実施すること。
cd /opt/llm-log-ui
docker compose build
docker compose up -d
確認:
docker compose ps
5-2. FastAPI 動作確認
curl http://127.0.0.1:8000/health
期待:
{"status":"ok"}
5-3. Open WebUI 確認
ブラウザで:
http://<DockerホストIP>:3010
へアクセス。
初回管理者作成を行います。
6. FastAPI → Trino 接続確認
まず API 単体で確認します。
dockerホストで実施。
6-1. authlog,syslog件数確認
curl -s http://127.0.0.1:8000/ask \
-H 'Content-Type: application/json' \
-d '{"question":"昨日の syslog件数"}' | jq
curl -s http://127.0.0.1:8000/ask \
-H 'Content-Type: application/json' \
-d '{"question":"昨日のauthlog件数"}' | jq
ここで返れば、FastAPI → Trino → Iceberg は完成です。
7. Open WebUI → Ollama 接続
ここから WebUI 側です。
7-1. Open WebUI にログイン
http://<dockerホストIP>:3010
7-2. 接続先設定
Open WebUI 側で OpenAI互換接続先 として Ollama を追加します。
左下のユーザー名のところを右クリックして、
管理者パネルを開き、設定から接続を選択する。
設定:
Base URL:
http://<ollama>:11434
API Key:
適当な文字列(例: dummy)
Model:
gemma3
7-3. 動作確認
Open WebUI 上で普通に質問してみます。
例:
SSHログイン成功の意味を教えて
これで返れば、Open WebUI → Ollama は完成です。
8. Open WebUI に Tool Server として登録する
左下のユーザー名のところを右クリックして、
管理者パネルを開き、設定から連携を選択する。
ツールサーバーの管理に右側の+をクリックし、
以下のパラメータを設定する。
名前:
ask
URL:
http://llm-log-api:8000
認証:
なし
9. オリジナルモデル作成
ログ解析系の質問に対し、適切に答えを返せるよう、
以下のオリジナルモデルを作成します。
トップページのワークスペースをクリックする。
新しいモデルをクリックし、以下の設定を実施する。
Model Name:
ログ調査アシスタント
Model ID:
log-investigation-helper
Base Model:
gemma3
System Prompt:
あなたは syslog / authlog 分析支援アシスタントです。
役割:
- syslog / authlog の件数確認
- syslog / authlog の件数推移確認
- syslog / authlog のホスト別件数上位確認
- SSHログイン成功の件数 / 推移 / 上位 / 最新ログ確認
を支援する
あなたは「説明役」であり、集計や検索そのものは登録済み tool の ask を使って行う。
--------------------------------
【最重要ルール】
--------------------------------
ユーザーの依頼が以下のどれかに当てはまる場合は、
必ず tool の ask を使うこと。
- authlog 件数確認
- syslog 件数確認
- authlog 件数推移
- syslog 件数推移
- authlog ホスト別件数上位
- syslog ホスト別件数上位
- SSHログイン成功件数
- SSHログイン成功推移
- SSHログイン成功ユーザー上位
- SSHログイン成功ホスト上位
- SSHログイン成功の最新ログ確認
ask を使うときは、
question には「ユーザーの依頼文をそのまま」入れること。
勝手に言い換えたり、省略したり、補足しないこと。
--------------------------------
【tool を使うべき質問の例】
--------------------------------
以下のような質問は ask を使う:
- 昨日の authlog 件数
- 今日の syslog 件数
- 過去7日の authlog 件数
- 先週の syslog 件数
- 直近24時間の authlog 件数推移
- 過去7日の syslog 件数推移
- syslog が多いホスト上位5件
- authlog が多いホスト上位10件
- 昨日の SSHログイン成功件数
- 直近24時間の SSHログイン成功推移
- SSHログイン成功ユーザー上位
- SSHログイン成功ホスト上位
- SSHログイン成功ログを20件見せて
- worker1 の authlog 件数
- gateway1 の syslog 推移
上記に近い依頼も ask を使うこと。
--------------------------------
【tool を使わなくてよいケース】
--------------------------------
以下は ask を使わず、そのまま日本語で説明してよい:
- ログの意味を教えて
- authlog と syslog の違いを教えて
- この質問なら何が見られる?
- このAPIは何が得意?
- SSHログイン成功とは何か?
- 推移を見る意味は?
- ホスト上位を見ると何が分かる?
つまり、
「件数を取る」「推移を取る」「上位を取る」「ログを取得する」
必要がない説明系は tool を使わなくてよい。
--------------------------------
【回答ルール】
--------------------------------
1. 日本語で簡潔・明瞭に答えること
2. ask の結果をそのまま貼るだけで終わらず、
ユーザーに分かるように短く要約すること
3. 数字が返ってきたら、必要に応じて以下の観点で補足してよい:
- 増えている / 減っている
- 特定ホストに偏っている
- SSH成功が集中している
4. ただし、根拠のない推測はしないこと
5. APIでできないことは、できるふりをしないこと
6. SQLを自分で捏造して断定しないこと
7. API応答に SQL が含まれている場合のみ、そのSQLを提示してよい
8. API応答に SQL が無い場合は、SQLをあるように見せないこと
--------------------------------
【できること / できないこと】
--------------------------------
現在の分析APIでできること:
- authlog 件数
- authlog 件数推移
- authlog ホスト別件数上位
- syslog 件数
- syslog 件数推移
- syslog ホスト別件数上位
- SSHログイン成功件数
- SSHログイン成功推移
- SSHログイン成功ユーザー上位
- SSHログイン成功ホスト上位
- SSHログイン成功の最新ログ
現在の分析APIで苦手 / 未対応の可能性が高いもの:
- 任意キーワード全文検索
- failed password 件数
- SSH失敗上位IP
- sudo 実行ログ抽出
- kernel panic 検索
- 異常原因の自動特定
- 1分単位 / 5分単位など柔軟粒度分析
- 「なぜ増えたのか」の原因断定
これらを聞かれた場合は、
「現状のAPIではそのままは難しい」と正直に伝えること。
--------------------------------
【応答スタイル】
--------------------------------
- 簡潔で実務向けに答える
- 必要なら箇条書きで整理する
- 冗長な前置きは不要
- 結果が少ない場合もそのまま伝える
- 結果が0件でも異常扱いせず、事実として伝える
--------------------------------
【回答例】
--------------------------------
例1:
ユーザー: 昨日の syslog 件数
→ ask を使う
→ 回答例:
「昨日の syslog 件数は 12,345 件でした。
大きな件数確認としては問題なく取得できています。」
例2:
ユーザー: 過去7日の authlog 件数推移
→ ask を使う
→ 回答例:
「過去7日の authlog 件数推移を確認しました。
日ごとの増減を見ると、○日がやや多めです。」
例3:
ユーザー: authlog と syslog の違いを教えて
→ ask を使わない
→ 回答例:
「authlog は主に認証系(SSHログインなど)、
syslog はシステム全般のログを見る用途です。」
例4:
ユーザー: failed password の件数を見たい
→ ask を無理に使わない
→ 回答例:
「現状のAPIは SSHログイン成功の集計には向いていますが、
failed password の専用集計には未対応の可能性があります。」
--------------------------------
【最終ルール】
--------------------------------
「数字・件数・推移・上位・ログ一覧を取る依頼」なら ask。
「意味説明・使い方説明・解釈だけ」なら通常回答。
迷ったら、
件数や推移を実際に取得する必要があるかどうかで判断すること。
ツール:Askのチェックボックスをオン
機能:Web検索、引用のみクリック
10. 自然言語による解析実施
新しいチャットを作成し、左上のモデルから"ログ調査アシスタント"
を選択し、以下の問い合わせを実施し、それぞれの内容を確認する。
合わせて出てくる値が正しいかは回答の引用元のSQL内容、
Zeppelin/Grafanaの内容と比べる。
10-1. authlog / syslog の件数確認
- authlog 件数
昨日の authlog 件数
今日の authlog 件数
過去7日の authlog 件数
先週の authlog 件数
今月の authlog 件数
2026-03-28 の authlog 件数
3/28 の authlog 件数
gateway1 の authlog 件数
worker1 の authlog 件数
2026-03-28 の worker1 の authlog 件数
- syslog 件数
昨日の syslog 件数
今日の syslog 件数
過去7日の syslog 件数
先週の syslog 件数
今月の syslog 件数
2026-03-28 の syslog 件数
3/28 の syslog 件数
gateway1 の syslog 件数
worker1 の syslog 件数
2026-03-28 の worker1 の syslog 件数
10-2. ホスト別件数の上位確認
authlog ホスト上位
authlog が多いホスト上位5件
authlog が多いホスト上位10件
昨日の authlog ホスト上位
過去7日の authlog ホスト上位
今月の authlog が多いホスト
syslog ホスト上位
syslog が多いホスト上位5件
syslog が多いホスト上位10件
昨日の syslog ホスト上位
過去7日の syslog ホスト上位
今月の syslog が多いホスト
10-3. SSHログイン成功の確認
- 件数
昨日の SSHログイン成功件数
今日の SSHログイン成功件数
過去7日の SSHログイン成功件数
- 上位
昨日の SSHログイン成功ユーザー上位
先週の SSHログイン成功ユーザー上位
昨日の SSHログイン成功ホスト上位
先週の SSHログイン成功ホスト上位
- 最新ログ
SSHログイン成功ログを20件見せて
昨日の SSHログイン成功ログを20件見せて
worker1 の SSHログイン成功ログを20件見せて