Splunk Observability Cloud による Agentic AI アプリケーションの監視

2 minutes   Author Derek Mitchell

Splunk Observability for AI は、AI アプリケーションスタックのパフォーマンス、品質、セキュリティ、コストを監視します。以下の機能が含まれています

  • AI Agent Monitoring は、LLM およびエージェントアプリケーションのパフォーマンス、品質、セキュリティ、コストを監視します。
  • AI Infrastructure Monitoring は、AI インフラストラクチャの健全性、可用性、消費量(または使用量)を監視します。

このワークショップでは、Splunk Observability Cloud でこれらの機能をデプロイして操作する実践的な経験を提供します。以下の内容が含まれます

  • AI インフラストラクチャ関連のメトリクスを取得するために Azure アカウントを Splunk Observability Cloud に接続する方法を理解する。
  • AI インフラストラクチャに関連する標準搭載のダッシュボードナビゲーターを探索する。
  • LangChainLangGraph で構築された Agentic AI アプリケーションのアーキテクチャを確認する。
  • Agentic AI アプリケーションをデプロイし、OpenTelemetry計装する練習をする。
  • Splunk Observability Cloud でメトリクス、トレース、ログを使用してエージェントのパフォーマンスを理解する方法を探索する。
  • Agentic AI アプリケーションを変更してツールコールエージェントを使用する練習をする。
  • アプリケーションに品質問題を追加し、セマンティック品質評価を使用して Splunk Observability Cloud で検出する練習をする。
  • アプリケーションに AI Defense 計装セキュリティリスクを追加し、Splunk Observability Cloud で検出する練習をする。
Tip

