> ## Documentation Index
> Fetch the complete documentation index at: https://docs.coreweave.com/llms.txt
> Use this file to discover all available pages before exploring further.

# クイックスタート: カスタムエージェントの可観測性を設定する

> Agent Lens でカスタムエージェントをインストルメントし、既存の OTel スパンを取得します。

CoreWeave Forge SDK を使用すると、一般的な SDK やカスタムハーネスで構築したエージェントをトレースできます。このクイックスタートでは、独自に構築したマルチターンエージェントに CoreWeave Agent Lens を手動で統合し、OpenTelemetry スパンを出力して取得する方法を説明します。エージェント向け Agent Lens の概要については、[エージェントをトレースする](/ja/products/agent-lens/tracing/instrument)を参照してください。

Claude Agent SDK や Codex などの SDK やハーネスに Agent Lens を統合する場合は、[エージェントインテグレーションを選択する](/ja/products/agent-lens/get-started/integrations)を参照してください。Agent Lens は、エージェント構築用の各種 SDK やエージェントハーネスに自動でパッチを適用するため、すばやく統合できます。

<h2 id="what-youll-learn">
  学習内容
</h2>

このクイックスタートを終えると、Agent Lens 互換の OTel スパンを出力するマルチターンエージェントが動作する状態になります。また、Agent Lens が会話、ターン、LLM Call、ツール呼び出しをエージェントコードにどのように対応付けるかを理解できるため、同じパターンを独自のカスタムエージェントにも適用できるようになります。

このガイドのコードでは、Wikipedia で情報を調べられる小規模な Python または TypeScript のリサーチエージェントをセットアップします。このエージェントは 3 つの質問 (3 ターン) を行い、Wikipedia を検索して回答を探すタイミングを LLM に判断させます。Agent Lens はすべてのステップ (会話、各質問、各 AI の応答、各 Wikipedia ルックアップ) を記録するため、Agent Lens の Conversations タブで処理の流れを確認できます。

このガイドでは、次の方法を説明します。

* `tracing.init()` を使用して、エージェントトレース用に Agent Lens を初期化する。
* `start_conversation` / `startConversation` と `start_turn` / `startTurn` を使用して、会話とターンを開始する。
* `start_llm` / `startLLM` で LLM Call をラップし、使用量を記録する。
* `start_tool` / `startTool` でツール実行をラップし、結果を記録する。
* トークン数とコストがレンダリングされるように、完全なトークン使用量と料金を算出可能なモデルを記録する。
* 生成された会話、ターン、ツール呼び出しを Conversations タブで表示する。

<h2 id="how-the-agent-lens-sdk-works-with-agents">
  Agent Lens SDK とエージェントの連携の仕組み
</h2>

Agent Lens SDK には、エージェント向けの汎用的な OTel 取り込みシステムが含まれています。そのため、Agent Lens はエージェントのコード内にある任意の OTel スパンから情報を取得できます。ただし、Agent Lens UI の Conversations タブでエージェントのトレースをレンダリングするには、以下のスパンを特別に処理する必要があります。

| 概念 | Python | TypeScript | OTel スパン |
| - | - | - | - |
| 1 つの会話 | `tracing.start_conversation(...)` | `tracing.startConversation(...)` | (スパンなし、ターンをグループ化) |
| ユーザーまたはエージェントによる 1 回のやり取り | `tracing.start_turn(...)` | `tracing.startTurn(...)` | `invoke_agent` |
| 1 回の LLM API 呼び出し | `tracing.start_llm(...)` | `tracing.startLLM(...)` | `chat` |
| 1 回のツール実行 | `tracing.start_tool(...)` | `tracing.startTool(...)` | `execute_tool` |

Python では、4 つの関数はいずれもコンテキストマネージャーとして使用できます (`with tracing.start_*(...) as obj:`) 。ブロックを抜ける際には、例外が発生した場合も含め、スパンが終了し属性がフラッシュされます。TypeScript では、返された各オブジェクトに対して `.end()` を呼び出してください。例外発生時にも確実にクリーンアップされるよう `try { ... } finally { obj.end(); }` を使用し、同時に実行される run 同士が会話を共有しないよう、エージェントの各 run を `tracing.runIsolated()` でラップしてください。

