🤖

GenAI向けのOpenTelemetry SemanticsとCloud Traceへの表示

に公開

ADKを試していて、Cloud TraceにAI AgentのTrace情報が入っていることが確認できています。
ただ、画面側を見ると、すべての情報が表示されているわけではないようです。

そこでADKのTracing部分を見つつ、それを参考に簡単なLLMを利用しない疑似アプリケーションを作成して、Cloud TraceにAI Agentっぽい情報を表示してみましょう。

Note: ADKにこのあたりの話をissue登録した。

https://github.com/google/adk-python/issues/356

先にまとめ

  • OpenTelemetryのSpan属性やEventを設定すればCloud Traceに生成AI用の情報を表示できるよ
  • ただこの分野はまだまだ議論中なので、様子見でもいいかもね

ADKのTracing周りの実装

ADKでは telemetry.py という部分でTracingに関する情報を扱っています。

https://github.com/google/adk-python/blob/923d8066373bfad01bbf4cc2aea460e4cfadb228/src/google/adk/telemetry.py#L18

内部ではOpenTelemtryを利用しており、Spanに各種属性を設定して、送信していることがわかります。

span.set_attribute('gen_ai.system', 'gcp.vertex.agent')
  span.set_attribute(
      'gcp.vertex.agent.invocation_id', invocation_context.invocation_id
  )
  span.set_attribute('gcp.vertex.agent.event_id', event_id)
  span.set_attribute(
      'gcp.vertex.agent.tool_response',
      function_response_event.model_dump_json(exclude_none=True),
  )

  # Setting empty llm request and response (as UI expect these) while not
  # applicable for tool_response.
  span.set_attribute('gcp.vertex.agent.llm_request', '{}')
  span.set_attribute(
      'gcp.vertex.agent.llm_response',
      '{}',
  )

いくつかのコードを見る限り、これらの情報をCloud Traceに送れば冒頭で紹介したような「生成AI」というタグが付いたTraceが表示されそうです。

そこでまずこれらのspan属性を送信するだけの簡単なアプリケーションを書いてみます。

サンプルアプリ①

ではサンプルアプリケーションを作成します。
なおOpenTelemetryを利用して、Cloud Traceへトレース情報を送るためには、今まではCloudTraceSpanExporter(opentelemetry-exporter-gcp-trace)を利用してきましたが、
最近のアップデートでCloud TraceがOpenTelemetry Protocol(OTLP)に対応しました。

Cloud TraceのAPIと比べて、OTLPの方がかなりAPI制限が緩和されており、送信可能な属性情報が大きいです。LLMのような大量のテキストを扱うアプリケーションではOTLPを利用したほうが適しているため、今回はOTLP経由でspanを扱いたいと思います。

まず必要なライブラリをインストールします。

uv add opentelemetry-api opentelemetry-exporter-otlp-proto-http opentelemetry-sdk

次にサンプルアプリケーションを作ります。
最小実装です。

main.py
import google.auth
import os
from opentelemetry import trace
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from google.auth.transport.requests import AuthorizedSession
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor

def setup_tracing():
    credentials, project_id = google.auth.default(quota_project_id=os.environ.get('GOOGLE_CLOUD_PROJECT'))
    resource = Resource.create(attributes={
        "gcp.project_id": os.environ.get('GOOGLE_CLOUD_PROJECT', project_id)
    })

    trace_provider = TracerProvider(resource=resource)
    trace_provider.add_span_processor(
        BatchSpanProcessor(OTLPSpanExporter(
            session=AuthorizedSession(credentials),
            endpoint="https://telemetry.googleapis.com:443/v1/traces",
        ))
    )
    trace.set_tracer_provider(trace_provider)    

def main():
    
    setup_tracing()
    
    tracer = trace.get_tracer(__name__)
    
    with tracer.start_as_current_span("main") as span:
        span.set_attribute('gen_ai.system', 'gcp.vertex.agent')
        span.set_attribute('gen_ai.request.model', "sample")

if __name__ == "__main__":
    main()

これで実行してみます。

uv run main.py

Cloud Traceを見てみましょう。

狙った通り最低限「生成AI」と表示されたspanが表示されていますね。

より情報を増やすには?