このワークショップを進める最も簡単な方法は以下を使用することです

  • このページの右上にある左右の矢印(< | >
  • キーボードの左(◀️)と右(▶️)カーソルキー
Last Modified 2026/04/13

Splunk Observability Cloud による Agentic AI アプリケーションの監視のサブセクション

EC2 インスタンスへの接続

5 minutes  

EC2 インスタンスへの接続

各参加者用に AWS/EC2 に Ubuntu Linux インスタンスを用意しています。

インストラクターから提供された IP アドレスとパスワードを使用して、以下のいずれかの方法で EC2 インスタンスに接続してください

  • Mac OS / Linux
    • ssh splunk@IP address
  • Windows 10+
    • OpenSSH クライアントを使用してください
  • 以前のバージョンの Windows
    • Putty を使用してください

インスタンス名の取得

ssh で EC2 インスタンスにログインしたら、以下のコマンドを使用してインスタンス名を取得してください

echo $INSTANCE

このインスタンス名はあなた固有のものであり、ワークショップの後半で Splunk Observability Cloud でデータを検索する際に使用するため、メモしておいてください。

Last Modified 2026/04/13

Azure OpenAI メトリクス、ダッシュボード、ナビゲーターの確認

10 minutes  

このワークショップでは、Azure で動作する OpenAI モデルを使用します。

Azure OpenAI アプリケーションから Splunk Observability Cloud にメトリクスを送信するように設定することで、Azure OpenAI アプリケーションのパフォーマンスを監視できます。

ドキュメントに記載されている手順に従って、ワークショップ用の Splunk Observability Cloud インスタンスと Azure アカウントの統合は既に完了しています。

Azure OpenAI メトリクスが含まれるようにするため、Cognitive Services からメトリクスを取得するように接続を設定しました

Azure connection Azure connection

Azure OpenAI メトリクス

Azure OpenAI では、以下のメトリクスが取得されます

  • ProcessedPromptTokens
  • GeneratedTokens
  • AzureOpenAIRequests
  • AzureOpenAITimeToResponse
  • AzureOpenAIAvailabilityRate
  • AzureOpenAITokenPerSecond
  • AzureOpenAIContextTokensCacheMatchRate

Metrics -> Metric finder に移動し、ProcessedPromptTokens メトリクスを検索して View in chart をクリックします

注: このリンクを使用して Metric finder でこのメトリクスを表示することもできます。

Processed Prompt Tokens Metric Processed Prompt Tokens Metric

Azure OpenAI ナビゲーター

Splunk Observability Cloud は、OpenTelemetry の生成 AI クライアントおよびモデルサーバーのメトリクスを収集し、Azure で動作する Open AI 大規模言語モデル(LLM)サービスのトークン使用量を追跡します。

これらのメトリクスは Azure OpenAI ナビゲーターを使用して確認できます。Infrastructure -> Overview -> AI Frameworks に移動し、Azure OpenAI をクリックします

Azure OpenAI Navigator Azure OpenAI Navigator

Azure OpenAI ダッシュボード

Splunk Observability Cloud は、Azure OpenAI 用の組み込みダッシュボードを提供しており、以下の項目を即座に可視化できます

  • アクティブな Azure OpenAI モデル
  • トークン使用量
  • 呼び出しレイテンシー
  • モデル別の呼び出し数
  • 最初のバイトまでの時間
  • 合計レスポンス時間
  • モデルの可用性
  • リクエストあたりのトークン数
  • モデルごとの処理済みトークン数
  • モデルごとの生成済みトークン数

Dashboards に移動し、Azure OpenAI を検索してダッシュボードを表示します

Azure OpenAI Dashboard Azure OpenAI Dashboard

Last Modified 2026/04/20

OpenTelemetry Collector のデプロイ

10 minutes  

このワークショップでは、Kubernetes 上で動作する Agentic AI アプリケーションからメトリクス、トレース、ログをキャプチャするために OpenTelemetry を使用します。このセクションでは、Helm を使用して Kubernetes クラスターに OpenTelemetry Collector をインストールします。これにより、環境からメトリクス、トレース、ログをキャプチャし、Splunk に送信できるようになります。

Helm を使用して Collector をインストールする

まず、Helm リポジトリを追加する必要があります:

helm repo add splunk-otel-collector-chart https://signalfx.github.io/splunk-otel-collector-chart

次に、リポジトリが最新であることを確認します:

helm repo update

Helm チャートのデプロイメントを設定するために、/home/splunk ディレクトリに values.yaml という名前の新しいファイルを作成します:

# swith to the /home/splunk dir
cd /home/splunk
# create a values.yaml file in vi
vi values.yaml

次に、以下の内容を貼り付けます:

貼り付ける前に :set paste と入力してください。これにより、vi が貼り付けたコードを自動インデントするのを防ぐことができます。

agent:
  config:
    exporters:
      signalfx:
        send_otlp_histograms: true

vi で変更を保存するには、esc キーを押してコマンドモードに入り、:wq! と入力してから enter/return キーを押します。

このカスタム設定により、エクスポーターが受信したヒストグラムメトリクスは、SignalFx 形式に変換されることなく OTLP 形式で Splunk Observability バックエンドに送信されます。この設定は、gen_ai.evaluation.score などの AI Agent Monitoring で使用されるヒストグラムメトリクスが期待通りに処理されるようにするために重要です。

次のコマンドを使用して Collector をインストールできます:

  helm upgrade --install splunk-otel-collector --version 0.136.0 \
  --set="splunkObservability.realm=$REALM" \
  --set="splunkObservability.accessToken=$ACCESS_TOKEN" \
  --set="clusterName=$INSTANCE-cluster" \
  --set="environment=agentic-ai-$INSTANCE" \
  --set="splunkPlatform.token=$HEC_TOKEN" \
  --set="splunkPlatform.endpoint=$HEC_URL" \
  --set="splunkPlatform.index=splunk4rookies-workshop" \
  -f values.yaml \
  splunk-otel-collector-chart/splunk-otel-collector 
NAME: splunk-otel-collector
LAST DEPLOYED: Fri Dec 20 01:01:43 2024
NAMESPACE: default
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
Splunk OpenTelemetry Collector is installed and configured to send data to Splunk Observability realm us1.

Collector が動作していることを確認する

次のコマンドで Collector が動作しているかどうかを確認できます:

kubectl get pods
NAME                                                         READY   STATUS    RESTARTS   AGE
splunk-otel-collector-agent-dkn88                            1/1     Running   0          53s
splunk-otel-collector-agent-ksmh4                            1/1     Running   0          53s
splunk-otel-collector-agent-lc2lf                            1/1     Running   0          53s
splunk-otel-collector-k8s-cluster-receiver-dbf64995b-xgm9b   1/1     Running   0          53s

K8s クラスターが O11y Cloud に表示されていることを確認する

新しい Kubernetes エクスペリエンスを使用する場合

O11y Cloud で新しい Kubernetes エクスペリエンスを使用するように設定されている場合は、このセクションの手順に従ってください。そうでない場合は、代わりに 従来の Kubernetes エクスペリエンスを使用する場合 のセクションを参照してください。

Splunk Observability Cloud で、Infrastructure -> Kubernetes overview に移動し、クラスター名(<your instance name>-cluster)を追加します:

ヒント: インスタンス名を忘れた場合は、echo $INSTANCE コマンドを使用してください

Kubernetes overview filter Kubernetes overview filter

Apply Filters をクリックすると、以下のようなクラスターの概要が表示されます:

Kubernetes overview new experience Kubernetes overview new experience

従来の Kubernetes エクスペリエンスを使用する場合

Splunk Observability Cloud で、Infrastructure -> Kubernetes -> Kubernetes Clusters に移動し、クラスター名(<your instance name>-cluster)を検索します:

ヒント: インスタンス名を忘れた場合は、echo $INSTANCE コマンドを使用してください

Kubernetes cluster Kubernetes cluster

Last Modified 2026/04/20

Agentic AI アプリケーションアーキテクチャ

15 minutes  

アプリケーション概要

このワークショップでは、旅行予約のための Agentic AI アプリケーションを使用します。 OpenTelemetry でアプリケーションを計装する前に、アプリケーションの仕組みを理解しておくと役立ちます。

Application Architecture Application Architecture

このアプリケーションは、旅行プランニングリクエストを受け付け、LangChain を活用した複数の LLM ノードで構成される LangGraph ワークフローを通じて処理する Flask API です。各ノードは特定の役割を持ち、共有ステートを更新して次のステップに引き渡します。

このパートでは、以下の内容を確認します

  • リクエストのライフサイクル
  • 共有ステートモデル
  • LangGraph ノードの動作方法
  • コード内で使用されている LangChain の抽象化
  • 後でオブザーバビリティが重要になる箇所

アプリケーションのアーキテクチャと実装の詳細については、サブセクションに移動してください。

Last Modified 2026/04/20

4. Agentic AI アプリケーションアーキテクチャのサブセクション

4.1 リクエストのライフサイクル

アプリケーションの動作

大まかに言うと、このアプリケーションはリクエストを受け取り、それを複数ステップのワークフローに変換します

  • coordinator
  • flight specialist
  • hotel specialist
  • activity specialist
  • synthesizer

メインのフローは次のようになっています

@app.route("/travel/plan", methods=["POST"])
def plan():
    data = request.get_json()

    origin = data.get("origin", "Seattle")
    destination = data.get("destination", "Paris")
    user_request = data.get(
        "user_request",
        f"Planning a week-long trip from {origin} to {destination}. "
        "Looking for boutique hotel, flights and unique experiences.",
    )
    travellers = int(data.get("travellers", 2))

    result = plan_travel_internal(
        origin=origin,
        destination=destination,
        user_request=user_request,
        travellers=travellers
    )

    return jsonify(result), 200

この流れを分かりやすく説明すると、次のようになります

  1. Flask がリクエストを受信します
  2. plan_travel_internal() がワークフローの状態を構築します
  3. LangGraph がノードを実行します
  4. 各ノードが状態を更新します
  5. 最終的な旅程が JSON として返されます

知識チェック

この API フローにおいて、LangGraph のワークフローは実際にどこで実行が開始されますか?

ここをクリックして回答を表示

plan_travel_internal() の内部で開始されます。Flask のルートはリクエストの受信と パラメータの抽出のみを行います。plan_travel_internal() がワークフローの状態を初期化し、 LangGraph のグラフを呼び出します。その後、ノード(coordinator、specialist、synthesizer)が 状態を更新しながら実行され、最終的な旅程が生成されます。

Last Modified 2026/04/20

4.2 共有ステート

LangGraph における共有ステート

このアプリで最も重要な LangGraph のコンセプトは、共有ステートオブジェクトです

class PlannerState(TypedDict):
    messages: Annotated[List[AnyMessage], add_messages]
    user_request: str
    session_id: str
    origin: str
    destination: str
    departure: str
    return_date: str
    travellers: int
    flight_summary: Optional[str]
    hotel_summary: Optional[str]
    activities_summary: Optional[str]
    final_itinerary: Optional[str]
    current_agent: str

このステートは、グラフ内のノードからノードへと移動します。

各ノードは以下を行います

  • ステートから値を読み取る
  • 何らかの処理を実行する
  • 新しい値をステートに書き戻す
  • current_agent を設定して次の処理を制御する

これは LangGraph の重要なメンタルモデルですステートフルなワークフローオーケストレーション

知識チェック

messages フィールドに使用されている構文をどのように説明しますか?

messages: Annotated[List[AnyMessage], add_messages]
ここをクリックして回答を確認

messages: Annotated[List[AnyMessage], add_messages] は2つのことを行います。

  • List[AnyMessage] はフィールドのを定義します:LangChain のメッセージオブジェクト(system、human、または AI メッセージ)のリストです。
  • Annotated[..., add_messages]LangGraph の動作を追加し、このフィールドの更新をどのように処理するかをグラフに指示します。

具体的には、add_messages はノードが新しいメッセージを書き込んだ際に、LangGraph が既存のリストを上書きするのではなく、追記することを意味します。 そのため、各ノードがメッセージを追加するたびに、会話履歴が増えていきます。

Last Modified 2026/04/20

4.3 オーケストレーション

実行が始まる場所

メインのオーケストレーションは plan_travel_internal() で行われます

def plan_travel_internal(
    origin: str,
    destination: str,
    user_request: str,
    travellers: int,
    ) -> Dict[str, object]:
    session_id = str(uuid4())
    departure, return_date = _compute_dates()

    initial_state: PlannerState = {
        "messages": [HumanMessage(content=user_request)],
        "user_request": user_request,
        "session_id": session_id,
        "origin": origin,
        "destination": destination,
        "departure": departure,
        "return_date": return_date,
        "travellers": travellers,
        "flight_summary": None,
        "hotel_summary": None,
        "activities_summary": None,
        "final_itinerary": None,
        "current_agent": "start",
    }

    workflow = build_workflow()
    compiled_app = workflow.compile()

    for step in compiled_app.stream(initial_state, config):
        node_name, node_state = next(iter(step.items()))
        final_state = node_state

この関数は以下のアプリケーションライフサイクルを実装しています

  • 初期ステートを構築する
  • グラフを構築する
  • コンパイルする
  • ステップごとにストリーム実行する

知識チェック

質問 1

なぜコードは単にグラフを一度呼び出して最終結果を取得するのではなく、compiled_app.stream(initial_state, config) を使用しているのですか?

ここをクリックして回答を確認

ストリーミングはワークフローを各ノードの実行ごとにステップバイステップで実行するためです。これにより、アプリケーションは中間ステートを観察し、どのノードが実行中かを追跡し、最終出力を待つだけでなくリアルタイムでワークフローを監視できます。

質問 2

なぜグラフを実行する前に initial_state を作成するのですか?

ここをクリックして回答を確認

LangGraph のワークフローは共有ステートオブジェクト上で動作するためです。initial_state は、ワークフローの進行に伴ってノードが読み取り、更新し、受け渡す開始データを提供します。

Last Modified 2026/04/20

4.4 グラフの定義

グラフの定義方法

グラフは build_workflow() で明示的に構築されます

def build_workflow() -> StateGraph:
    graph = StateGraph(PlannerState)
    graph.add_node("coordinator", lambda state: coordinator_node(state))
    graph.add_node("flight_specialist", lambda state: flight_specialist_node(state))
    graph.add_node("hotel_specialist", lambda state: hotel_specialist_node(state))
    graph.add_node("activity_specialist", lambda state: activity_specialist_node(state))
    graph.add_node("plan_synthesizer", lambda state: plan_synthesizer_node(state))
    graph.add_conditional_edges(START, should_continue)
    graph.add_conditional_edges("coordinator", should_continue)
    graph.add_conditional_edges("flight_specialist", should_continue)
    graph.add_conditional_edges("hotel_specialist", should_continue)
    graph.add_conditional_edges("activity_specialist", should_continue)
    graph.add_conditional_edges("plan_synthesizer", should_continue)
    return graph

ルーティングロジックは以下の通りです

def should_continue(state: PlannerState) -> str:
    mapping = {
    "start": "coordinator",
    "flight_specialist": "flight_specialist",
    "hotel_specialist": "hotel_specialist",
    "activity_specialist": "activity_specialist",
    "plan_synthesizer": "plan_synthesizer",
    }
    return mapping.get(state["current_agent"], END)

条件付きエッジを使用していますが、ワークフローは実質的にリニア(直線的)です

  • start
  • coordinator
  • flight specialist
  • hotel specialist
  • activity specialist
  • synthesizer
  • end

理解度チェック

ワークフローが実質的にリニアであるなら、なぜグラフは add_conditional_edgesshould_continue() ルーターを使用しているのでしょうか?

ここをクリックして回答を表示

ワークフローを柔軟で拡張可能にするためです。現在のフローはリニアですが、ルーティング関数によりグラフはステートに基づいて次のノードを動的に決定できます。これにより、グラフを再設計することなく、分岐、リトライ、異なる実行パスを後から簡単に追加できます。

Last Modified 2026/04/20

4.5 ノードの定義

ノードの仕組み

このアプリにおける LangGraph のノードは、state を受け取り、更新された state を返す Python 関数です。

例えば、flight specialist は以下のようになります:

def flight_specialist_node(state: PlannerState) -> PlannerState:
    llm = _create_llm(
    "flight_specialist", temperature=0.4, session_id=state["session_id"]
    )

    step = (
        f"Find an appealing flight from {state['origin']} to {state['destination']} "
        f"departing {state['departure']} for {state['travellers']} travellers."
    )

    messages = [
        SystemMessage(content="You are a flight booking specialist. Provide concise options."),
        HumanMessage(content=step),
    ]

    result = llm.invoke(messages)
    state["flight_summary"] = result.content
    state["messages"].append(result)
    state["current_agent"] = "hotel_specialist"
    return state

これは一般的なノードパターンを示しています:

  1. LLM を作成またはアクセスする
  2. 構造化された state からプロンプトを構築する
  3. モデルを呼び出す
  4. 結果を state に保存する
  5. 次のノードを設定する

hotel ノードと activity ノードも同じ構造に従っているため、ワークフローの説明が容易です。

知識チェック

flight_specialist ノードの LLM を作成する際に、temperature を 0.4 に指定しました。これはどういう意味でしょうか?

ここをクリックして回答を確認

Temperature は、モデルの応答がどの程度ランダムまたは創造的になるかを制御します。

  • 低い temperature(例: 0.0〜0.3): より決定論的で一貫した応答になります
  • 中程度(約 0.4〜0.7): 正確性と創造性のバランスが取れています
  • 高い(0.8 以上): より多様で創造的ですが、予測しにくくなります

つまり、temperature=0.4 に設定すると、flight_specialist エージェントはおおむね一貫性があり信頼できる応答を生成しつつ、わずかなバリエーションを持たせることができます。これは正確性が求められるが、完全に固定的な回答は不要なタスクに適しています。

Last Modified 2026/04/20

4.6 Message Abstractions

LangChain Message Abstractions

このアプリケーションは、1つの長いプロンプト文字列ではなく、LangChain のメッセージ抽象化を使用しています。

from langchain_core.messages import (
    AIMessage,
    BaseMessage,
    HumanMessage,
    SystemMessage,
)

これは、各ノードで以下を分離できるため重要です

  • システムロール
  • ユーザータスク
  • モデルの応答

messages = [
    SystemMessage(content="You are a flight booking specialist. Provide concise options."),
    HumanMessage(content=step),
]
result = llm.invoke(messages)

理解度チェック

system、human、AI メッセージをどのように定義しますか?

ここをクリックして回答を表示

LangChain と LangGraph では、メッセージは通常、誰が話しているか、および会話を導く上でどのような役割を果たしているかによって分類されます

  • System message: AI の動作に関するルールとコンテキストを設定します。インタラクション全体を通じてモデルがどのように応答すべきかを導く指示、制約、トーン、および目標を定義します。
  • Human message: ユーザーからの入力です。AI が応答すべき質問、リクエスト、または情報を含みます。
  • AI message: モデルの応答です。システムの指示とユーザーの入力に基づいて、アシスタントが生成した出力を表します。
Last Modified 2026/04/20

4.7 LLMの作成

LLMの作成

LLM自体はここで作成されます

def _create_llm(agent_name: str, *, temperature: float, session_id: str) -> AzureChatOpenAI:
    azure_deployment_name = os.getenv("AZURE_OPENAI_DEPLOYMENT_NAME")
    azure_openai_api_version = os.getenv("AZURE_OPENAI_API_VERSION")

    return AzureChatOpenAI(
        azure_deployment=azure_deployment_name,
        openai_api_version=azure_openai_api_version,
        temperature=temperature,
        model_name = azure_deployment_name,
        # AZURE_OPENAI_API_KEY and AZURE_OPENAI_ENDPOINT environment variables will be used to connect to the LLM
    )

このアプローチでは、モデルの設定をワークフローロジックから分離しています。 各ノードは、どの程度決定論的または創造的であるべきかに応じて、異なるtemperatureを使用できます。

理解度チェック

OpenAI(Azure OpenAIではなく)用のLLMをどのように作成しますか?

クリックして回答を表示

OpenAI用のLLMの作成にはいくつかの違いがあります。関数は AzureChatOpenAI の代わりに ChatOpenAI オブジェクトを返します。

OpenAIを直接使用する場合、Azure固有のパラメータ(azure_deploymentopenai_api_versionAzure endpoint)は使用しません。代わりに、モデル名を指定し、標準の OPENAI_API_KEY 環境変数を使用します。

以下は例です

def _create_llm(agent_name: str, *, temperature: float, session_id: str) -> ChatOpenAI:
    model_name = os.getenv("OPENAI_MODEL_NAME", "gpt-4o-mini")

    return ChatOpenAI(
        model=model_name,
        temperature=temperature,
        # Uses OPENAI_API_KEY automatically from environment
    )
Last Modified 2026/04/20

4.8 Decomposition Pattern

シンセサイザーが分解パターンを示します

最後のノードは、各専門エージェントの出力を1つの回答にまとめます。

def plan_synthesizer_node(state: PlannerState) -> PlannerState:
    llm = _create_llm(
    "plan_synthesizer", temperature=0.3, session_id=state["session_id"]
    )

    content = json.dumps(
        {
            "flight": state["flight_summary"],
            "hotel": state["hotel_summary"],
            "activities": state["activities_summary"],
        },
        indent=2,
    )

    response = llm.invoke(
        [
            SystemMessage(
                content="You are the travel plan synthesiser. Combine the specialist insights into a concise, structured itinerary."
            ),
            HumanMessage(
                content=(
                    f"Traveller request: {state['user_request']}\n\n"
                    f"Origin: {state['origin']} | Destination: {state['destination']}\n"
                    f"Dates: {state['departure']} to {state['return_date']}\n\n"
                    f"Specialist summaries:\n{content}"
                )
            ),
        ]
    )
    state["final_itinerary"] = response.content
    state["messages"].append(response)
    state["current_agent"] = "completed"
    return state

これはエージェントアプリの典型的なパターンです

  • 作業を専門エージェントに分解する
  • 中間出力を収集する
  • 最終的な応答に統合する

これは、この概要から得るべき主要なアーキテクチャのアイデアの1つです。

理解度チェック

アプリが1つのエージェントに旅行プラン全体を生成させるのではなく、個別の plan_synthesizer ノードを使用するのはなぜですか?

ここをクリックして回答を表示

システムはまず問題を専門的なタスク(フライト、ホテル、アクティビティ)に分解するためです。各専門エージェントが焦点を絞った要約を生成し、plan_synthesizer ノードがそれらの出力を1つのまとまった旅程に統合します。

このパターンはモジュール性、信頼性、オブザーバビリティを向上させます。各エージェントがより小さな問題を処理し、最後のノードが結果を統合するためです。

Last Modified 2026/04/20

Agentic AI アプリケーションのデプロイ

15 minutes  

Agentic AI アプリケーションのデプロイ (Linux)

まず、Linux EC2 インスタンス上でアプリケーションを直接実行します。

環境変数の設定

コマンドターミナルで、以下の環境変数を設定します。これらはアプリケーションが Azure でホストされている OpenAI モデルに接続するために使用されます:

注: AZURE_OPENAI_ENDPOINTAZURE_OPENAI_API_KEY の値はワークショップの講師から提供されます。

export AZURE_OPENAI_DEPLOYMENT_NAME=gpt-4.1-mini
export AZURE_OPENAI_API_VERSION=2024-12-01-preview
export AZURE_OPENAI_ENDPOINT=your_azure_openai_endpoint
export AZURE_OPENAI_API_KEY=your_azure_openai_api_key

仮想環境の作成

次に、Python 仮想環境を作成し、アプリケーションの実行に必要なパッケージをインストールします:

cd ~/workshop/agentic-ai/base-app
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt

アプリケーションの実行

以下のコマンドでアプリケーションを実行します:

python3 main.py

アプリケーションのテスト

EC2 インスタンスに接続した2つ目のターミナルセッションを開き、以下のコマンドを実行してアプリケーションをテストします。提案された旅行プランが JSON 形式で返されます:

curl http://localhost:8080/travel/plan \
  -H "Content-Type: application/json" \
  -d '{
    "origin": "Seattle",
    "destination": "Tokyo",
    "user_request": "We are planning a week-long trip to Seattle from Tokyo. Looking for boutique hotel, business-class flights and unique experiences.",
    "travelers": 2
  }'
{"activities_summary":"Sure! Here are signature activities for a week in Tokyo:\n\n1. Day 1: Explore Asakusa and Senso-ji Temple, then stroll Nakamise Shopping Street.\n2. Day 2: Visit Tsukiji Outer Market for fresh sushi breakfast, then tour Ginza for upscale shopping.\n3. Day 3: Spend the day in Shibuya\u2014cross the famous scramble, visit Hachiko statue, and shop in trendy boutiques.\n4. Day 4: Explore Harajuku\u2019s Takeshita Street and Meiji Shrine, followed by Omotesando\u2019s stylish cafes.\n5. Day 5: Discover Akihabara\u2019s electronics and anime culture, with a visit to a themed caf\u00e9.\n6. Day 6: Take a day trip to Odaiba for teamLab Borderless digital art museum and waterfront views.\n7. Day 7: Relax in Ueno Park, visit museums, and shop at Ameya-Yokocho market.\n\nWould you like hotel or dining recommendations as well?","agent_steps":[{"agent":"coordinator","status":"completed"},{"agent":"flight_specialist","status":"completed"},{"agent":"hotel_specialist","status":"completed"}

Agentic AI アプリケーションのデプロイ (Kubernetes)

アプリケーションが正常に動作することを確認できたので、Kubernetes にデプロイしましょう。

Dockerfile の作成

ビルド済みの Dockerfile が ~/workshop/agentic-ai/base-app/Dockerfile にあります。requirements.txt ファイルのすべてのパッケージが Docker イメージのビルド時にインストールされることが確認できます:

RUN pip install --no-cache-dir -r requirements.txt

コンテナは以下のコマンドで起動されます:

CMD ["python", "main.py"]

Docker イメージのビルド

cd ~/workshop/agentic-ai/base-app
docker build --platform linux/amd64 -t localhost:9999/agentic-ai-app:base-app .
docker push localhost:9999/agentic-ai-app:base-app

Azure 認証情報を含む Secret の作成

Kubernetes の Secret を使用して Azure OpenAI のエンドポイントとキーを保存します:

kubectl create ns travel-agent

kubectl create secret generic azure-openai-api -n travel-agent --from-literal=azure-openai-api-endpoint=$AZURE_OPENAI_ENDPOINT --from-literal=azure-openai-api-key=$AZURE_OPENAI_API_KEY

Kubernetes マニフェストファイルを使用したアプリケーションのデプロイ

ビルド済みの Kubernetes マニフェストが ~/workshop/agentic-ai/base-app/k8s.yaml にあります。

以下のようにマニフェストファイルを使用してアプリケーションをデプロイします:

kubectl apply -f ~/workshop/agentic-ai/base-app/k8s.yaml

アプリケーションの実行確認

以下のコマンドを使用して、アプリケーション Pod のステータスが Running であることを確認します:

kubectl get pods -n travel-agent
NAME                                        READY   STATUS    RESTARTS   AGE
travel-planner-langchain-68977dc5c4-4w7p9   1/1     Running   0          41s

Kubernetes でのアプリケーションのテスト

以下のコマンドを実行してアプリケーションをテストします:

curl http://travel-planner.localhost/travel/plan \
  -H "Content-Type: application/json" \
  -d '{
    "origin": "Seattle",
    "destination": "Tokyo",
    "user_request": "We are planning a week-long trip to Seattle from Tokyo. Looking for boutique hotel, business-class flights and unique experiences.",
    "travelers": 2
  }'
{"activities_summary":"Sure! Here are signature activities for a week in Tokyo:\n\n1. Day 1: Explore Asakusa and Senso-ji Temple, then stroll Nakamise Shopping Street.\n2. Day 2: Visit Tsukiji Outer Market for fresh sushi breakfast, then tour Ginza for upscale shopping.\n3. Day 3: Spend the day in Shibuya\u2014cross the famous scramble, visit Hachiko statue, and shop in trendy boutiques.\n4. Day 4: Explore Harajuku\u2019s Takeshita Street and Meiji Shrine, followed by Omotesando\u2019s stylish cafes.\n5. Day 5: Discover Akihabara\u2019s electronics and anime culture, with a visit to a themed caf\u00e9.\n6. Day 6: Take a day trip to Odaiba for teamLab Borderless digital art museum and waterfront views.\n7. Day 7: Relax in Ueno Park, visit museums, and shop at Ameya-Yokocho market.\n\nWould you like hotel or dining recommendations as well?","agent_steps":[{"agent":"coordinator","status":"completed"},{"agent":"flight_specialist","status":"completed"},{"agent":"hotel_specialist","status":"completed"}

トラブルシューティング

トラブルシューティングが必要な場合は、以下のコマンドを使用してアプリケーションのログを確認します:

kubectl logs -l app=travel-planner-langchain -n travel-agent -f
Last Modified 2026/04/20

Agentic AI アプリケーションの計装

15 minutes  

注意: このセクションでは複数のファイルを変更する必要があります。 変更箇所がわからない場合やアプリケーションが動作しなくなった場合は、 ~/workshop/agentic-ai/app-with-instrumentation フォルダにある モデルソリューションを参照してください。

Agentic AI アプリケーションを OpenTelemetry で計装し、Kubernetes にデプロイするには、いくつかのステップが必要です:

  1. requirements.txt ファイルに計装パッケージを追加する
  2. opentelemetry-instrument を使用してアプリケーションを起動するように Dockerfile を更新する
  3. 計装パッケージを含む新しい Docker イメージをビルドする
  4. 環境変数を含む Kubernetes マニフェストを更新する
  5. Kubernetes マニフェストをデプロイする

計装パッケージの追加

次に、いくつかの計装パッケージをインストールする必要があります。~/workshop/agentic-ai/base-app/requirements.txt を開いて編集し、ファイルの末尾に以下のパッケージを追加します:

splunk-opentelemetry==2.8.0
splunk-otel-instrumentation-langchain==0.1.7
splunk-otel-genai-emitters-splunk==0.1.7
splunk-otel-util-genai==0.1.9
opentelemetry-instrumentation-flask==0.59b0

各パッケージの説明は以下の通りです:

  • splunk-opentelemetry: Splunk ディストリビューションの OpenTelemetry Python です。Python アプリケーションを計装して、分散トレースをキャプチャし Splunk APM にレポートします。
  • splunk-otel-instrumentation-langchain: LangChain の LLM/チャットワークフローに対する OpenTelemetry 計装を提供するパッケージです。
  • splunk-otel-genai-emitters-splunk: Splunk Platform でのストレージとフィルタリングを最適化するために、Splunk スキーマの評価結果ログ用エミッターを提供するパッケージです。
  • splunk-otel-util-genai: OpenTelemetry セマンティック規約を使用した生成 AI ワークロードの計装を容易にする API とデータ型を提供するユーティリティ関数を含むパッケージです。
  • opentelemetry-instrumentation-flask: OpenTelemetry WSGI ミドルウェアを基盤として、Flask アプリケーションの Web リクエストを追跡するライブラリです。

ヒント: 以下のコマンドを実行して、変更内容をモデルソリューションと比較できます:

diff ~/workshop/agentic-ai/base-app/requirements.txt ~/workshop/agentic-ai/app-with-instrumentation/requirements.txt

Dockerfile の更新

次に、OpenTelemetry 計装を有効にする必要があります。これは、opentelemetry-instrument でアプリケーションが起動されるように Dockerfile を更新することで行います。~/workshop/agentic-ai/base-app/Dockerfile を開いて編集し、最後の行を以下のように更新します:

# Run the server with instrumentation
CMD ["opentelemetry-instrument", "python", "main.py"]

ヒント: 以下のコマンドを実行して、変更内容をモデルソリューションと比較できます:

diff ~/workshop/agentic-ai/base-app/Dockerfile ~/workshop/agentic-ai/app-with-instrumentation/Dockerfile

更新された Docker イメージのビルド

新しいタグで更新された Docker イメージをビルドします:

docker build --platform linux/amd64 -t localhost:9999/agentic-ai-app:app-with-instrumentation .
docker push localhost:9999/agentic-ai-app:app-with-instrumentation

ConfigMap の定義

アプリケーションを Kubernetes にデプロイする際、テレメトリー(メトリクス、トレース、ログ)を明確でユニークな環境識別子とともに Splunk Observability Cloud に送信したいと考えます。これにより、異なるデプロイメント間でのデータのフィルタリング、比較、トラブルシューティングが容易になります。

これを実現するために、deployment.environment という名前の OpenTelemetry リソース属性を設定します。値をハードコーディングするのではなく、EC2 インスタンスに既に存在する INSTANCE 環境変数から値を取得します。これにより、各デプロイメントに正しい環境名が自動的にタグ付けされます。

この設定を Kubernetes ConfigMap に保存し、後からアプリケーション Pod に環境変数として注入できるようにします。

以下のコマンドで ConfigMap を作成します:

kubectl create configmap instance-config \
--from-literal=OTEL_RESOURCE_ATTRIBUTES=deployment.environment=agentic-ai-$INSTANCE \
-n travel-agent

このコマンドの動作:

  • OpenTelemetry が期待する OTEL_RESOURCE_ATTRIBUTES 環境変数を定義します。
  • $INSTANCE の値に応じて、deployment.environmentagentic-ai-shw-1c43 のような値に設定します。
  • travel-agent ネームスペースに ConfigMap を作成します。

次のステップで Kubernetes デプロイメントを設定する際に、この ConfigMap を参照します。

Kubernetes マニフェストの更新

OpenTelemetry 計装、特に AI Agent Monitoring には、計装データの収集、処理、エクスポートの方法を定義する多数の環境変数を設定する必要があります。

~/workshop/agentic-ai/base-app/k8s.yaml ファイルを開いて編集します。計装を含むイメージを使用するように image タグ を更新します:

          image: localhost:9999/agentic-ai-app:app-with-instrumentation

同じファイルで、Begin: Add Environment VariablesEnd: Add Environment Variables というコメントの間に、以下の 環境変数 を追加します:

ヒント: 貼り付ける前に :set paste と入力すると、vi による自動インデントを防ぐことができます。

            # Begin: Add Environment Variables
            # Service Name
            - name: OTEL_SERVICE_NAME
              value: "travel-planner"
            # Additional OTEL configuration
            - name: OTEL_RESOURCE_ATTRIBUTES
              valueFrom:
                configMapKeyRef:
                  name: instance-config
                  key: OTEL_RESOURCE_ATTRIBUTES
            - name: SPLUNK_OTEL_AGENT
              valueFrom:
                fieldRef:
                  fieldPath: status.hostIP
            - name: OTEL_EXPORTER_OTLP_ENDPOINT
              value: "http://$(SPLUNK_OTEL_AGENT):4317"
            - name: OTEL_EXPORTER_OTLP_PROTOCOL
              value: "grpc"
            - name: OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE
              value: "DELTA"
            - name: OTEL_PYTHON_EXCLUDED_URLS
              value: "^(https?://)?[^/]+(/health)?$"
            - name: OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT
              value: "true"
            - name: OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT_MODE
              value: "SPAN"
            - name: OTEL_INSTRUMENTATION_GENAI_EMITTERS
              value: "span_metric,splunk"
            - name: SPLUNK_PROFILER_ENABLED
              value: "true"
            # End: Add Environment Variables

注意: スクロールしないとテキストの一部が表示されない場合があります。 右上の Copy text to clipboard ボタンを使用して、すべてのテキストをコピーしてください。

注意: YAML ではインデントが重要です。新しい環境変数が既存の環境変数と揃っていることを確認してください。

以下の環境変数は Agentic AI モニタリングに固有のもので、説明は以下の通りです:

  • OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE: OTLP メトリクスエクスポーターが、出力されるメトリクスに対して累積合計、デルタ、またはメモリ効率の良いテンポラリティのいずれを報告するかを決定します。Agentic AI モニタリングでは DELTA に設定することが推奨されます。
  • OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT: Agentic AI アプリケーションからのメッセージキャプチャを有効/無効にするために使用されます。このワークショップでは true に設定しています。
  • OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT_MODE: メッセージのキャプチャ方法を定義します。このワークショップでは SPAN に設定しており、スパンイベントストアを使用してメッセージがキャプチャされます。
  • OTEL_INSTRUMENTATION_GENAI_EMITTERS: このワークショップでは span_metric,splunk に設定しており、スパンとメトリクスの両方のデータ、および Splunk 固有の機能がキャプチャされます。

ヒント: 以下のコマンドを実行して、変更内容をモデルソリューションと比較できます:

diff ~/workshop/agentic-ai/base-app/k8s.yaml ~/workshop/agentic-ai/app-with-instrumentation/k8s.yaml

更新されたアプリケーションのデプロイ

以下のようにマニフェストファイルを使用して、更新されたアプリケーションをデプロイできます:

kubectl apply -f ~/workshop/agentic-ai/base-app/k8s.yaml

Kubernetes でのアプリケーションテスト

新しいアプリケーション Pod が正常に起動し、古い Pod がなくなっていることを確認します:

kubectl get pods -n travel-agent
NAME                                        READY   STATUS    RESTARTS   AGE
travel-planner-langchain-68977dc5c4-4w7p9   1/1     Running   0          41s

次に、以下のコマンドを実行してアプリケーションをテストします:

curl http://travel-planner.localhost/travel/plan \
  -H "Content-Type: application/json" \
  -d '{
    "origin": "Seattle",
    "destination": "Tokyo",
    "user_request": "We are planning a week-long trip to Seattle from Tokyo. Looking for boutique hotel, business-class flights and unique experiences.",
    "travelers": 2
  }'

トラブルシューティング

トラブルシューティングが必要な場合は、以下のコマンドを使用してアプリケーションログを確認します:

kubectl logs -l app=travel-planner-langchain -n travel-agent -f
Last Modified 2026/04/20

エージェントトレースデータの確認

10 minutes  

LLM プロバイダー設定の確認

Splunk Observability Cloud には、Large Language Model(LLM)を接続するためのインテグレーションが含まれています。Splunk はこの接続を使用して、アプリケーションが生成した LLM レスポンスのセマンティック品質を評価します。

このインテグレーションは、ワークショップの組織にすでに設定されています。

設定を確認するには、Data Management → Deployed Integrations に移動し、LLM Providers を検索して選択します。以下のプロバイダーが表示されるはずです

LLM Providers LLM Providers

Azure OpenAI O11y Specialists プロバイダーをクリックして、詳細を確認します

LLM Provider Details LLM Provider Details

この組織では、サンプリングレートは 20% に設定されています。これは、平均してアプリケーションが生成した LLM レスポンスの 20% のセマンティック品質を Splunk が評価することを意味します。

また、1分あたり50回の評価のレートリミットも設定されています。サンプリングレートとレートリミットは、お客様のニーズに応じて調整できます。サンプリングレートを高くするとより多くの評価データが得られますが、トークン使用量と関連コストも増加します。

Splunk Observability Cloud でトレースデータを表示する

Splunk Observability Cloud で APM に移動し、Service Map を選択します。 環境名が選択されていることを確認してください(例agentic-ai-$INSTANCE)。 以下のようなサービスマップが表示されるはずです

Service Map Service Map

右側のメニューで Traces をクリックします。次に、実行時間の長いトレースの1つを選択します。以下の例のように表示されるはずです

Trace Trace

Agent flow セクションにエージェント名(coordinator、flight-specialist など)が表示されていないことに注目してください。

下にスクロールして、トレース内の AI インタラクションの1つをクリックしてみましょう。ここでは、プロンプトとレスポンスがキャプチャされていることが確認できます。また、このトレースのセマンティック品質評価の結果も確認できます

Trace Details Trace Details

次に、APM に移動し、AI agents を選択します。環境名が選択されていることを確認してください(例agentic-ai-$INSTANCE)。ページが空であることに気づくでしょう!

Agents Agents

これらの計装の問題については、次のセクションで対処します。

Last Modified 2026/04/20

Tool Call の追加

15 minutes  

注意:このセクションでは複数のファイルを変更する必要があります。 変更箇所がわからない場合や、アプリケーションが動作しなくなった場合は、 ~/workshop/agentic-ai/app-with-agents-and-tools フォルダにあるモデルソリューションを参照してください。

前のセクションでは、エージェントが新しい Agents ページや、トレース上部の Agent flow に表示されないことを確認しました。

その理由は、アプリケーションが現在エージェントを使用しておらず、LLM を直接呼び出しているためです。

つまり、現在のアプリケーションは台本のある劇のようなものです。すべてのセリフとアクションがコードに書かれています。LLM を呼び出す際、特定のセリフを読むよう依頼しているだけです。LLM が自ら判断を行っていないため、Observability for AI の計装はそれを自律的なエージェントとして認識しません。

次のセクションでは、LLM にツールとそれらの使用方法を判断する権限を与えます。エージェントモデルに移行することで、LLM は Tool Call を生成するようになります。OpenTelemetry の計装がこれらのインタラクションをキャプチャし、LLM の思考プロセスとツールの使用状況を確認できるようになり、各エージェントが Splunk Observability Cloud に表示されるようになります。

直接呼び出しとエージェントトレースの比較

これらの変更を行う前に、LLM を直接呼び出した場合とエージェント経由で呼び出した場合のトレースのキャプチャ方法の違いを詳しく見てみましょう。

直接呼び出しのトレース

llm.invoke() を呼び出すと、計装は標準的な「Chat」または「Completion」スパンを検出します。プロンプトとレスポンスが記録されます。エージェントフレームワークが管理する「ループ」や「ツール呼び出し」ロジックがないため、Splunk Observability Cloud はスパンを「Agent」として分類するために必要なメタデータを検出できません。

エージェントトレース

エージェント(例create_react_agent)を使用すると、フレームワークは実行を特定の「Agent」および「Tool」スパンでラップします。これらのスパンには、OpenTelemetry に「これは単なるチャットではなく、特定のツールを使用した推論ループです」と伝えるメタデータが含まれています。これが、トレースの可視化における Agents ページと Agent Flow ダイアグラムにデータを表示する仕組みです。

バックアップの作成

Python コードを変更する前に、以下のコマンドで main.py ファイルのバックアップを作成します

cp ~/workshop/agentic-ai/base-app/main.py ~/workshop/agentic-ai/base-app/main.py.bak

Import 文の追加

~/workshop/agentic-ai/base-app/main.py ファイルを編集用に開きます。

Begin: Add Import StatementsEnd: Add Import Statements の間に、以下の import 文を追加します

# Begin: Add Import Statements

from langchain_core.tools import tool
from langchain.agents import (
    create_agent as _create_react_agent,  # type: ignore[attr-defined]
)

# End: Add Import Statements

ツールの追加

同じ main.py ファイルで、Begin: Tool DefinitionsEnd: Tool Definitions の間にツール定義を追加します

# Begin: Tool Definitions

@tool
def mock_search_flights(origin: str, destination: str, departure: str) -> str:
    """Return mock flight options for a given origin/destination pair."""
    # create a local random.Random instance
    seed = hash((origin, destination, departure)) % (2**32)
    rng = random.Random(seed)
    airline = rng.choice(["SkyLine", "AeroJet", "CloudNine"])
    fare = rng.randint(700, 1250)

    return (
        f"Top choice: {airline} non-stop service {origin}->{destination}, "
        f"depart {departure} 09:15, arrive {departure} 17:05. "
        f"Premium economy fare ${fare} return."
    )


@tool
def mock_search_hotels(destination: str, check_in: str, check_out: str) -> str:
    """Return mock hotel recommendation for the stay."""
    seed = hash((destination, check_in, check_out)) % (2**32)
    rng = random.Random(seed)
    name = rng.choice(["Grand Meridian", "Hotel Lumière", "The Atlas"])
    rate = rng.randint(240, 410)

    return (
        f"{name} near the historic centre. Boutique suites, rooftop bar, "
        f"average nightly rate ${rate} including breakfast."
    )


@tool
def mock_search_activities(destination: str) -> str:
    """Return a short list of signature activities for the destination."""
    data = DESTINATIONS.get(destination.lower(), DESTINATIONS["paris"])
    bullets = "\n".join(f"- {item}" for item in data["highlights"])
    return f"Signature experiences in {destination.title()}:\n{bullets}"
    
# End: Tool Definitions

AI Agent Monitoring 用のアプリケーション設定

現在、アプリケーションは以下のように LLM を作成して呼び出しています

def flight_specialist_node(state: PlannerState) -> PlannerState:
    llm = _create_llm(
    "flight_specialist", temperature=0.4, session_id=state["session_id"]
    )
    ...
    result = llm.invoke(messages)
    ...

AI Agent Monitoring では、代わりにエージェント名のメタデータを含むエージェントを作成し、LLM ではなくエージェントを呼び出す必要があります。

coordinator_nodeflight_specialist_nodehotel_specialist_nodeactivity_specialist_node、および plan_synthesizer_node 関数の定義を以下の内容に置き換えます

ヒントvi エディタで大量の行をまとめて削除するには、Shift + v を押して Visual Line モードにし、下矢印キーで削除したい行をすべて選択してから、d を押して 選択した行を削除します。

def coordinator_node(
    state: PlannerState
) -> PlannerState:
    llm = _create_llm("coordinator", temperature=0.2, session_id=state["session_id"])
    agent = _create_react_agent(llm, tools=[]).with_config(
        {
            "run_name": "coordinator",
            "tags": ["agent", "agent:coordinator"],
            "metadata": {
                "agent_name": "coordinator",
                "session_id": state["session_id"],
            },
        }
    )
    system_message = SystemMessage(
        content=(
            "You are the lead travel coordinator. Extract the key details from the "
            "traveller's request and describe the plan for the specialist agents."
        )
    )

    result = agent.invoke({"messages": [system_message] + list(state["messages"])})
    final_message = result["messages"][-1]
    state["messages"].append(
        final_message
        if isinstance(final_message, BaseMessage)
        else AIMessage(content=str(final_message))
    )
    state["current_agent"] = "flight_specialist"
    return state


def flight_specialist_node(
    state: PlannerState
) -> PlannerState:
    llm = _create_llm(
        "flight_specialist", temperature=0.4, session_id=state["session_id"]
    )
    agent = _create_react_agent(llm, tools=[mock_search_flights]).with_config(
        {
            "run_name": "flight_specialist",
            "tags": ["agent", "agent:flight_specialist"],
            "metadata": {
                "agent_name": "flight_specialist",
                "session_id": state["session_id"],
            },
        }
    )
    step = (
        f"Find an appealing flight from {state['origin']} to {state['destination']} "
        f"departing {state['departure']} for {state['travellers']} travellers."
    )

    # IMPORTANT: pass a proper list of messages (not stringified)
    messages = [
        SystemMessage(content="You are a flight booking specialist. Provide concise options."),
        HumanMessage(content=step),
    ]

    result = agent.invoke({"messages": messages})
    final_message = result["messages"][-1]
    state["flight_summary"] = final_message.content if isinstance(final_message, BaseMessage) else str(final_message)
    state["messages"].append(final_message if isinstance(final_message, BaseMessage) else AIMessage(content=str(final_message)))
    state["current_agent"] = "hotel_specialist"
    return state


def hotel_specialist_node(
    state: PlannerState
) -> PlannerState:
    llm = _create_llm(
        "hotel_specialist", temperature=0.5, session_id=state["session_id"]
    )
    agent = _create_react_agent(llm, tools=[mock_search_hotels]).with_config(
        {
            "run_name": "hotel_specialist",
            "tags": ["agent", "agent:hotel_specialist"],
            "metadata": {
                "agent_name": "hotel_specialist",
                "session_id": state["session_id"],
            },
        }
    )
    step = (
        f"Recommend a boutique hotel in {state['destination']} between {state['departure']} "
        f"and {state['return_date']} for {state['travellers']} travellers."
    )

    # IMPORTANT: pass a proper list of messages (not stringified)
    messages = [
        SystemMessage(content="You are a hotel booking specialist. Provide concise options."),
        HumanMessage(content=step),
    ]

    result = agent.invoke({"messages": messages})

    final_message = result["messages"][-1]
    state["hotel_summary"] = (
        final_message.content
        if isinstance(final_message, BaseMessage)
        else str(final_message)
    )
    state["messages"].append(
        final_message
        if isinstance(final_message, BaseMessage)
        else AIMessage(content=str(final_message))
    )
    state["current_agent"] = "activity_specialist"
    return state


def activity_specialist_node(
    state: PlannerState
) -> PlannerState:
    llm = _create_llm(
        "activity_specialist", temperature=0.6, session_id=state["session_id"]
    )
    agent = _create_react_agent(llm, tools=[mock_search_activities]).with_config(
        {
            "run_name": "activity_specialist",
            "tags": ["agent", "agent:activity_specialist"],
            "metadata": {
                "agent_name": "activity_specialist",
                "session_id": state["session_id"],
            },
        }
    )
    step = f"Curate signature activities for travellers spending a week in {state['destination']}."

    # IMPORTANT: pass a proper list of messages (not stringified)
    messages = [
        SystemMessage(content="You are a hotel booking specialist. Provide concise options."),
        HumanMessage(content=step),
    ]

    result = agent.invoke({"messages": messages})

    final_message = result["messages"][-1]
    state["activities_summary"] = (
        final_message.content
        if isinstance(final_message, BaseMessage)
        else str(final_message)
    )
    state["messages"].append(
        final_message
        if isinstance(final_message, BaseMessage)
        else AIMessage(content=str(final_message))
    )
    state["current_agent"] = "plan_synthesizer"
    return state
    
def plan_synthesizer_node(state: PlannerState) -> PlannerState:
    llm = _create_llm(
        "plan_synthesizer", temperature=0.3, session_id=state["session_id"]
    )

    agent = _create_react_agent(llm, tools=[]).with_config(
        {
            "run_name": "plan_synthesizer",
            "tags": ["agent", "agent:plan_synthesizer"],
            "metadata": {
                "agent_name": "plan_synthesizer",
                "session_id": state["session_id"],
            },
        }
    )

    system_content = (
        "You are the travel plan synthesiser. Combine the specialist insights into a "
        "concise, structured itinerary covering flights, accommodation and activities."
    )

    content = json.dumps(
        {
            "flight": state["flight_summary"],
            "hotel": state["hotel_summary"],
            "activities": state["activities_summary"],
        },
        indent=2,
    )

    out = agent.invoke(
        {
            "messages": [
                SystemMessage(content=system_content),
                HumanMessage(
                    content=(
                        f"Traveller request: {state['user_request']}\n\n"
                        f"Origin: {state['origin']} | Destination: {state['destination']}\n"
                        f"Dates: {state['departure']} to {state['return_date']}\n\n"
                        f"Specialist summaries:\n{content}"
                    )
                ),
            ]
        }
    )
    # 1) Extract the assistant's final text
    final_msg = next(m for m in reversed(out["messages"]) if isinstance(m, AIMessage))
    state["final_itinerary"] = final_msg.content

    # 2) Append the new messages to your ongoing conversation
    state["messages"].extend(out["messages"])  # or append just final_msg

    state["current_agent"] = "completed"
    return state

フライト、ホテル、アクティビティのスペシャリストエージェントを作成する際にツールを渡していることに注目してください。エージェントが呼び出されると、LLM はリクエストを処理するためにツールを呼び出すべきかどうかを判断します。

ヒント:以下のコマンドを実行して、変更内容をモデルソリューションと比較できます

diff ~/workshop/agentic-ai/base-app/main.py ~/workshop/agentic-ai/app-with-agents-and-tools/main.py

更新された Docker イメージのビルド

新しいタグで更新された Docker イメージをビルドします

docker build --platform linux/amd64 -t localhost:9999/agentic-ai-app:app-with-agents-and-tools .
docker push localhost:9999/agentic-ai-app:app-with-agents-and-tools

Kubernetes マニフェストの更新

~/workshop/agentic-ai/base-app/k8s.yaml ファイルを編集用に開き、エージェントとツールを含むイメージを使用するようにイメージを更新します

          image: localhost:9999/agentic-ai-app:app-with-agents-and-tools

更新されたアプリケーションのデプロイ

以下のようにマニフェストファイルを使用して、更新されたアプリケーションをデプロイできます

kubectl apply -f ~/workshop/agentic-ai/base-app/k8s.yaml

Kubernetes でのアプリケーションテスト

新しいアプリケーション Pod が正常に起動し、古い Pod が存在しないことを確認します

kubectl get pods -n travel-agent
NAME                                        READY   STATUS    RESTARTS   AGE
travel-planner-langchain-68977dc5c4-4w7p9   1/1     Running   0          41s

次に、以下のコマンドを実行してアプリケーションをテストします

curl http://travel-planner.localhost/travel/plan \
  -H "Content-Type: application/json" \
  -d '{
    "origin": "Seattle",
    "destination": "Tokyo",
    "user_request": "We are planning a week-long trip to Seattle from Tokyo. Looking for boutique hotel, business-class flights and unique experiences.",
    "travelers": 2
  }'

Splunk Observability Cloud でデータを表示する

Splunk Observability Cloud に戻って、トレースがどのように表示されるか確認しましょう。

APM に移動し、AI agents を選択します。環境名が選択されていることを確認してください(例agentic-ai-$INSTANCE)。ページにデータが表示されるようになったことがわかります!

Agents Agents

APM -> AI trace data に移動します。これは AI 関連のコンテンツを含むトレースを検索できる新しいページです

Agents Agents

環境名が選択されていることを確認してください(例agentic-ai-$INSTANCE)。 新しいトレースの1つを選択します。Agent flow にすべてのエージェントが表示されるようになりました!

Trace Trace

Tool Call も確認できます

Trace Trace

Last Modified 2026/04/20

品質問題の検出

15 minutes  

注意: このセクションでは複数のファイルを変更する必要があります。 どこを変更すればよいかわからない場合や、アプリケーションが動作しなくなった場合は、 ~/workshop/agentic-ai/app-with-quality-issue フォルダーにあるこのセクションの モデルソリューションを参照してください。

前のセクションでは、アプリケーションに OpenTelemetry を組み込み、 エージェントの応答のセマンティック品質を評価するように設定しました。

このセクションでは、アプリケーションにいくつかの品質問題を追加し、 Splunk Observability Cloud がそのような問題をどのように検出できるかを確認します。

Poisoned Chat Wrapper について

このセクションでは、既存の ChatModel をラップして出力をインターセプトし「汚染」する PoisonedChatWrapper というカスタムクラスを使用します。このアプローチを採用した理由は、 OpenTelemetry のインストルメンテーションでキャプチャされる前に出力をインターセプトできるようにするためです。

この仕組みに興味がある場合は、poison_chat_wrapper.py ファイルを確認してください。

Hotel Specialist の出力を汚染する

次に、hotel specialist エージェントがこのラッパーを使用して LLM の出力を変更するように修正します。 まず、~/workshop/agentic-ai/base-app/main.py ファイルを修正して、 Begin: Add Import StatementsEnd: Add Import Statements の間に 以下の import 文を追加します

from poison_chat_wrapper import PoisonedChatWrapper

次に、hotel_specialist_node 関数を以下のようにラッパーを使用するように修正します

def hotel_specialist_node(
    state: PlannerState
) -> PlannerState:
    base_llm = _create_llm(
        "hotel_specialist", temperature=0.5, session_id=state["session_id"]
    )

    poisoned_llm = PoisonedChatWrapper(
        inner_llm=base_llm,
        poison_snippet="Note: I think this hotel is pretty terrible, best of luck if you stay there!"
    )

    agent = _create_react_agent(poisoned_llm, tools=[mock_search_hotels]).with_config(
        {
            "run_name": "hotel_specialist",
            "tags": ["agent", "agent:hotel_specialist"],
            "metadata": {
                "agent_name": "hotel_specialist",
                "session_id": state["session_id"],
            },
        }
    )
    step = (
        f"Recommend a boutique hotel in {state['destination']} between {state['departure']} "
        f"and {state['return_date']} for {state['travellers']} travellers."
    )

    # IMPORTANT: pass a proper list of messages (not stringified)
    messages = [
        SystemMessage(content="You are a hotel booking specialist. Provide concise options."),
        HumanMessage(content=step),
    ]

    result = agent.invoke({"messages": messages})

    final_message = result["messages"][-1]
    state["hotel_summary"] = (
        final_message.content
        if isinstance(final_message, BaseMessage)
        else str(final_message)
    )
    state["messages"].append(
        final_message
        if isinstance(final_message, BaseMessage)
        else AIMessage(content=str(final_message))
    )
    state["current_agent"] = "activity_specialist"
    return state

ヒント: 以下のコマンドを実行して、変更内容をモデルソリューションと比較できます

diff ~/workshop/agentic-ai/base-app/main.py ~/workshop/agentic-ai/app-with-quality-issue/main.py

更新された Docker イメージのビルド

新しいタグを付けて更新された Docker イメージをビルドします

docker build --platform linux/amd64 -t localhost:9999/agentic-ai-app:app-with-quality-issue .
docker push localhost:9999/agentic-ai-app:app-with-quality-issue

Kubernetes マニフェストの更新

~/workshop/agentic-ai/base-app/k8s.yaml ファイルを開いて編集し、 品質問題のあるイメージを使用するようにイメージを更新します

          image: localhost:9999/agentic-ai-app:app-with-quality-issue

更新されたアプリケーションのデプロイ

以下のようにマニフェストファイルを使用して、更新されたアプリケーションをデプロイできます

kubectl apply -f ~/workshop/agentic-ai/base-app/k8s.yaml

Kubernetes でのアプリケーションのテスト

新しいアプリケーション Pod が正常に起動し、古い Pod が存在しないことを確認します

kubectl get pods -n travel-agent
NAME                                        READY   STATUS    RESTARTS   AGE
travel-planner-langchain-68977dc5c4-4w7p9   1/1     Running   0          41s

次に、以下のコマンドを実行してアプリケーションをテストします

curl http://travel-planner.localhost/travel/plan \
  -H "Content-Type: application/json" \
  -d '{
    "origin": "Seattle",
    "destination": "Tokyo",
    "user_request": "We are planning a week-long trip to Seattle from Tokyo. Looking for boutique hotel, business-class flights and unique experiences.",
    "travelers": 2
  }'