`gen_ai.usage.*` や `gen_ai.agent.name` など、その他の [GenAI セマンティック規約の属性](https://opentelemetry.io/docs/specs/semconv/gen-ai/gen-ai-agent-spans/)を使用すると追加のレンダリングが可能になりますが、これらは必須ではありません。

<h2 id="prerequisites">
  前提条件
</h2>

* CoreWeave Forge アカウントと [APIキー](https://forge.coreweave.com/settings#apikeys)
* OpenAI APIキー
* Python 3.9 以降 (Python のサンプルを使用する場合)
* Node.js 18 以降と、`tsx` などの TypeScript ランナー (TypeScript のサンプルは組み込みの `fetch` を必要とし、プレーンな JavaScript としては実行できません)

<h2 id="install-packages">
  パッケージのインストール
</h2>

開発環境に次のパッケージをインストールします。

<CodeGroup>
  ```bash Python theme={"system"}
  pip install coreweave openai requests
  ```

  ```bash TypeScript theme={"system"}
  npm install @coreweave/forge-sdk openai
  npm install --save-dev tsx
  ```
</CodeGroup>

TypeScript のサンプルは `.mts` ファイルとして保存し、`npx tsx [FILENAME].mts` で実行します。

<h2 id="initialize-agent-lens">
  Agent Lens を初期化する
</h2>

`tracing.init()` は APIキーで認証を行い、エージェント スパンを Agent Lens に送信する OTel エクスポーターを設定します。プロジェクト名にはチーム名を含める必要があります。SDK は `WANDB_API_KEY` 環境変数から APIキーを読み取りますが、`api_key` / `apiKey` 引数で渡すこともできます。

<CodeGroup>
  ```python lines Python theme={"system"}
  import getpass
  import os

  os.environ["WANDB_API_KEY"] = getpass.getpass("Enter your API key: ")
  os.environ["OPENAI_API_KEY"] = getpass.getpass("Enter your OpenAI API key: ")

  TEAM = input("Enter your team name: ")
  PROJECT = input("Enter your project name: ")

  from coreweave.forge.agentlens import tracing
  tracing.init(f"{TEAM}/{PROJECT}", autopatch_integrations=False)
  ```

  ```typescript lines highlight="" TypeScript theme={"system"}
  // このプロジェクトを実行する前に、環境に WANDB_API_KEY と OPENAI_API_KEY を設定してください
  import { tracing } from '@coreweave/forge-sdk/agentlens';

  await tracing.init('[YOUR-TEAM]/[YOUR-PROJECT]');
  ```
</CodeGroup>

<h2 id="define-a-tool">
  ツールを定義する
</h2>

次のコードでは、エージェントが使用する Wikipedia 検索ツールと、そのツールをいつどのように使用するかを指定する OpenAI のツールスキーマを定義します。

<CodeGroup>
  ```python lines Python theme={"system"}
  import json
  import requests

  def wikipedia_search(query: str) -> str:
      r = requests.get(
          "https://en.wikipedia.org/w/api.php",
          params={
              "action": "query", "generator": "search", "gsrsearch": query, "gsrlimit": 1,
              "prop": "extracts", "exintro": True, "explaintext": True, "format": "json",
          },
          headers={"User-Agent": "agent-lens-demo"},
      ).json()
      return next(iter(r["query"]["pages"].values()))["extract"]

  wikipedia_tool_schema = {
      "type": "function",
      "function": {
          "name": "wikipedia_search",
          "description": "Search Wikipedia for a topic and return its intro paragraph.",
          "parameters": {
              "type": "object",
              "properties": {"query": {"type": "string"}},
              "required": ["query"],
          },
      },
  }
  ```

  ```typescript lines TypeScript theme={"system"}
  async function wikipediaSearch(query: string): Promise<string> {
    const url = new URL('https://en.wikipedia.org/w/api.php');
    url.search = new URLSearchParams({
      action: 'query',
      generator: 'search',
      gsrsearch: query,
      gsrlimit: '1',
      prop: 'extracts',
      exintro: 'true',
      explaintext: 'true',
      format: 'json',
    }).toString();
    const res = await fetch(url, { headers: { 'User-Agent': 'agent-lens-demo' } });
    const data = (await res.json()) as {
      query: { pages: Record<string, { extract: string }> };
    };
    return Object.values(data.query.pages)[0].extract;
  }

  const wikipediaToolSchema = {
    type: 'function' as const,
    function: {
      name: 'wikipedia_search',
      description: 'Search Wikipedia for a topic and return its intro paragraph.',
      parameters: {
        type: 'object',
        properties: { query: { type: 'string' } },
        required: ['query'],
      },
    },
  };
  ```
</CodeGroup>

<h2 id="run-a-traced-multi-turn-agent">
  トレースされるマルチターンエージェントを実行する
</h2>

ツールと Agent Lens の初期化が完了したら、次のステップではそれらを組み合わせて完全なエージェントループを構築します。このループを見ると、会話、ターン、LLM Call、ツール呼び出しがどのようにネストされるかがわかります。

次の例では、1 つの会話の中で 3 つのターンを実行します。各ターンでは次の処理を行います。

1. `chat` スパンを開始し、ツールを呼び出すかどうかを LLM に判断させます。
2. LLM がツールの使用をリクエストした場合は、その呼び出しを囲む `execute_tool` スパンを開始し、結果を LLM に返します。
3. 2 つ目の `chat` スパンを開始し、最終的な回答を生成します。

<Note>
  Agent Lens SDK は、OpenAI、Anthropic、Google Gen AI の各クライアントライブラリによる呼び出しを自動的にトレースします。このクイックスタートでは、スパン同士の関係を示すために `start_llm()` を使用して各 LLM Call を手動で記録するので、`init()` に `autopatch_integrations=False` を渡しています。これを指定しないと、各呼び出しが `start_llm()` スパンと自動インテグレーションの両方で記録され、二重に記録されます。この引数は最初の `init()` 呼び出しで渡してください。以前の呼び出しで適用されたパッチは、後から `autopatch_integrations=False` を指定して呼び出しても削除されません。独自のコードでは、自動パッチ適用で LLM Call を記録するか、自動パッチ適用を無効にして自分で記録するかのどちらかにしてください。
</Note>

<CodeGroup>
  ```python lines Python theme={"system"}
  from coreweave.forge.agentlens import tracing
  from openai import OpenAI

  openai_client = OpenAI()
  MODEL = "gpt-4o-mini"

  def run_turn(history, user_message):
      history.append({"role": "user", "content": user_message})

      with tracing.start_turn(user_message=user_message, model=MODEL):
          # LLM Call 1: モデルがツールを使用すると判断する場合があります。
          with tracing.start_llm(model=MODEL, provider_name="openai") as llm:
              resp = openai_client.chat.completions.create(
                  model=MODEL, messages=history, tools=[wikipedia_tool_schema],
              )
              msg = resp.choices[0].message
              llm.output(msg.content or "")
              # record() は、使用量、料金計算の対象となるモデル、応答 ID をまとめて設定します。
              llm.record(
                  usage=tracing.Usage(
                      input_tokens=resp.usage.prompt_tokens,
                      output_tokens=resp.usage.completion_tokens,
                      cache_read_input_tokens=getattr(
                          resp.usage.prompt_tokens_details, "cached_tokens", 0
                      ),
                  ),
                  response_id=resp.id,
                  response_model=resp.model,
              )
              history.append(msg.model_dump(exclude_none=True))

          # ツールがリクエストされなかった場合は、最初の LLM の応答をそのまま回答とします。
          if not msg.tool_calls:
              return msg.content

          # リクエストされたツール呼び出しを 1 つずつ実行します。
          for tc in msg.tool_calls:
              with tracing.start_tool(
                  name=tc.function.name,
                  arguments=tc.function.arguments,
                  tool_call_id=tc.id,
              ) as tool:
                  tool.result = wikipedia_search(**json.loads(tc.function.arguments))
                  history.append({
                      "role": "tool",
                      "tool_call_id": tc.id,
                      "content": tool.result,
                  })

          # LLM Call 2: 最終的な回答を生成します。
          with tracing.start_llm(model=MODEL, provider_name="openai") as llm:
              resp = openai_client.chat.completions.create(model=MODEL, messages=history)
              msg = resp.choices[0].message
              llm.output(msg.content)
              llm.record(
                  usage=tracing.Usage(
                      input_tokens=resp.usage.prompt_tokens,
                      output_tokens=resp.usage.completion_tokens,
                      cache_read_input_tokens=getattr(
                          resp.usage.prompt_tokens_details, "cached_tokens", 0
                      ),
                  ),
                  response_id=resp.id,
                  response_model=resp.model,
              )
              history.append({"role": "assistant", "content": msg.content})
              return msg.content

  tracing.init(f"{TEAM}/{PROJECT}", autopatch_integrations=False)

  with tracing.start_conversation(agent_name="research-bot") as conversation:
      history = []
      for question in [
          "Who founded Anthropic?",
          "What is Claude (the AI assistant)?",
          "Summarize what we discussed in one sentence.",
      ]:
          print(f"USER: {question}")
          print(f"AGENT: {run_turn(history, question)}\n")

  tracing.shutdown()
  ```

  ```typescript lines TypeScript theme={"system"}
  import { tracing } from '@coreweave/forge-sdk/agentlens';
  import OpenAI from 'openai';

  const openaiClient = new OpenAI();
  const MODEL = 'gpt-4o-mini';

  // history は OpenAI のチャットメッセージのリストです。簡潔にするため、型指定は緩くしています。
  async function runTurn(history: any[], userMessage: string): Promise<string | null> {
    history.push({ role: 'user', content: userMessage });

    const turn = tracing.startTurn({ userMessage, model: MODEL });
    try {
      // LLM Call 1: モデルがツールを使用すると判断する場合があります。
      const llm1 = tracing.startLLM({ model: MODEL, providerName: 'openai' });
      let msg;
      try {
        const resp = await openaiClient.chat.completions.create({
          model: MODEL,
          messages: history,
          tools: [wikipediaToolSchema],
        });
        msg = resp.choices[0].message;
        llm1.output(msg.content ?? '');
        // record() は、使用量、料金計算の対象モデル、応答 ID を 1 回の呼び出しでまとめて設定します。
        llm1.record({
          usage: {
            inputTokens: resp.usage?.prompt_tokens,
            outputTokens: resp.usage?.completion_tokens,
            cacheReadInputTokens: resp.usage?.prompt_tokens_details?.cached_tokens,
          },
          responseId: resp.id,
          responseModel: resp.model,
        });
        history.push(msg);
      } finally {
        llm1.end();
      }

      // ツールがリクエストされなかった場合は、最初の LLM の応答がそのまま回答になります。
      if (!msg.tool_calls?.length) {
        return msg.content ?? null;
      }

      // リクエストされたツール呼び出しをそれぞれ実行します。
      for (const tc of msg.tool_calls) {
        if (tc.type !== 'function') continue;
        const tool = tracing.startTool({
          name: tc.function.name,
          args: tc.function.arguments,
          toolCallId: tc.id,
        });
        try {
          const { query } = JSON.parse(tc.function.arguments);
          tool.result = await wikipediaSearch(query);
          history.push({ role: 'tool', tool_call_id: tc.id, content: tool.result });
        } finally {
          tool.end();
        }
      }

      // LLM Call 2: 最終的な回答を生成します。
      const llm2 = tracing.startLLM({ model: MODEL, providerName: 'openai' });
      try {
        const resp = await openaiClient.chat.completions.create({
          model: MODEL,
          messages: history,
        });
        const msg2 = resp.choices[0].message;
        llm2.output(msg2.content ?? '');
        llm2.record({
          usage: {
            inputTokens: resp.usage?.prompt_tokens,
            outputTokens: resp.usage?.completion_tokens,
            cacheReadInputTokens: resp.usage?.prompt_tokens_details?.cached_tokens,
          },
          responseId: resp.id,
          responseModel: resp.model,
        });
        history.push({ role: 'assistant', content: msg2.content });
        return msg2.content ?? null;
      } finally {
        llm2.end();
      }
    } finally {
      turn.end();
    }
  }

  await tracing.init('[YOUR-TEAM]/[YOUR-PROJECT]');

  await tracing.runIsolated(async () => {
    const conversation = tracing.startConversation({ agentName: 'research-bot' });
    try {
      const history: any[] = [];
      for (const question of [
        'Who founded Anthropic?',
        'What is Claude (the AI assistant)?',
        'Summarize what we discussed in one sentence.',
      ]) {
        console.log(`USER: ${question}`);
        console.log(`AGENT: ${await runTurn(history, question)}\n`);
      }
    } finally {
      conversation.end();
    }
  });

  await tracing.shutdown();
  ```
</CodeGroup>

<h2 id="record-token-usage-and-cost">
  トークン使用量とコストを記録する
</h2>

各 `chat` スパンには、トークン使用量とモデル ID が含まれます。Agent Lens は使用量からトークン数をレンダリングし、使用量とモデル ID からコストを算出します。そのため、値が不完全な場合や価格を算出できない場合は、トレースの他の部分が正しく見えていても、トークンが `0 in / 0 out`、またはコストが `Cost -` と表示されます。`record(...)` を使用すると、これらのフィールド (`output_messages`、`response_id`、`reasoning` など) を 1 回の呼び出しでまとめて設定できます。適用されるのは、渡したフィールドのみです。

コストを表示するには、次の 2 点を正しく設定する必要があります。

* **完全な使用量。** `input_tokens` は、キャッシュされたトークンを含む入力の*合計*です。Agent Lens はキャッシュの読み取りと書き込みをそれぞれ個別の料金で計算し、入力の合計から差し引きます。そのため、`cache_read_input_tokens` と `cache_creation_input_tokens` は、これらを含めた合計の `input_tokens` と*あわせて*報告する必要があります。プロンプトキャッシュを備えたプロバイダー (Anthropic など) では、キャッシュされたトークンが入力の大半を占めることが多いため、これらを省略すると使用量とコストがほぼゼロとしてレンダリングされます。
* **価格を算出できるモデル ID。** 応答で返される具体的な ID (`resp.model`) を `response_model` として渡します。コストはモデルに基づくルックアップで算出されます。Agent Lens は `response_model` (プロバイダーが実際に使用したモデル) を優先し、これがない場合は `start_llm` に渡した `model` を使用します。`opus` や `sonnet` などのエイリアスでは価格を算出できず、`Cost -` と表示されます。

OpenAI はキャッシュされたトークンを `prompt_tokens` に含めてカウントするため、上記の例をそのまま適用できます。一方、Anthropic はキャッシュされたトークンを `input_tokens` とは*別に*報告するため、Agent Lens が価格計算に使用する合計にこれらを加算してください。

<CodeGroup>
  ```python lines Python theme={"system"}
  with tracing.start_llm(model=MODEL, provider_name="anthropic") as llm:
      resp = anthropic_client.messages.create(
          model=MODEL, max_tokens=1024, messages=history,
      )
      u = resp.usage
      llm.output(resp.content[0].text)
      llm.record(
          usage=tracing.Usage(
              input_tokens=u.input_tokens
              + u.cache_read_input_tokens
              + u.cache_creation_input_tokens,
              output_tokens=u.output_tokens,
              cache_read_input_tokens=u.cache_read_input_tokens,
              cache_creation_input_tokens=u.cache_creation_input_tokens,
          ),
          response_id=resp.id,
          response_model=resp.model,
      )
  ```

  ```typescript lines TypeScript theme={"system"}
  const u = resp.usage;
  llm.record({
    usage: {
      inputTokens:
        u.input_tokens + u.cache_read_input_tokens + u.cache_creation_input_tokens,
      outputTokens: u.output_tokens,
      cacheReadInputTokens: u.cache_read_input_tokens,
      cacheCreationInputTokens: u.cache_creation_input_tokens,
    },
    responseId: resp.id,
    responseModel: resp.model,
  });
  ```
</CodeGroup>

<h2 id="see-your-agent-traces-in-the-conversations-tab">
  Conversations タブでエージェントのトレースを確認する
</h2>

Agent Lens で project を開き、**Conversations** を選択します。次の内容が表示されます。

* `research-bot` の会話が 1 つあり、3 つのターンが含まれています。
* 各ターン (`invoke_agent`) の中に、2 つの `chat` スパンと 1 つの `execute_tool` スパンがネストされています。
* 各 `chat` には、トークン数、レイテンシー、モデル、メッセージのやり取り全体が表示されます。

会話を選択すると、**Thread** タブと **Spans** タブで入力、出力、ツールの引数、ツール結果を確認できます。

<h2 id="link-to-a-conversation-from-your-app">
  アプリから会話にリンクする
</h2>

独自の UI から Agent Lens 内の会話にディープリンクするには、その会話の ID が必要です。`start_conversation` / `startConversation` はこの ID を `conversation_id` / `conversationId` として公開しています。この ID をログしたり、独自のリクエスト ID と一緒に保存したりしておけば、後で **Conversations** タブから該当の会話を開くことができます。

<CodeGroup>
  ```python lines Python theme={"system"}
  with tracing.start_conversation(agent_name="research-bot") as conversation:
      # ... ターンを実行 ...
      print(f"Agent Lens conversation ID: {conversation.conversation_id}")
  ```

  ```typescript lines TypeScript theme={"system"}
  await tracing.runIsolated(async () => {
    const conversation = tracing.startConversation({ agentName: 'research-bot' });
    try {
      // ... ターンを実行 ...
      console.log(`Agent Lens conversation ID: ${conversation.conversationId}`);
    } finally {
      conversation.end();
    }
  });
  ```
</CodeGroup>

<h2 id="next-steps">
  次のステップ
</h2>

* [Agent Lens でエージェントをトレースする](/ja/products/agent-lens/tracing/instrument)方法と、Agent Lens SDK で利用できる機能やオプションについて確認してください。
* Agent Lens をエージェントと統合するその他の方法については、[エージェントインテグレーションを選択する](/ja/products/agent-lens/get-started/integrations)を参照してください。