上のキャプチャは「生成 AI トークン数 0(入力)、0(出力)」や「表示する生成 AI データの OpenTelemetry 規則と一致するイベントはありません。」と書いてあります。

実運用を考えると実際に扱われたトークン数や、内部で扱われたメッセージをトレースしたいケースは非常に多いです。

これらの情報はどの様にすればよいのでしょうか?
そこでOpenTelemetryのリポジトリを探ってみると、Generative AIに関するSpanやEventの定義情報が見つかりました。

https://opentelemetry.io/docs/specs/semconv/gen-ai/

これらのSpan属性や、Event情報をつけることでよりこの部分に情報が増やせるのではないかと考えられますので色々情報を足してみましょう。

サンプルアプリ②

上の定義を見ると

トークン数に関しては以下の属性が使えそうです。

属性名
gen_ai.usage.output_tokens 数値
gen_ai.usage.input_tokens 数値

ではまずこれらを設定してみましょう。

main.py
    with tracer.start_as_current_span("main") as span:
        span.set_attribute('gen_ai.system', 'gcp.vertex.agent')
        span.set_attribute('gen_ai.request.model', "sample")
        
        span.set_attribute('gen_ai.usage.input_tokens', 100)
        span.set_attribute('gen_ai.usage.output_tokens', 200)

これで実行してみます。

uv run main.py

Cloud Traceを見てみましょう。

きたこれ!

実際のAI AgentではLLM APIが返却するusageなどからinput_tokenやoutput_tokenを取得して設定すると良いと思います。

GenAI用のEvent

次にLLMに渡されている情報をTracingしたいと思います。
Span属性を見ると、どうもプロンプトを入力するような属性がありません。

プロンプトやレスポンスについては、Span属性としてではなくEventのBodyとして定義するようです。
https://opentelemetry.io/docs/specs/semconv/gen-ai/gen-ai-events/

ただEventのBody部はEvent属性へ移行する予定ようですが、とりあえず今回はEvent属性として設定してみたいと思います。
(というより Span Eventを作成する span.addEvnet にbodyを受け取るようなインターフェースが無いので、どの様にbodyを追加するのかわかりません。)

https://github.com/open-telemetry/semantic-conventions/issues/1870

プロンプトに関するイベントは以下です。

gen_ai.system.message

Modelに対して、system messageを送信した場合に設定するEventです。
設定できる属性/ボディ部は以下です。

属性名/ボディ名
gen_ai.system 文字列 geminivertex_aiなどのモデルプロバイダ
content 文字列 プロンプトのcontent
role 文字列 プロンプトのrole(roleがsystemじゃ無いときのみ設定)

gen_ai.user.message

Modelに対して、user messageを送信した場合に設定するEventです。
設定できる属性/ボディ部は以下です。

属性名/ボディ名
gen_ai.system 文字列 geminivertex_aiなどのモデルプロバイダ
content 文字列 プロンプトのcontent
role 文字列 プロンプトのrole(roleがuserじゃ無いときのみ設定)

gen_ai.assistant.message

Modelに対して、assistant messageを送信した場合に設定するEventです。
設定できる属性/ボディ部は以下です。

属性名/ボディ名
gen_ai.system 文字列 geminivertex_aiなどのモデルプロバイダ
content 文字列 プロンプトのcontent
role 文字列 プロンプトのrole(roleがassistantじゃ無いときのみ設定)
tool_calls Map tool callsの定義 モデルによって作成されたfunction callingなどの値
function Map
arguments 文字列 モデルによって提供された関数の引数 JSONで返却されるけど文字としていれる
name 文字列 モデルによって提供された関数名
id 文字列 tool callingのID
type enum tool callingのタイプ(functionとか)

gen_ai.tool.message

Modelからtool or function callingが返却された場合に設定するEventです。

属性名/ボディ名
gen_ai.system 文字列 geminivertex_aiなどのモデルプロバイダ
content 文字列 プロンプトのcontent
role 文字列 プロンプトのrole(roleがtoolじゃ無いときのみ設定)
id 文字列 tool callingのID

gen_ai.choice.message

Modelからのレスポンスが有った場合にに設定するEventです。