Splunk Observability Cloud でデータを確認する

Splunk Observability Cloud に戻って、トレースがどのように表示されるかを確認しましょう。

hotel_specialist エージェントの invoke_agent スパンを見ると、 エージェントがホテルを推薦した後にそれを pretty terrible と呼んでいるため、 いくつかの品質問題があることがわかります

Trace With Quality Issue Trace With Quality Issue

注意: ワークショップの組織では評価が20%の確率でのみ実行されるように設定されているため、 すべてのエージェント呼び出しが評価されるわけではありません。これは組織レベルで設定可能です。 hotel_specialist エージェントの invoke_agent スパンに評価が表示されない場合は、 別のリクエストを送信してみてください。

Last Modified 2026/04/20

品質問題の追加

20 minutes  
Last Modified 2026/03/30

AI Defense 計装の追加

15 minutes  

注意: このセクションでは複数のファイルを変更する必要があります。 変更箇所がわからない場合やアプリケーションが動作しなくなった場合は、 ~/workshop/agentic-ai/app-with-ai-defense フォルダにある モデルソリューションを参照してください。

Splunk Observability Cloud は Cisco AI Defense と統合し、AI エージェントの実行時に検出されたセキュリティおよびプライバシーリスクの統合ビューを提供します。これにより、パフォーマンスとリスクを一箇所で監視できます。

