連載: NMS開発者がLLMブートキャンプで学んだこと
前回は、重要度分類器を作りながら、system と user を分けること、few-shot で境界例を渡すこと、Output Parser で JSON を検証することの大事さを見ました。
ただ、プロンプトを少し真面目に書き始めると、すぐに別の問題が見えてきます。
プロンプトが長くなる。
few-shot の例を増やすとさらに長くなる。
障害ログをそのまま入れると、入力が一気に大きくなる。
そして、同じプロンプトでも temperature を変えると出力の雰囲気が変わる。
LLM を業務システムに組み込むとき、Token と Temperature は地味な設定値に見えます。しかし実際には、コスト、速度、文脈、安定性、出力品質に直接効いてくる設計項目でした。
今回は、授業で扱った response.usage.total_tokens、tiktoken、context length、max_tokens、temperature の実験を、NMS の障害ログ分析に寄せて整理します。
背景
NMS の現場で扱うログは、短いようで長いです。
1行だけなら、たいした量ではありません。
2026-03-10 21:14:03 CRITICAL router-core-01 BGP neighbor 10.10.0.2 Down
しかし、実際の障害対応ではこうなります。
2026-03-10 21:14:03 CRITICAL router-core-01 BGP neighbor 10.10.0.2 Down
2026-03-10 21:14:08 WARNING router-core-01 Interface Gi0/1 input errors increased
2026-03-10 21:14:15 CRITICAL nms-poller SNMP timeout router-core-01
2026-03-10 21:14:18 WARNING router-core-01 Interface Gi0/1 input errors increased
2026-03-10 21:14:24 CRITICAL nms-poller SNMP timeout router-core-01
さらに、大規模障害では同じようなログが数十行、数百行と出ます。
最初は、こう考えていました。
「LLM は長い文章を読めるのだから、ログを全部渡せばよいのではないか」
でも、授業でトークン使用量を確認し、tiktoken で数え、会話履歴が増えると入力トークンも増えることを見て、この考え方は危ないと分かりました。
トークンは料金だけの話ではありません。
LLM に渡せる情報量、応答速度、回答の安定性、重要情報が埋もれるかどうかにも関係します。
NMS の障害分析で言えば、ログを全部入れることは「情報をたくさん渡す」ことではなく、「ノイズも一緒に渡す」ことになる場合があります。
API レスポンスの usage を見る
前の授業では、OpenAI API のレスポンスを少しずつ分解して見ました。
response
response.choices
response.choices[0].message.content
response.usage.total_tokens
最初に response.usage.total_tokens を見たときは、単に「どれくらい使ったか分かる数字」くらいに思っていました。
しかし、実務目線ではこの値はかなり重要です。
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "user", "content": "API とは何ですか?"}
],
)
print(response.usage.total_tokens)
API の回答本文だけを見ていると、LLM がどれくらいの入力を読み、どれくらいの出力を生成したのかが分かりません。
でも、業務システムに入れるなら、少なくとも次のような情報は残したくなります。
- どのモデルを使ったか
- 入力トークン数はいくつか
- 出力トークン数はいくつか
- 合計トークン数はいくつか
-
finish_reasonはstopかlengthか - 実行時間はどれくらいか
障害対応システムに LLM を入れるなら、これは監査ログにもなります。
たとえば、ある障害チケットで AI が要約を作ったとします。そのとき、どのログを入力し、どのモデルで、何トークン使い、出力が途中で切れていないかを後から確認できる必要があります。
LLM の回答だけを保存するのでは足りません。
LLM 呼び出し自体も観測対象にする必要があります。
これは NMS を作ってきた感覚にも近いです。監視システムは、監視対象だけでなく、自分自身の状態も見なければいけません。LLM 機能も同じで、API 呼び出しの使用量や失敗を記録しないと、運用に乗せづらいです。
tiktoken で数えてみる
授業の資料にある実習コードを参考に、tiktoken を使ってトークン数を数えました。
import tiktoken
enc = tiktoken.encoding_for_model("gpt-4o-mini")
def count_token(text, model="gpt-4o-mini"):
enc = tiktoken.encoding_for_model(model)
return len(enc.encode(text))
この関数を使うと、API を呼ぶ前に入力テキストのおおよそのトークン数を確認できます。
障害ログで試すと、次のようになります。
log_one = "2026-03-10 21:14:03 CRITICAL router-core-01 BGP neighbor 10.10.0.2 Down"
logs_100 = "\n".join([
f"2026-03-10 21:{i:02d}:03 CRITICAL router-core-01 BGP neighbor 10.10.0.2 Down"
for i in range(100)
])
print(count_token(log_one))
print(count_token(logs_100))
ここで見たいのは、正確な料金計算だけではありません。
「この入力は、LLM にそのまま渡してよい大きさなのか」
「重複ログを消すとどれくらい減るのか」
「few-shot の例を追加すると、どれくらい増えるのか」
こうした感覚を持つために、トークン数を数えます。
授業では、RecursiveCharacterTextSplitter.from_tiktoken_encoder() を使って、トークン数を基準に chunk を作る例もありました。
from langchain_text_splitters import RecursiveCharacterTextSplitter
splitter_tiktoken = RecursiveCharacterTextSplitter.from_tiktoken_encoder(
model_name="gpt-4o-mini",
chunk_size=50,
chunk_overlap=10,
)
chunks = splitter_tiktoken.split_text(long_text)
for i, chunk in enumerate(chunks):
tokens = count_token(chunk)
print(f"[chunk {i+1}] : {tokens} tokens")
print(chunk)
RAG の文書分割で使う話ではありますが、NMS のログ分析にも近い発想が使えます。
1回の障害で発生したログを、装置単位、時間帯単位、イベント種別単位で分ける。各 chunk のトークン数を見る。大きすぎる chunk は分割する。小さすぎる chunk はまとめる。
ログも、LLM に渡す前には「文書」として設計する必要があります。
生ログをそのまま渡す前に考えること
次の図は、今回の実験を一枚にまとめたものです。
同じ障害ログが100行並んでいても、情報量が100倍になるわけではありません。重複ログをイベント別に集約すると、入力 Token を減らしながら、発生件数や重要イベントを見つけやすくできます。
また、障害分類や JSON 生成では、表現の多様性より出力の安定性が重要です。そのため、NMS のような運用系では「ログを前処理してから、低い Temperature で試す」という順番が現実的です。
障害ログ 100行をそのまま渡す
今回の例として、障害ログ 100行の要約を考えます。
まず、何も考えずにそのまま渡す版です。
raw_logs = "\n".join([
"2026-03-10 21:14:03 CRITICAL router-core-01 BGP neighbor 10.10.0.2 Down",
"2026-03-10 21:14:08 WARNING router-core-01 Interface Gi0/1 input errors increased",
"2026-03-10 21:14:15 CRITICAL nms-poller SNMP timeout router-core-01",
] * 34)
prompt = f"""
以下の障害ログを読み、NOC 担当者向けに要約してください。
{raw_logs}
""".strip()
print(count_token(prompt))
これでも LLM は何かしら答えてくれます。
ただ、問題があります。
同じようなログが大量に並んでいるため、LLM にとって重要な変化が見えづらくなります。タイムスタンプ、ホスト名、同じ文言が何度も繰り返されます。トークンは増えますが、増えた分だけ情報価値が増えているとは限りません。
NMS の画面でも同じです。
アラート一覧に同じイベントが100件並んでいても、人間は状況を理解しやすくなりません。むしろ、最初にやるのは集約です。
- どの装置で起きているか
- どのイベント種別が多いか
- 最初に発生した時刻はいつか
- 最後に発生した時刻はいつか
- 重複しているログはどれか
- 顧客影響に関係しそうなイベントはどれか
LLM に渡す前にも、同じことをした方がよいと感じました。
前処理してから渡す
次に、簡単な前処理を入れます。
ここでは本格的なログパーサではなく、考え方を示すための小さな例です。
from collections import Counter
logs = [
"2026-03-10 21:14:03 CRITICAL router-core-01 BGP neighbor 10.10.0.2 Down",
"2026-03-10 21:14:08 WARNING router-core-01 Interface Gi0/1 input errors increased",
"2026-03-10 21:14:15 CRITICAL nms-poller SNMP timeout router-core-01",
] * 34
def normalize_log(line):
parts = line.split()
# 日付と時刻を除いて、イベントの本体だけを見る
return " ".join(parts[2:])
counter = Counter(normalize_log(line) for line in logs)
compressed = "\n".join(
f"{count}回: {event}"
for event, count in counter.most_common()
)
print(compressed)
print(count_token(compressed))
出力は、たとえばこうなります。
34回: CRITICAL router-core-01 BGP neighbor 10.10.0.2 Down
34回: WARNING router-core-01 Interface Gi0/1 input errors increased
34回: CRITICAL nms-poller SNMP timeout router-core-01
これだけでも、LLM に渡す情報はかなり整理されます。
元の100行ログでは、同じ情報が繰り返されていました。前処理後は「何が何回起きたか」が見えます。
もちろん、実務ではこれだけでは足りません。
最初の発生時刻と最後の発生時刻は必要です。装置種別も必要です。メンテナンス中かどうか、関連チケットがあるかどうかも見たくなります。
それでも、LLM に渡す前に集約するだけで、トークン数とノイズはかなり減ります。
このときのプロンプトは、次のようにできます。
summary_prompt = f"""
あなたはネットワーク運用センターの一次対応担当者です。
以下は障害ログを重複集約したものです。
ログの件数だけで断定せず、イベント種別と対象装置を見て、
NOC 担当者が最初に確認すべき内容を JSON で整理してください。
集約ログ:
{compressed}
出力キー:
- severity
- summary
- suspected_cause
- first_actions
- missing_information
""".strip()
ポイントは、LLM に「生ログ100行」を渡すのではなく、「集約した観測結果」を渡すことです。
これは、LLM に任せる前にアプリケーション側でやるべき仕事だと思います。
5分で試す NMS 障害ログ Token Budget Checker
ここまでの内容を、OpenAI API や DB を使わずに確認できる小さなプロジェクトにしました。
NMSを模した100行のサンプルログを作成し、次の三つの入力サイズを比較します。
- 生ログ
- 完全一致するイベント本体を重複排除したログ
- 件数と最初・最後の発生時刻を付けて集約したログ
tiktoken がインストールされていれば gpt-4o-mini の Tokenizer を使います。未インストールでも、標準ライブラリだけの簡易推定で前処理前後を比較できます。
# nms_token_budget.py
from collections import defaultdict
from dataclasses import dataclass
import re
try:
import tiktoken
except ImportError:
tiktoken = None
MODEL = "gpt-4o-mini"
@dataclass(frozen=True)
class Event:
level: str
device: str
event_type: str
detail: str
def build_sample_logs() -> list[str]:
patterns = [
Event("CRITICAL", "router-core-01", "BGP_DOWN", "peer=10.10.0.2"),
Event("WARNING", "router-core-01", "INPUT_ERRORS", "ifName=Gi0/1"),
Event("CRITICAL", "nms-poller", "SNMP_TIMEOUT", "target=router-core-01"),
Event("WARNING", "switch-access-12", "CPU_HIGH", "value=92%"),
]
logs = []
for i in range(100):
event = patterns[i % len(patterns)]
logs.append(
f"2026-03-10 21:{14 + i // 60:02d}:{i % 60:02d} "
f"{event.level} {event.device} {event.event_type} {event.detail}"
)
return logs
def event_body(line: str) -> str:
return " ".join(line.split()[2:])
def aggregate_logs(logs: list[str]) -> list[str]:
groups = defaultdict(list)
for line in logs:
groups[event_body(line)].append(line)
result = []
for body, lines in groups.items():
first_time = " ".join(lines[0].split()[:2])
last_time = " ".join(lines[-1].split()[:2])
result.append(
f"count={len(lines)} first={first_time} "
f"last={last_time} event={body}"
)
return result
def count_tokens(text: str) -> int:
if tiktoken is not None:
encoder = tiktoken.encoding_for_model(MODEL)
return len(encoder.encode(text))
# インストールなしで前処理前後を比較するための簡易推定
return len(re.findall(r"[A-Za-z0-9_.:/=%-]+|[^\s]", text))
def report(name: str, lines: list[str]) -> int:
tokens = count_tokens("\n".join(lines))
print(f"{name:18} lines={len(lines):3d} tokens={tokens:4d}")
return tokens
def main() -> None:
raw_logs = build_sample_logs()
deduplicated = list(dict.fromkeys(event_body(line) for line in raw_logs))
aggregated = aggregate_logs(raw_logs)
counter = MODEL if tiktoken is not None else "simple estimate"
print(f"token counter={counter}")
raw_tokens = report("raw", raw_logs)
report("deduplicated", deduplicated)
aggregated_tokens = report("aggregated", aggregated)
reduction = (1 - aggregated_tokens / raw_tokens) * 100
print(f"token reduction: {reduction:.1f}%")
print("\n--- aggregated input for LLM ---")
print("\n".join(aggregated))
print("\nrecommended generation settings")
print("temperature=0, output=JSON, first_actions<=3")
if __name__ == "__main__":
main()
モデルと同じ Tokenizer で数えたい場合だけ、先に次を実行します。OpenAI API Key は不要です。
python -m pip install tiktoken
python nms_token_budget.py
依存関係なしの簡易推定では、次の結果になりました。
token counter=simple estimate
raw lines=100 tokens= 600
deduplicated lines= 4 tokens= 16
aggregated lines= 4 tokens= 36
token reduction: 94.0%
集約版は、単純な重複排除版より Token 数が少し多くなります。件数と最初・最後の発生時刻を追加しているためです。
目的は最小 Token 数にすることではありません。障害分析に必要な情報を残しながら、生ログより小さくすることです。
実際の NMS では、この処理を次のような入力ゲートに発展させられます。
SNMP Trap / Syslog
-> 正規化
-> 重複排除
-> 装置・イベント別集約
-> Token Budget 確認
-> LLM 要約
-> JSON Validation
-> チケット候補
NMS 側で確定的に処理できる集約は先に行い、LLM には要約、原因候補、一次対応候補の整理を任せます。この責務分離によって、Token コストを抑えながら、出力の根拠と再現性を高められます。
トークンは文脈管理でも効いてくる
会話履歴を扱う実習コードでは、履歴が増えるとトークン数も増えることを tiktoken で確認できます。
ノートでは、次のような関数でメッセージのトークン数を数えていました。
import tiktoken
enc = tiktoken.encoding_for_model("gpt-4o-mini")
def count_tokens(messages):
total = 0
for msg in messages:
total += len(enc.encode(msg.content))
total += 4
return total
そして、会話が増えるたびにトークン数が増えていくことを見ました。
conversation = [SystemMessage(content="あなたは親切な AI アシスタントです")]
sample_exchanges = [
("私の名前は abc です。よろしくお願いします", "よろしくお願いします、abc さん。"),
("私の名前は何ですか?", "あなたの名前は abc さんです。")
]
for i, (user_msg, ai_msg) in enumerate(sample_exchanges):
conversation.append(HumanMessage(user_msg))
conversation.append(AIMessage(ai_msg))
tokens = count_tokens(conversation)
print(f"{i} | {tokens} |")
これは、障害対応アシスタントを考えるとかなり重要です。
障害対応では、会話が続きます。
最初のアラートを要約する。追加ログを入れる。担当者が確認結果を書く。LLM が次の確認項目を出す。さらに別のログが追加される。復旧後に報告文を作る。
この履歴を全部 LLM に渡し続けると、トークンは増え続けます。
しかも、古い仮説が残ることもあります。
たとえば、最初は「BGP neighbor down が主原因かもしれない」と考えたが、その後「監視サーバ側の SNMP 到達性問題だった」と分かったとします。このとき、古い仮説をそのまま履歴に残し続けると、LLM が後の回答でも BGP 側に引っ張られる可能性があります。
授業では trim_messages のように、メッセージ履歴を切る考え方も出てきました。
from langchain_core.messages import trim_messages
trimmer = trim_messages(
max_tokens=4,
strategy="last",
token_counter=len,
start_on="human",
)
trimmed = trimmer.invoke(messages)
実務では、単純に古いものから切ればよいわけではありません。
障害対応なら、残すべき情報と捨ててもよい情報を分けたいです。
- 確定した事実
- まだ仮説の情報
- すでに否定された仮説
- 復旧済みの作業
- 顧客報告に必要な情報
- 単なる重複ログ
Token 管理は、単なる節約ではなく、文脈管理でもあります。
max_tokens で出力を制限する
別の実習コードでは、max_tokens による出力制限も試しました。
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "user", "content": "SNMP Trapを受信したときの一次切り分け手順を、初心者向けに説明してください"}
],
max_tokens=50,
)
max_tokens を小さくすると、出力が途中で切れることがあります。
API のレスポンスでは、finish_reason が length になることもあります。
これは障害対応では危険です。
たとえば、一次対応手順を JSON で返してほしいのに、途中で切れると parse できません。
{
"severity": "critical",
"summary": "router-core-01 で BGP neighbor down と SNMP timeout が発生しています。",
"first_actions": [
"router-core-01 への疎通を確認する",
"BGP neighbor 10.10.0.2 の状態を確認する",
人間が見れば「途中で切れたな」と分かります。
でも、システムとしては壊れた JSON です。
そのため、max_tokens は小さければよいわけではありません。
出力を短くしたいなら、max_tokens だけで切るのではなく、プロンプト側で出力項目を絞る方が安全です。
出力は JSON のみ。
summary は 120文字以内。
first_actions は最大3件。
各 action は 60文字以内。
そのうえで、max_tokens は異常に長い出力を防ぐ上限として使う。
このくらいの感覚が実務向きだと思いました。
Temperature を変えると何が変わるか
授業では、同じ質問に対して temperature=0.0 と temperature=1.5 のように値を変え、出力の違いを見ました。
question = "人工知能の未来について、一文で説明してください"
for i in range(3):
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": question}],
temperature=0.0,
)
print(f"{i} : {response.choices[0].message.content}")
別のセルでは、temperature=1.5 でも試していました。
for i in range(3):
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": question}],
temperature=1.5,
)
print(f"{i} : {response.choices[0].message.content}")
さらに、softmax の温度として、logits に対して temperature を変える例もありました。
logits = [2.0, 1.5, 1.0, 0.5]
temperatures = [0.1, 0.5, 1.0, 2.0, 10.0, 100.0]
for T in temperatures:
probs = calculate_softmax(logits, T)
print(f"{T} | {probs}")
ざっくり言えば、temperature が低いほど高確率の候補に寄りやすく、出力は安定しやすくなります。
temperature が高いほど、候補の分布が平らになり、出力に多様性が出やすくなります。
ただし、ここで誤解しやすいことがあります。
temperature=0 にすれば、常に完全に同じ結果が保証されるわけではありません。
また、temperature を上げれば賢くなるわけでもありません。
これは、創造性のつまみというより、次のトークン選択の揺れを調整するパラメータです。
障害分析で Temperature を上げてみる
NMS の障害分析で、temperature を変えるとどうなるかを考えます。
入力は、次のようなログです。
2026-03-10 21:14:03 CRITICAL router-core-01 BGP neighbor 10.10.0.2 Down
2026-03-10 21:14:08 WARNING router-core-01 Interface Gi0/1 input errors increased
2026-03-10 21:14:15 CRITICAL nms-poller SNMP timeout router-core-01
このログに対して、temperature=0 で JSON 要約を作ると、比較的安定した出力を期待できます。
response = client.chat.completions.create(
model="gpt-4o-mini",
temperature=0,
messages=[
{
"role": "system",
"content": """
あなたはネットワーク運用センターの一次対応担当者です。
障害ログを読み、NOC 担当者が最初に確認すべき内容を整理してください。
出力は JSON のみ。
キーは severity, summary, suspected_cause, first_actions にしてください。
""".strip(),
},
{
"role": "user",
"content": incident_log,
},
],
)
一方、temperature=1.0 や 1.5 にすると、表現の幅は出るかもしれません。
しかし、障害分析ではそれが良いとは限りません。
たとえば、次のような揺れが起きる可能性があります。
-
severityがcriticalになったりhighになったりする - 原因候補が広がりすぎる
- ログにない可能性まで強めに書く
- first_actions の順番や粒度が毎回変わる
- JSON の表現が少しずつ変わる
顧客向けのお知らせ文を複数案作るなら、少し高めの temperature は役に立つかもしれません。
しかし、障害ログの分類、JSON 生成、チケット初期化、一次対応手順の抽出では、私は低めから始める方が自然だと感じました。
Temperature は用途で決める
今回の実験で、自分の中では次のように整理しました。
temperature=0 付近:
- JSON 出力
- 障害重要度分類
- ログ要約
- RAG の根拠付き回答
- 手順書ベースの回答
temperature=0.3〜0.7:
- チケット説明文の改善
- 顧客向け文面の自然化
- 運用者向けコメントの言い換え
- FAQ 回答の読みやすさ調整
temperature=1.0 以上:
- アイデア出し
- 複数案の生成
- 表現バリエーション
- 学習用サンプル作成
もちろん、これは絶対のルールではありません。
モデルやタスク、プロンプト、出力形式によって変わります。
ただ、NMS のような運用系では、創造性より再現性が大事な場面が多いです。
特に、次のような処理では低めにしたいです。
-
severityを決める - チケット登録用 JSON を作る
- Runbook の手順を抜き出す
- RAG で社内文書に基づいて答える
- 自動通知の文面を作る
逆に、次のような処理では少し揺れがあってもよいと思います。
- 顧客向け説明文を複数案作る
- 障害報告書の表現を改善する
- ポストモーテムのタイトル案を出す
- 運用改善アイデアをブレストする
同じ NMS ドメインでも、用途によって適切な temperature は違います。
RAG と Temperature
RAGの実習資料では、回答がうまくいかないときに再生成する流れの中で、temperature=0.7 の LLM を使うメモがありました。
llm_temp = ChatOpenAI(model="gpt-4o-mini", temperature=0.7)
RAG の文脈では、temperature の扱いは少し悩ましいです。
根拠文書に厳密に従ってほしいなら低めがよいです。
でも、回答が硬すぎる、要約がうまくまとまらない、別の言い方で再生成したい、という場面では少し上げる選択肢もあります。
ただし、根拠から離れるリスクも増えます。
障害対応マニュアル RAG を考えると、これはかなり重要です。
たとえば、Runbook に「BGP neighbor down の場合は、まず peer 疎通、BGP 状態、回線アラートを確認する」と書いてあるとします。
このとき、LLM にはその手順に沿って答えてほしいです。
高い temperature で、もっと気の利いた提案を出してほしいわけではありません。むしろ、根拠にない作業を混ぜられると危険です。
RAG では、temperature だけでなく、プロンプトにも制約を書きたくなります。
必ず渡された文書に基づいて回答してください。
文書にない内容は「文書内では確認できません」と書いてください。
推測で手順を追加しないでください。
つまり、Temperature は単独で効く設定ではありません。
検索品質、プロンプト、出力検証、根拠表示とセットで考える必要があります。
stop と seed も少し触ってみる
APIパラメータの実習コードでは、stop や seed も試しました。
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "user", "content": "果物を10個、番号付きで列挙してください"}
],
stop=["4."],
temperature=0,
)
stop は、特定の文字列が出たら生成を止めるためのものです。
出力形式を制御したいときに使える場面はありますが、業務システムでは慎重に使う必要があります。意図しないところで止まると、JSON が壊れたり、必要な手順が欠けたりします。
seed は、同じ条件での再現性を少し扱いやすくするためのパラメータとして試しました。
for seed_val in [123, 123, 456]:
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": "色を3つ推薦してください"}],
seed=seed_val,
temperature=1.0,
)
print(response.choices[0].message.content)
ただ、これも「完全に同じ結果を保証する魔法」ではなく、実務ではテストや比較実験の補助として見るくらいがよさそうです。
NMS の障害対応では、seed で無理に再現性を作るより、低い temperature、明確な出力 schema、validation、監査ログを組み合わせる方が大事だと思いました。
業務で使うなら
Token と Temperature を NMS の現場で使うなら、私は最初に次の設計を入れたいです。
まず、LLM に渡す前のログ前処理です。
- 重複ログを集約する
- 装置名、interface、peer、service を抽出する
- 発生回数、最初の時刻、最後の時刻をまとめる
- 重要度の低いイベントを別枠にする
- 個人情報や不要な文字列を削る
- 入力トークン数を計測する
次に、LLM 呼び出し時のパラメータ設計です。
- 障害分類や JSON 出力は
temperature=0付近から始める - 顧客向け文面生成は少しだけ temperature を上げて比較する
-
max_tokensは出力破損を避ける範囲で上限として使う -
finish_reasonを確認する -
usage.total_tokensを保存する
最後に、運用ログです。
- 入力トークン数
- 出力トークン数
- モデル名
- temperature
- max_tokens
- finish_reason
- JSON parse 成功/失敗
- retry 回数
このあたりを保存しておくと、後から「なぜこの回答になったのか」「なぜコストが増えたのか」「どのログで失敗しやすいのか」を確認できます。
LLM を使った障害対応機能も、普通の業務機能と同じように観測可能にする必要があります。
まとめ
Token と Temperature は、LLM の中ではかなり地味なテーマに見えます。
しかし、NMS や障害対応のような運用系の用途では、実務品質を左右する重要なパラメータでした。
トークン数を見ずに長いログをそのまま入れると、コストが増え、応答が遅くなり、重要な情報が埋もれる可能性があります。会話履歴を全部渡し続けると、古い仮説に引っ張られることもあります。
temperature=0 にすれば完全に決定的になるわけではありませんが、障害分類や JSON 出力では低めから始めるのが自然です。一方で、顧客向け文面や改善案のブレストでは、少し揺れを持たせる価値があります。
今回の実験で分かったのは、LLM に何を渡すか、どう使うかを決める作業は、「何をしたいか」を LLM 向けに具体的に考え直す作業だということでした。
こうした地味な工夫の積み重ねが、LLM を業務システムに近づけていきます。
次回は、この「何を渡すか」という話をさらに進めて、Embedding と Vector DB をネットワーク運用目線で整理します。
ログやマニュアルをただ長い文章として渡すのではなく、意味で検索し、必要な部分だけを LLM に渡す。
RAG の入口になる部分です。
