コンテンツにスキップ

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 のチャット補完機能におけるストリーミング中の生成内容の総時間 ミリ秒

参考資料

フィードバック

このページは役に立ちましたか?