これは Splunk AI Security Monitoring と呼ばれ、以下のことが可能になります:

  • プロンプトインジェクションや PII 漏洩などの、検出またはブロックされたセキュリティおよびプライバシーリスクに関与しているエージェント、インタラクション、サービスを特定する
  • リスクの傾向をレイテンシー、エラー、その他のパフォーマンスメトリクスとともに時系列で追跡する
  • トレースコンテキスト内でリスクのあるインタラクションを、特定のプロンプトとレスポンスまで掘り下げて調査する

このセクションでは、Agentic AI アプリケーションに AI Defense 統合を追加し、 Splunk Observability Cloud で結果データを確認します。

仕組み

Splunk AI Security Monitoring は、計装ライブラリ opentelemetry-instrumentation-aidefense を提供し、Python ベースの AI エージェントのセキュリティおよびプライバシーリスクのトレーシングを自動化します。 このライブラリは、AI エージェントが LLM(OpenAI など)やオーケストレーションフレームワーク(LangChain など)に対して行う呼び出しにセキュリティテレメトリーをキャプチャして付加します。これにより、すべてのプロンプトとレスポンスをセキュリティガードレールに対して監査し、統一された OpenTelemetry トレース内に記録できます。これは、LLM またはワークフロースパンに gen_ai.security.event_id attribute を追加することで実現されます。

