> ## 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.

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

> Weave SDK を使用してマルチターンエージェントをトレースします。会話、ターン、LLM Call、ツール呼び出しは project の Agents ビューにレンダリングされます。

export const AgentLensBanner = ({href}) => <Tip>
    <strong>This workflow is also available in CoreWeave Agent Lens.</strong> Agent Lens is the Forge experience built for tracing, monitoring, and analyzing AI agents, with automated insights into agent failures and user intents. It uses the same trace data as Weights & Biases Weave, so the traces you already send appear there with nothing to migrate.{' '}
    <a href={href || '/products/agent-lens'}>{href ? 'See how to do this in Agent Lens' : 'Learn about Agent Lens'}</a>.
  </Tip>;

<AgentLensBanner href="/ja/products/agent-lens/get-started/custom-agents" />

[Try in Colab](https://colab.research.google.com/github/wandb/docs/blob/main/weave/cookbooks/source/custom-agents-quickstart.ipynb) · [GitHub source](https://github.com/wandb/docs/blob/main/weave/cookbooks/source/custom-agents-quickstart.ipynb)

Weave SDK を使用すると、人気の SDK や custom harnesses で構築されたエージェントをトレースできます。このクイックスタートでは、カスタム構築のマルチターンエージェントに Weave を手動で統合して、OpenTelemetry スパンを emit および取得する方法を説明します。エージェント向け Weave の概念的な理解については、[Trace your agents](/ja/products/wandb/weave/guides/tracking/trace-agents) を参照してください。

SDK や harnesses (Claude Agent SDK や Codex など) と Weave を統合したい場合は、[エージェントインテグレーションを選択する](/ja/products/wandb/weave/agent-integration-quickstart) を参照してください。Weave は、迅速な統合のために、いくつかの agent-building SDKs および agent harnesses に autopatches します。

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

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

このガイドのコードでは、Wikipedia で情報を調べられる小規模な調査エージェントを構築します。3 つの質問 (3 ターン) を行い、回答を得るために Wikipedia を検索するタイミングを LLM で判断します。Weave はすべてのステップ (会話、各質問、各 AI 応答、各 Wikipedia ルックアップ) を記録するため、Weave Agents ビューで何が起きたかを確認できます。

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

* `weave.init()` を使用して、エージェントのトレース用に Weave を初期化します。
* `start_conversation` / `startConversation` と `start_turn` / `startTurn` を使用して、会話とターンを開始します。
* `start_llm` / `startLLM` で LLM Call をラップし、使用量を記録します。
* `start_tool` / `startTool` でツール実行をラップし、結果を記録します。
* トークン数とコストがレンダリングされるように、すべてのトークン使用量と料金を算出できるモデルを記録します。
* Agents ビューで、記録された会話、ターン、ツール呼び出しを確認します。

<h2 id="how-the-weave-sdk-works-with-agents">
  Weave SDK がエージェントでどのように動作するか
</h2>

Weave SDK には、エージェント向けの汎用 OTel インジェストシステムが含まれており、Weave はエージェントのコード内の任意の OTel スパンから情報を取得できます。ただし、Weights & Biases UI の Agents ビューでエージェントのトレースをレンダリングするには、Weave は以下のスパンに対して特別な処理が必要です。

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

Python では、4 つの関数すべてがコンテキストマネージャーとして機能します (`with weave.start_*(...) as obj:`)。終了時に、スパンと属性をフラッシュし、例外時も含めて終了します。TypeScript では、返された各オブジェクトで `.end()` を呼び出します。例外時のクリーンアップを保証するために `try { ... } finally { obj.end(); }` を使用してください。

その他の [GenAI semantic-convention attributes](https://opentelemetry.io/docs/specs/semconv/gen-ai/gen-ai-agent-spans/)、例えば `gen_ai.usage.*` や `gen_ai.agent.name` は追加のレンダリングを可能にしますが、オプションです。

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

* CoreWeave Forge アカウントと [APIキー](https://forge.coreweave.com/settings#apikeys)。
* OpenAI APIキー。
* Python 3.10 以上 (Python の例の場合) 。
* Node.js 18 以上 (TypeScript の例では組み込みの `fetch` が必要です) 。

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

以下のパッケージを開発環境にインストールしてください：

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

  ```bash TypeScript theme={"system"}
  npm install weave openai
  ```
</CodeGroup>

<h2 id="initialize-weave">
  Weave の初期化
</h2>

`weave.init()` は W\&B で認証を行い、**Agents** ビューにエージェント スパンを送信する OTel エクスポーターを設定します。project がチームに存在しない場合、Weave は初めて書き込むときに作成します。

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

  os.environ["WANDB_API_KEY"] = getpass.getpass("CoreWeave Forge APIキーを入力してください: ")
  os.environ["OPENAI_API_KEY"] = getpass.getpass("OpenAI APIキーを入力してください: ")

  TEAM = input("CoreWeave Forge チーム名を入力してください: ")
  PROJECT = input("Weights & Biases プロジェクト名を入力してください: ")

  import weave
  weave.init(f"{TEAM}/{PROJECT}")
  ```

  ```typescript lines highlight="4" TypeScript twoslash theme={"system"}
  // @noErrors
  // この project を実行する前に、環境に WANDB_API_KEY、OPENAI_API_KEY を設定してください
  import * as weave from 'weave';

  await weave.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": "weave-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 twoslash theme={"system"}
  // @noErrors
  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': 'weave-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>

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

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

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

<CodeGroup>
  ```python lines highlight="10,12,19,38,51,55,69" Python theme={"system"}
  import weave
  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 weave.start_turn(user_message=user_message, model=MODEL):
          # LLM Call 1: モデルがツールの使用を判断する場合があります。
          with weave.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=weave.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

          # 要求された各ツール呼び出しを実行します。
          for tc in msg.tool_calls:
              with weave.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 weave.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=weave.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

  with weave.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")
  ```

  ```typescript lines highlight="11,14,25,47,62,70,89" theme={"system"}
  // @noErrors
  import * as weave from 'weave';
  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 = weave.startTurn({ model: MODEL });
    try {
      // LLM Call 1: モデルがツールの使用を判断する場合があります。
      const llm1 = weave.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 = weave.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 = weave.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();
    }
  }

  const conversation = weave.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();
  }
  ```
</CodeGroup>

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

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

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

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

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

<CodeGroup>
  ```python lines Python theme={"system"}
  with weave.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=weave.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 twoslash theme={"system"}
  // @noErrors
  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-agents-view">
  エージェントのトレースを Agents ビューで確認する
</h2>

`weave.init()` が実行されると、project へのリンクが出力され、以下を確認できます:

* `research-bot` の **Agents** タブに表示される行。
* 3 つのターンからなる 1 つの会話。
* 各ターン (`invoke_agent`) に含まれる 2 つの `chat` スパンと、内部にネストされた `execute_tool` スパン。
* 各 `chat` のトークン数、レイテンシー、モデル、および完全なメッセージ交換。

任意のターンをクリックして、入力、出力、ツールの引数、ツール結果を検査します。

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

自分の UI から Weave Agents ビュー内の会話へのディープリンクを作成するには、entity、project、および会話 ID から URL を構築します。`weave.init()` は `entity` と `project` を保持するクライアントを返し、`start_conversation` は `conversation_id` を公開します。

<CodeGroup>
  ```python lines Python theme={"system"}
  from weave.trace.urls import agent_conversation_path

  client = weave.init(f"{TEAM}/{PROJECT}")

  with weave.start_conversation(agent_name="research-bot") as conversation:
      # ... ターンを run ...
      url = agent_conversation_path(
          client.entity, client.project, conversation.conversation_id
      )
      print(f"View this conversation at {url}")
  ```

  ```plaintext TypeScript lines theme={"system"}
  この機能は TypeScript では利用できません。
  ```
</CodeGroup>

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

* [Weave でエージェントをトレースする](/ja/products/wandb/weave/guides/tracking/trace-agents)方法と、Weave SDK で利用できる機能やオプションについて確認してください。
* Weave をエージェントに統合するその他の方法については、[エージェントインテグレーションを選択する](/ja/products/wandb/weave/agent-integration-quickstart)を参照してください。
