OpenLLMetry
OpenLLMetry は Traceloop チームによって Apache 2.0 ライセンスの下で開発・保守されており、OpenTelemetry の機能を拡張して LLM アプリケーション向けの専用の監視およびデバッグツールを提供します。OpenTelemetry の標準化されたテレメトリデータ形式を利用して、LLM アプリケーションの主要なパフォーマンス指標とトレース情報を標準化して出力します。
設定¶
OTEL を使ってトレースデータを DataKit に送信する前に、Collector が設定されていることを確認し、あわせて設定ファイルcustomer_tags = ["llm.request.type","traceloop.entity.path","llm.is_streaming","gen_ai.openai.api_base","gen_ai.prompt.1.content","gen_ai.response.model","gen_ai.completion.0.content","gen_ai.request.model","gen_ai.request.temperature","gen_ai.system","traceloop.workflow.name"] を次のように調整してください。
[[inputs.opentelemetry]]
## customer_tags はホワイトリストとして機能し、タグがデータセンターへ送信されるのを防ぎます。
## すべての . は _ に置き換えられます。例:
customer_tags = ["llm.request.type","traceloop.entity.path","llm.is_streaming","gen_ai.openai.api_base","gen_ai.prompt.1.content","gen_ai.response.model","gen_ai.completion.0.content","gen_ai.request.model","gen_ai.request.temperature","gen_ai.system","traceloop.workflow.name"]
...
調整が完了したら、DataKit を再起動します。
OpenTelemetry SDK のインストール¶
pip install opentelemetry-api opentelemetry-instrumentation pip install opentelemetry-instrumentation-flask
OpenLLMetry SDK のインストール¶
pip install traceloop-sdk
アプリケーションで OpenLLMetry を初期化する¶
from traceloop.sdk import Traceloop
# OpenLit を初期化
# Traceloop.init()
Traceloop.init(app_name="kimi_openllmetry_stream_flask")
OpenLLMetry のサンプルコード¶
import os
import httpx
from flask import Flask, request, Response,jsonify,stream_with_context
from traceloop.sdk import Traceloop
from traceloop.sdk.decorators import workflow,task
from openai import OpenAI
from opentelemetry.instrumentation.flask import FlaskInstrumentor
app = Flask(__name__)
# FlaskInstrumentor を使用して Flask アプリケーションを自動計装する
FlaskInstrumentor().instrument_app(app)
# OpenLit を初期化
Traceloop.init(app_name="kimi_openllmetry_stream_flask")
# 環境変数から API Key を取得する
api_key = os.getenv("MOONSHOT_API_KEY")
if not api_key:
raise ValueError("MOONSHOT_API_KEY 環境変数を設定してください")
client = OpenAI(
api_key=api_key,
base_url="https://api.moonshot.cn/v1",
)
def estimate_token_count(input_messages) -> int:
"""
入力メッセージのトークン数を計算します。
"""
try:
header = {
"Authorization": f"Bearer {api_key}",
}
data = {
"model": "moonshot-v1-128k",
"messages": input_messages,
}
with httpx.Client() as client:
print("API を呼び出しています")
r = client.post("https://api.moonshot.cn/v1/tokenizers/estimate-token-count", headers=header, json=data)
r.raise_for_status()
response_data = r.json()
print(response_data["data"]["total_tokens"])
return response_data["data"]["total_tokens"]
except httpx.RequestError as e:
print(f"リクエストに失敗しました: {e}")
raise
except (KeyError, ValueError) as e:
print(f"レスポンスの解析に失敗しました: {e}")
raise
def select_model(input_messages, max_tokens=1024) -> str:
"""
入力されたコンテキストメッセージと想定される max_tokens 値に応じて、適切なモデルを選択します。
"""
if not isinstance(max_tokens, int) or max_tokens <= 0:
raise ValueError("max_tokens は正の整数でなければなりません")
prompt_tokens = estimate_token_count(input_messages)
total_tokens = prompt_tokens + max_tokens
if total_tokens <= 8 * 1024:
return "moonshot-v1-8k"
elif total_tokens <= 32 * 1024:
return "moonshot-v1-32k"
elif total_tokens <= 128 * 1024:
return "moonshot-v1-128k"
else:
raise ValueError("トークン数が上限を超えています 😢")
@app.route('/ask', methods=['POST'])
@workflow(name="ask_workflow")
def ask():
data = request.json
messages = data.get('messages')
max_tokens = data.get('max_tokens', 2048)
if not messages:
return jsonify({"error": "messages フィールドは空にできません"}), 400
try:
model = select_model(messages, max_tokens)
completion = client.chat.completions.create(
model=model,
messages=messages,
max_tokens=max_tokens,
temperature=0.3,
stream=True # ストリーミング生成を有効化
)
def generate():
for chunk in completion:
# yield chunk.choices[0].delta.content or ''
delta = chunk.choices[0].delta
if delta.content:
print(delta.content, end="")
yield delta.content or ''
return Response(stream_with_context(generate()), content_type='text/event-stream')
except Exception as e:
return jsonify({"error": str(e)}), 500
if __name__ == '__main__':
app.run(debug=True,port=5001)
env を設定し、OpenTelemetry を通じてデータを Datakit に送信します。
export TRACELOOP_BASE_URL=http://localhost:9529/otel
メトリクスの詳細¶
| メトリクス名 | 説明 | 単位 |
|---|---|---|
gen_ai.client.generation.choices |
クライアントが生成した選択肢の数 | 個 |
gen_ai.client.operation.duration_bucket |
クライアント操作の継続時間ヒストグラムバケット | ミリ秒 |
gen_ai.client.operation.duration_count |
クライアント操作の総回数 | 回 |
gen_ai.client.operation.duration_max |
クライアント操作の最大継続時間 | ミリ秒 |
gen_ai.client.operation.duration_min |
クライアント操作の最小継続時間 | ミリ秒 |
gen_ai.client.operation.duration_sum |
クライアント操作の総継続時間 | ミリ秒 |
llm.openai.chat_completions.streaming_time_to_first_token_bucket |
OpenAI のチャット補完機能におけるストリーミング中の最初の Token 生成時間のヒストグラムバケット | ミリ秒 |
llm.openai.chat_completions.streaming_time_to_first_token_count |
OpenAI のチャット補完機能におけるストリーミング中の最初の Token 生成の総回数 | 回 |
llm.openai.chat_completions.streaming_time_to_first_token_max |
OpenAI のチャット補完機能におけるストリーミング中の最初の Token 生成の最大時間 | ミリ秒 |
llm.openai.chat_completions.streaming_time_to_first_token_min |
OpenAI のチャット補完機能におけるストリーミング中の最初の Token 生成の最小時間 | ミリ秒 |
llm.openai.chat_completions.streaming_time_to_first_token_sum |
OpenAI のチャット補完機能におけるストリーミング中の最初の Token 生成の総時間 | ミリ秒 |
llm.openai.chat_completions.streaming_time_to_generate_bucket |
OpenAI のチャット補完機能におけるストリーミング中の生成内容の総時間ヒストグラムバケット | ミリ秒 |
llm.openai.chat_completions.streaming_time_to_generate_count |
OpenAI のチャット補完機能におけるストリーミング中の生成内容の総回数 | 回 |
llm.openai.chat_completions.streaming_time_to_generate_max |
OpenAI のチャット補完機能におけるストリーミング中の生成内容の最大時間 | ミリ秒 |
llm.openai.chat_completions.streaming_time_to_generate_min |
OpenAI のチャット補完機能におけるストリーミング中の生成内容の最小時間 | ミリ秒 |
llm.openai.chat_completions.streaming_time_to_generate_sum |
OpenAI のチャット補完機能におけるストリーミング中の生成内容の総時間 | ミリ秒 |
参考資料¶
- OpenLLMetry quickstart
- OpenLLMetry otel-collector
- OpenLLMetry github