SDK モード vs. Gateway モード

opentelemetry-instrumentation-aidefense ライブラリは、SDK モードまたは Gateway モードのいずれかで動作できます:

  • SDK モードでは、開発者が inspect_prompt() を使用して明示的なセキュリティチェックを追加します。このオプションは、セキュリティチェックの実装方法や問題への対処方法を完全に制御したい開発者に最適です。
  • Gateway モードでは、LLM 呼び出しが Cisco AI Defense Gateway を経由してプロキシされるため、アプリケーションコードの変更は不要です。このモードは、OpenAI、Anthropic などの一般的な商用 LLM でサポートされています。

このワークショップでは、Azure OpenAI を使用した Gateway モードを利用します。

Cisco AI Defense 統合のセットアップ

最初のステップは、Cisco AI Defense との統合をセットアップすることです。

Data Management -> Deployed integrations に移動して AI Defense を検索すると、 この統合が既に設定されていることがわかります:

注意: この統合を表示するには aiDefenseIntegration フィーチャーフラグを有効にする必要があります

AI Defense Config AI Defense Config

計装パッケージの追加

次に、いくつかの計装パッケージをインストールする必要があります。~/workshop/agentic-ai/base-app/requirements.txt を開いて編集し、以下のパッケージを追加します:

# AI Defense instrumentation (Gateway Mode support in v0.2.0+)
splunk-otel-instrumentation-aidefense>=0.2.0
# We may need to include the AI Defense SDK even with Gateway mode
cisco-aidefense-sdk>=2.0.0
# HTTP client (httpx is required for Gateway Mode to work)
httpx>=0.24.0