属性名/ボディ名
gen_ai.system 文字列 geminivertex_aiなどのモデルプロバイダ
finish_reason enum 終了の理由
index int choiceのindex
message map response message
content 中身
role 文字列 role (assistantじゃない場合のみ設定)
tool_calls Map tool callsの定義 モデルによって作成されたfunction callingなどの値
function Map
arguments 文字列 モデルによって提供された関数の引数 JSONで返却されるけど文字としていれる
name 文字列 モデルによって提供された関数名
id 文字列 tool callingのID
type enum tool callingのタイプ(functionとか)

サンプルアプリ③

では実際にeventを発火してみます。

値の中には、Map型で定義されている値もありますが、Span属性の場合、Map型を直接扱えないため、別の形式で記述する必要があります。
今回はめんど.... JSON Encodeして突っ込みたいと思います。

main.py
    with tracer.start_as_current_span("main") as span:
        span.set_attribute('gen_ai.system', 'gcp.vertex.agent')
        span.set_attribute('gen_ai.request.model', "sample")

        span.set_attribute('gen_ai.usage.input_tokens', 100)
        span.set_attribute('gen_ai.usage.output_tokens', 200)

        span.add_event("gen_ai.system.message", attributes={"gen_ai.system": "vertex_ai", "content": "system message", "role": "system"})
        span.add_event("gen_ai.user.message", attributes={"gen_ai.system": "vertex_ai","content": json.dumps({"message": "テスト"}, ensure_ascii=False), "role": "user"})
        span.add_event("gen_ai.assistant.message", attributes={"gen_ai.system": "vertex_ai","tool_calls": json.dumps({"id": "tool_call_id", "type": "function", "function": {"name": "say_hello", "arguments": "{\"location\":\"Paris\"}"}}), "role": "assistant"})
        body = {
            "gen_ai.system": "sample",
            "finish_reason": "stop",
            "index": 0,
            "message": json.dumps({"content": "こんにちは 世界", "role": "bot"}, ensure_ascii=False),
        }
        span.add_event("gen_ai.choice", attributes=body)
        
        # 一応カスタムイベントもあるらしいので設定してみる
        span.add_event("gen_ai.custom", attributes={"gen_ai.system": "vertex_ai", "custom": "custom event"})

実行して、Cloud Traceで見てみます。

無事表示されました。
属性値としてユーザーメッセージや、レスポンスメッセージが見れるので、tracingとしては十分でしょう。
ただCloud Traceの対象部分のHTMLを見ると 空のHTMLコメント()があり、これはAngularなどで条件分岐でコンテンツを表示しなかった場合にも表示されるものです。
もしかしたら何かしらの条件で他の表示ができるかもしれませんが、ちょっとまだ条件が見つけれていません。

まとめ

今回はOpenTelemetryのGenerative AI(生成AI)定義を利用して、Cloud Traceに生成AI関連のトレース情報を表示してみました。

実際にはこれらはFrameworkのレイヤーで設定されるべきですが、OSSの実装を見る限りでは、ADK含めて、まだこれらの定義が利用されているものは見かけません。
生成AI用のOpenTelemetryである、traceloop/openllmetry( https://github.com/traceloop/openllmetry )でも実装されていないため、定義はあるが実装はまだの印象です。

このあたりの話し合いは以下で行われているようで、議事録を見る限りでは現在もSpan属性として送信するべきか、Eventとして送信するべきか議論の真っ最中のように見えます。

https://github.com/open-telemetry/community/blob/1c71595874e5d125ca92ec3b0e948c4325161c8a/projects/llm-semconv.md

https://docs.google.com/document/d/1EKIeDgBGXQPGehUigIRLwAUpRGa7-1kXB736EaYuJ2M/edit?tab=t.0

MCPについてはどうすんねんとか、関係者内での議論が見えて面白いですね。
MCPに関するPRは以下です。

https://github.com/open-telemetry/semantic-conventions/pull/2083

生成AI(やAI Agent)が急激に浸透する中で、実運用することを考えるとTracing/Metricsなどの測定は必須になっていきます。
コミュニティとしては、標準を作りつつある段階で、実Framework/サービスに落ちるのはもう少し先になるかもしれませんが、我々作る側からすると、早く欲しいものですね。

今後はADKやOpenLLMetryにこれらの機能が追加されていないか確認していきたいと思います。

Discussion