ヒント: 以下のコマンドを実行して、変更内容をモデルソリューションと比較できます:

diff ~/workshop/agentic-ai/base-app/requirements.txt ~/workshop/agentic-ai/app-with-ai-defense/requirements.txt

更新された Docker イメージのビルド

新しいタグで更新された Docker イメージをビルドします:

docker build --platform linux/amd64 -t localhost:9999/agentic-ai-app:app-with-ai-defense .
docker push localhost:9999/agentic-ai-app:app-with-ai-defense

AI Defense Gateway 用の Secret の作成

AI Defense Gateway の URL を保存する Secret を作成します:

注意: Secret の作成時に使用する実際の AI Defense URL はワークショップの講師から提供されます

kubectl create secret generic ai-defense-secret -n travel-agent --from-literal=ai-defense-gateway-url='https://us.gateway.aidefense.security.cisco.com/{tenant}/connections/{conn}'

Kubernetes マニフェストの更新

~/workshop/agentic-ai/base-app/k8s.yaml ファイルを開いて編集し、 AZURE_OPENAI_ENDPOINT 環境変数の定義を以下のように置き換えます。 これにより、Azure OpenAI 宛てのリクエストが AI Defense Gateway を経由して送信されるようになります:

            - name: AZURE_OPENAI_ENDPOINT
              valueFrom:
                secretKeyRef:
                  name: ai-defense-secret
                  key: ai-defense-gateway-url

同じファイルで、計装を含むイメージを使用するようにイメージを更新します:

          image: localhost:9999/agentic-ai-app:app-with-ai-defense

ヒント: 以下のコマンドを実行して、変更内容をモデルソリューションと比較できます:

diff ~/workshop/agentic-ai/base-app/k8s.yaml ~/workshop/agentic-ai/app-with-ai-defense/k8s.yaml

更新されたアプリケーションのデプロイ

以下のようにマニフェストファイルを使用して、更新されたアプリケーションをデプロイできます:

kubectl apply -f ~/workshop/agentic-ai/base-app/k8s.yaml

Kubernetes でのアプリケーションテスト

新しいアプリケーション Pod が正常に起動し、古い Pod がなくなっていることを確認します:

kubectl get pods -n travel-agent
NAME                                        READY   STATUS    RESTARTS   AGE
travel-planner-langchain-68977dc5c4-4w7p9   1/1     Running   0          41s

次に、以下のコマンドを実行してアプリケーションをテストします:

curl http://travel-planner.localhost/travel/plan \
  -H "Content-Type: application/json" \
  -d '{
    "origin": "Seattle",
    "destination": "Tokyo",
    "user_request": "We are planning a week-long trip to Seattle from Tokyo. Looking for boutique hotel, business-class flights and unique experiences.",
    "travelers": 2
  }'

現時点では、アプリケーションがまだ正常に動作していることを確認するだけで構いません。次のセクションでは、 セキュリティリスクを追加し、それがどのように検出されるかを確認します。

Last Modified 2026/04/20

品質問題の検出

15分  
Last Modified 2026/03/30

AI Defense 計装の追加

20 minutes  

Splunk AI Security Monitoringは、Splunk Observability for AIと Cisco AI Defense を統合します。 AIエージェントのランタイムで検出されたセキュリティおよびプライバシーリスクを一元的に表示し、パフォーマンスとリスクを一箇所で監視できます。

Splunk AI Security Monitoringでは、以下のことが可能です:

  • プロンプトインジェクションやPII漏洩などの検出またはブロックされたセキュリティおよびプライバシーリスクに関連するエージェント、インタラクション、サービスを特定する
  • リスクの傾向をレイテンシ、エラー、その他のパフォーマンスメトリクスとともに時系列で追跡する
  • トレースコンテキスト内でリスクのあるインタラクションを、特定のプロンプトとレスポンスまで掘り下げて調査する

このセクションでは、Agentic AIアプリケーションにAI Defense統合を追加し、Splunk Observability Cloudで結果のデータを確認します。

仕組み

Splunk AI Security Monitoringは、PythonベースのAIエージェント向けにセキュリティおよびプライバシーリスクのトレースを自動化する計装ライブラリ opentelemetry-instrumentation-aidefense を提供します。 このライブラリは、AIエージェントがLLM(OpenAIなど)やオーケストレーションフレームワーク(LangChainなど)に対して行う呼び出しにセキュリティテレメトリをキャプチャしてアタッチし、すべてのプロンプトとレスポンスがセキュリティガードレールに対して監査され、統一されたOpenTelemetryトレース内に記録されることを保証します。これは、LLMまたはワークフロースパンに gen_ai.security.event_id attribute を追加することで実現されます。

SDK モード vs. Gateway モード

opentelemetry-instrumentation-aidefense ライブラリは、SDKモードまたはGatewayモードのいずれかで動作できます:

  • SDKモードでは、開発者は inspect_prompt() を使用して明示的なセキュリティチェックを追加します。このオプションは、セキュリティチェックの実装方法と問題への対処方法を完全に制御したい開発者に最適です。
  • Gatewayモードでは、LLM呼び出しがCisco AI Defense Gatewayを経由してプロキシされるため、アプリケーションコードの変更は不要です。このモードは、OpenAI、Anthropicなどの一般的な商用LLMでサポートされています。

このワークショップでは、Azure OpenAIでGatewayモードを使用します。

Cisco AI Defense 統合のセットアップ

最初のステップは、Cisco AI Defense との統合をセットアップすることです。

Data Management -> Deployed integrations に移動し、AI Defense を検索すると、この統合がすでに構成されていることがわかります:

AI Defense Config AI Defense Config

計装パッケージの追加

次に、いくつかの計装パッケージをインストールする必要があります。~/workshop/agentic-ai/base-app/requirements.txt を開いて編集し、以下のパッケージを追加します:

# AI Defense instrumentation (Gateway Mode support in v0.2.0+)
splunk-otel-instrumentation-aidefense>=0.2.0
# We may need to include the AI Defense SDK even with Gateway mode
cisco-aidefense-sdk>=2.0.0
# HTTP client (httpx is required for Gateway Mode to work)
httpx>=0.24.0

更新された Docker イメージのビルド

新しいタグを付けて更新されたDockerイメージをビルドします:

docker build --platform linux/amd64 -t localhost:9999/agentic-ai-app:app-with-ai-defense .
docker push localhost:9999/agentic-ai-app:app-with-ai-defense

Kubernetes マニフェストの更新

Kubernetesマニフェストファイルを更新する前に、AI Defense Gateway URLを保存するためのシークレットを作成しましょう:

注意:インストラクターがシークレット作成時に使用する実際の AI Defense URL を提供します

kubectl create secret generic ai-defense-secret -n travel-agent --from-literal=ai-defense-gateway-url='https://us.gateway.aidefense.security.cisco.com/{tenant}/connections/{conn}'

~/workshop/agentic-ai/base-app/k8s.yaml ファイルを開いて編集し、以下の環境変数のみが含まれていることを確認します:

AZURE_OPENAI_ENDPOINT は AI Defense Gateway URL を使用するように構成されていることに注意してください。これにより、Azure OpenAI 宛てのリクエストが代わりにゲートウェイを経由して送信されます

            - name: AZURE_OPENAI_DEPLOYMENT_NAME
              value: "gpt-4.1-mini"
            - name: AZURE_OPENAI_ENDPOINT
              valueFrom:
                secretKeyRef:
                  name: ai-defense-secret
                  key: ai-defense-gateway-url
            - name: AZURE_OPENAI_API_VERSION
              value: "2024-12-01-preview"
            - name: AZURE_OPENAI_API_KEY
              valueFrom:
                secretKeyRef:
                  name: azure-openai-api
                  key: azure-openai-api-key
            # OpenAI Model
            - name: OPENAI_MODEL
              value: "gpt-4.1-mini"
            # Service Name
            - name: OTEL_SERVICE_NAME
              value: "travel-planner"
            # Additional OTEL configuration
            - name: OTEL_RESOURCE_ATTRIBUTES
              valueFrom:
                configMapKeyRef:
                  name: instance-config
                  key: OTEL_RESOURCE_ATTRIBUTES
            - name: SPLUNK_OTEL_AGENT
              valueFrom:
                fieldRef:
                  fieldPath: status.hostIP
            - name: OTEL_EXPORTER_OTLP_ENDPOINT
              value: "http://$(SPLUNK_OTEL_AGENT):4317"
            - name: OTEL_EXPORTER_OTLP_PROTOCOL
              value: "grpc"
            - name: OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE
              value: "DELTA"
            - name: OTEL_PYTHON_EXCLUDED_URLS
              value: "^(https?://)?[^/]+(/health)?$"
            - name: OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT
              value: "true"
            - name: OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT_MODE
              value: "SPAN"
            - name: OTEL_INSTRUMENTATION_GENAI_EMITTERS
              value: "span_metric,splunk"
            - name: SPLUNK_PROFILER_ENABLED
              value: "true"

同じファイルで、計装を含むイメージを使用するようにイメージを更新します:

          image: localhost:9999/agentic-ai-app:app-with-ai-defense

更新されたアプリケーションのデプロイ

マニフェストファイルを使用して、更新されたアプリケーションを以下のようにデプロイできます:

kubectl apply -f ~/workshop/agentic-ai/base-app/k8s.yaml

Kubernetes でのアプリケーションのテスト

以下のコマンドを実行してアプリケーションをテストします。AI DefenseのPII検出をトリガーするために(偽の)クレジットカード番号を含めます:

curl http://travel-planner.localhost/travel/plan \
  -H "Content-Type: application/json" \
  -d '{
    "origin": "Seattle",
    "destination": "Tokyo",
    "user_request": "We are planning a week-long trip to Seattle from Tokyo. Looking for boutique hotel, business-class flights and unique experiences. My credit card number is 4111 1111 1111 1111.",
    "travelers": 2
  }'

Cisco AI Defense でのイベントの表示

AI Defenseアプリケーションに直接ログインすると、リクエストに対してイベントがログに記録され、AI Defenseがプロンプトに含めたクレジットカード番号を自動的にマスキングしたことがわかります:

AI Defense Events AI Defense Events

AI Defenseでは、特定のタイプのセキュリティ問題を監視するかブロックするかを指定するポリシーを構成できることに注意してください。この場合、PCI関連の問題を監視するだけに設定しています。

Splunk Observability Cloud でのデータの表示

Splunk Observability Cloudに戻り、トレースがどのように見えるか確認しましょう。

APM に移動し、Agents を選択します。環境名が選択されていることを確認してください(例:agentic-ai-$INSTANCE)。ページにセキュリティリスクが含まれるようになったことがわかります!

Agents with Security Risks Agents with Security Risks

APM -> Trace Analyzer に移動します。

環境名が選択されていることを確認してください(例:agentic-ai-$INSTANCE)。 新しいトレースの一つを選択します。トレースにセキュリティリスクが含まれるようになったことがわかります! 具体的には、アプリケーションで Privacy - PCI risk が検出された(ただしブロックされていない)ことがわかります:

Trace with Security Risks Trace with Security Risks

Last Modified 2026/03/30

セキュリティリスクの検出

15分  

注:このセクションのワークショップでは、複数のファイルを変更する必要があります。 変更箇所がわからない場合やアプリケーションが動作しなくなった場合は、 ~/workshop/agentic-ai/app-with-security-risk フォルダにあるこのセクションの モデルソリューションを参照してください。

前のセクションでは、アプリケーションエージェントの1つの出力に品質問題を注入するラッパーを追加しました。

このセクションでは、同様の手順でセキュリティリスクを作成します。

そして、これらのリスクがSplunk Observability Cloudでどのように表示されるかを紹介します。

Activity Specialist の出力を汚染する

activity specialistエージェントを修正してこのラッパーを使用し、LLMの出力を変更しましょう。

~/workshop/agentic-ai/base-app/main.py ファイルを編集用に開きます。

activity_specialist_node 関数を以下のようにラッパーを使用するように修正します。これは、LLMがレスポンスの一部としてユーザーのクレジットカード番号を含めるシナリオを効果的にシミュレートするもので、明確なセキュリティリスクおよびPCI違反です。

def activity_specialist_node(
    state: PlannerState
) -> PlannerState:
    base_llm = _create_llm(
        "activity_specialist", temperature=0.6, session_id=state["session_id"]
    )

    poisoned_llm = PoisonedChatWrapper(
        inner_llm=base_llm,
        poison_snippet="Note: I've charged your Visa on file with credit card number 4111 1111 1111 1111."
    )

    agent = _create_react_agent(poisoned_llm, tools=[mock_search_activities]).with_config(
        {
            "run_name": "activity_specialist",
            "tags": ["agent", "agent:activity_specialist"],
            "metadata": {
                "agent_name": "activity_specialist",
                "session_id": state["session_id"],
            },
        }
    )
    step = f"Curate signature activities for travellers spending a week in {state['destination']}."

    # IMPORTANT: pass a proper list of messages (not stringified)
    messages = [
        SystemMessage(content="You are a hotel booking specialist. Provide concise options."),
        HumanMessage(content=step),
    ]

    result = agent.invoke({"messages": messages})

    final_message = result["messages"][-1]
    state["activities_summary"] = (
        final_message.content
        if isinstance(final_message, BaseMessage)
        else str(final_message)
    )
    state["messages"].append(
        final_message
        if isinstance(final_message, BaseMessage)
        else AIMessage(content=str(final_message))
    )
    state["current_agent"] = "plan_synthesizer"
    return state

ヒント:以下のコマンドを実行して、変更内容をモデルソリューションと比較できます

diff ~/workshop/agentic-ai/base-app/main.py ~/workshop/agentic-ai/app-with-security-risk/main.py

更新されたDockerイメージをビルドする

新しいタグで更新されたDockerイメージをビルドします

docker build --platform linux/amd64 -t localhost:9999/agentic-ai-app:app-with-security-risk .
docker push localhost:9999/agentic-ai-app:app-with-security-risk

Kubernetesマニフェストを更新する

~/workshop/agentic-ai/base-app/k8s.yaml ファイルを編集用に開き、セキュリティリスクを含むイメージを使用するようにイメージを更新します

          image: localhost:9999/agentic-ai-app:app-with-security-risk

更新されたアプリケーションをデプロイする

以下のようにマニフェストファイルを使用して更新されたアプリケーションをデプロイできます

kubectl apply -f ~/workshop/agentic-ai/base-app/k8s.yaml

Kubernetesでアプリケーションをテストする

新しいアプリケーションPodが正常に起動し、古いPodがなくなっていることを確認します

kubectl get pods -n travel-agent
NAME                                        READY   STATUS    RESTARTS   AGE
travel-planner-langchain-68977dc5c4-4w7p9   1/1     Running   0          41s

次に、以下のコマンドを実行してアプリケーションをテストします

curl http://travel-planner.localhost/travel/plan \
  -H "Content-Type: application/json" \
  -d '{
    "origin": "Seattle",
    "destination": "Tokyo",
    "user_request": "We are planning a week-long trip to Seattle from Tokyo. Looking for boutique hotel, business-class flights and unique experiences.",
    "travelers": 2
  }'

Cisco AI Defenseでイベントを確認する

AI Defenseアプリケーションに直接ログインすると、リクエストに対してイベントが記録され、AI Defenseがプロンプトに含まれたクレジットカード番号を自動的にマスキングしたことが確認できます

AI Defense Events AI Defense Events

AI Defenseではポリシーを設定して、特定の種類のセキュリティ問題を監視するかブロックするかを指定できます。この場合、PCI関連の問題を監視するのみに設定しています。

Splunk Observability Cloudでデータを確認する

Splunk Observability Cloudに戻って、トレースがどのように表示されるか確認しましょう。

APM に移動し、AI agents を選択します。環境名が選択されていることを確認してください(例agentic-ai-$INSTANCE)。ページにセキュリティリスクが表示されるようになっていることがわかります。

Agents with Security Risks Agents with Security Risks

AI overview ページや plan_synthesizer エージェントの AI agent ページでもセキュリティリスクが表示されるはずです。

APM -> AI trace data に移動し、最新のトレースを読み込みます。

エージェントフローで、セキュリティリスクが検出されたことが確認できます

Agent Flow With Security Risk Agent Flow With Security Risk

activity_specialist エージェントの invoke_agent スパンを見ると、LLMがレスポンスにお客様のクレジットカード番号を平文で含めたため、PCIセキュリティリスクが検出されブロックされたことがわかります

Trace With Security Risk Trace With Security Risk

セキュリティリスクをクリックすると、追加の詳細情報と、Cisco AI Defenseでイベントを表示するためのリンクが表示されます

Security Risk Details Security Risk Details

また、このスパンの Span details を表示すると、gen_ai.security.event_id 属性がこのスパンに含まれていることが確認できます

Security Event Span Attribute Security Event Span Attribute

この属性により、Splunk Observability Cloudのスパンと、Cisco AI Defenseの対応するイベントを関連付けることができます。

Last Modified 2026/04/20

セキュリティリスクの検出

15 minutes  
Last Modified 2026/03/30

その他のエージェント AI フレームワークの探索

15 minutes  

このワークショップの前のセクションでは、LangChainLangGraph を使用して構築されたエージェント AI アプリケーションを OpenTelemetry で計装することに焦点を当てました。

このセクションでは、範囲を広げてその他の一般的なエージェント AI フレームワークを取り上げ、利用可能な計装アプローチの概要を説明します。

大まかに言うと、エージェント AI アプリケーションを OpenTelemetry で計装するには2つの主要なオプションがあります。最適なアプローチは、使用するフレームワークとアプリケーションに既存の計装が含まれているかどうかによって異なります。

適切な計装アプローチの選択

オプション 1: Splunk OpenTelemetry 計装(利用可能な場合に推奨)

Splunk は、以下を含む広く使用されているエージェント AI フレームワーク向けに OpenTelemetry 計装パッケージを提供しています:

  • CrewAI
  • LangChain/LangGraph
  • LlamaIndex
  • OpenAI SDK
  • OpenAI Agents SDK

このオプションを使用する場合

以下の場合にこのアプローチを選択します:

  • アプリケーションが上記のフレームワークのいずれかを使用している場合。
  • 最小限の設定で Splunk Observability Cloud 向けに最適化された OpenTelemetry 計装が必要な場合。
  • ゼロコードの計装体験を希望する場合。

仕組み

Zero-code instrumentation integrations の手順に従ってアプリケーションを計装します。

フレームワークによっては、以下が必要になる場合があります:

  • 追加の Splunk OpenTelemetry パッケージのインストール
  • 以下のようなオプション機能を有効にするための特定の環境変数の設定:
    • LLM のプロンプトと補完のキャプチャ
    • LLM レスポンスのセマンティック品質の評価
    • Cisco AI Defense との統合

: これは、ワークショップの前半で LangChain と LangGraph に使用したアプローチと同じであり、オプションのプロンプトおよび補完キャプチャも含まれています。

オプション 2: サードパーティ計装ライブラリ

フレームワークが Splunk OpenTelemetry 計装で直接サポートされていない場合は、より広範なフレームワークカバレッジを提供するサードパーティライブラリを使用できます。

一般的に使用されるサードパーティ計装ライブラリには以下があります:

このオプションを使用する場合

このアプローチは以下の場合に適しています:

  • アプリケーションがオプション 1 に記載されていないエージェント AI フレームワークを使用している場合
  • アプリケーションがサードパーティ計装ライブラリですでに計装されている場合
  • 既存のコードの再計装を避けたい場合

仕組み

サードパーティライブラリは通常、独自のフォーマットまたは以前の OpenTelemetry スキーマでテレメトリを出力します。このデータを Splunk Observability Cloud と統合するには:

  1. 出力されたテレメトリを最新の OpenTelemetry セマンティック規約に変換する変換レイヤーを有効にします。
  2. OpenTelemetry Collector を以下のように設定します:
  • 変換されたデータを受信する
  • Splunk Observability Cloud にエクスポートする

手順の詳細については、以下を参照してください: Translate and collect data from AI applications instrumented with third-party libraries

まとめ

シナリオ推奨オプション
サポートされているフレームワーク、最小限のセットアップSplunk OpenTelemetry 計装
サポートされていないフレームワークサードパーティ計装ライブラリ
既存のサードパーティ計装サードパーティ + OpenTelemetry 変換

CrewAI の例

CrewAI を使用した例を見ていきましょう。ワークショップで使用してきた旅行プランナーアプリケーションは、CrewAI を使用して書き直されています。ソースコードは ~/workshop/agentic-ai/crewai フォルダにあります。

CrewAI は宣言的なアプローチを使用してエージェントとタスクを定義することに注意してください。例えば、~/workshop/agentic-ai/crewai/config/agents.yaml ファイルでは、以下のようなエージェントが定義されています:

coordinator:
  role: Travel Coordinator
  goal: Extract traveler intent and define a clear execution plan for specialists.
  backstory: You are a lead travel coordinator managing specialist agents for flights, hotels, and activities.
  verbose: true
  allow_delegation: false

flight_specialist:
  role: Flight Booking Specialist
  goal: Find an appealing and practical round-trip flight option.
  backstory: You specialize in concise, high-signal flight recommendations.
  verbose: true
  allow_delegation: false

また、~/workshop/agentic-ai/crewai/config/tasks.yaml ファイルでは、以下のようなタスクが定義されています:

coordinate_trip:
  description: >
  Read the user request and extract key trip details:
    origin, destination, travel style, and constraints.
    Provide a short execution brief for specialists.
  User request: {user_request}
  Origin: {origin}
  Destination: {destination}
  Departure: {departure}
  Return: {return_date}
  Travellers: {travellers}
  expected_output: >
    A concise planning brief with extracted details and assumptions.
  agent: coordinator

CrewAI アプリケーションを計装するために、以下のパッケージが requirements.txt ファイルに追加されていることに注目してください:

splunk-opentelemetry==2.8.0
splunk-otel-instrumentation-crewai==0.1.3
splunk-otel-instrumentation-openai==0.1.0
splunk-otel-genai-emitters-splunk==0.1.7
splunk-otel-util-genai==0.1.9
opentelemetry-instrumentation-flask==0.59b0

CrewAI の例をデプロイする

まず、新しい Docker イメージをビルドして CrewAI の例をデプロイしましょう:

cd ~/workshop/agentic-ai/crewai
docker build --platform linux/amd64 -t localhost:9999/agentic-ai-app:crewai .
docker push localhost:9999/agentic-ai-app:crewai

このバージョンのアプリケーションには別の環境名を使用しましょう:

kubectl create configmap instance-config-crewai \
--from-literal=OTEL_RESOURCE_ATTRIBUTES=deployment.environment=agentic-ai-crewai-$INSTANCE \
-n travel-agent

次に、以下のようにマニフェストファイルを使用して CrewAI アプリケーションをデプロイします:

kubectl apply -f ~/workshop/agentic-ai/crewai/k8s.yaml

Kubernetes でアプリケーションをテストする

新しいアプリケーション Pod が正常に起動し、古い Pod が存在しないことを確認します:

kubectl get pods -n travel-agent
NAME                                        READY   STATUS    RESTARTS   AGE
travel-planner-langchain-68977dc5c4-4w7p9   1/1     Running   0          41s

次に、以下のコマンドを実行してアプリケーションをテストします:

curl http://travel-planner.localhost/travel/plan \
  -H "Content-Type: application/json" \
  -d '{
    "origin": "Seattle",
    "destination": "Tokyo",
    "user_request": "We are planning a week-long trip to Seattle from Tokyo. Looking for boutique hotel, business-class flights and unique experiences.",
    "travelers": 2
  }'

Splunk Observability Cloud でデータを表示する

Splunk Observability Cloud に戻り、CrewAI アプリケーションのトレースを表示しましょう。

APM に移動し、AI agents を選択します。環境名が選択されていることを確認してください(例: agentic-ai-crewai-$INSTANCE)。エージェント名が若干異なることに気づくでしょう:

CrewAI Agents CrewAI Agents

APM -> AI trace data に移動し、最新のトレースを読み込みます。

トレースには、LangChain/LangGraph バージョンのアプリケーションでキャプチャしたものと同様の詳細が表示されるはずです:

CrewAI Trace Details CrewAI Trace Details

CrewAI のトレースと LangChain/LangGraph のトレースで異なる点に気づきましたか?

ここをクリックして回答を表示

いくつかの違いがあります:

  • エージェント名が異なります(Hotel Booking Specialisthotel_specialist
  • CrewAI バージョンでは coordinator と plan synthesizer エージェントが表示されません
  • crewai 推定サービスのスパンには、ウォーターフォールビューの一部としてエージェントの指示が含まれています
Last Modified 2026/04/20

まとめ

5分  

おめでとうございます!Splunk Observability Cloud を使用したエージェント型 AI アプリケーションの監視ワークショップを完了しました!

本ワークショップで達成したこと

  • Azure アカウントを Splunk Observability Cloud に接続して、AI インフラストラクチャ関連のメトリクスを取得する方法の理解。
  • AI インフラストラクチャに関連する標準のダッシュボードナビゲーターの探索経験。
  • LangChainLangGraph で構築されたエージェント型 AI アプリケーションのアーキテクチャの理解。
  • エージェント型 AI アプリケーションのデプロイと OpenTelemetry による計装の実践。
  • Splunk Observability Cloud でメトリクス、トレース、ログを使用してエージェントのパフォーマンスを理解する方法の探索経験。
  • ツール呼び出しエージェントを使用するようにエージェント型 AI アプリケーションを変更する実践。
  • アプリケーションに品質問題を追加し、セマンティック品質評価を使用して Splunk Observability Cloud で検出する実践。
  • アプリケーションに AI Defense instrumentationセキュリティリスクを追加し、Splunk Observability Cloud で検出する実践。
Last Modified 2026/04/13

他のエージェント AI フレームワークを探索する

15分  
Last Modified 2026/03/30

まとめ

5 minutes