> ## Documentation Index
> Fetch the complete documentation index at: https://arizeai-433a7140.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Add an agent span

> Add manual instrumentation to record a complete agent request, its result, and the model and tool calls behind it.

In [Capture your first agent run](https://arizeai-433a7140.mintlify.site/docs/phoenix/learn/capture-first-run.md), you enabled auto instrumentation to record model and tool calls. Now you'll add a span of your own so you can choose which work it covers and what it records.

This is [manual instrumentation](https://arizeai-433a7140.mintlify.site/docs/phoenix/tracing/how-to-tracing/setup-tracing/instrument.md#2-as-a-with-clause-to-trace-specific-code-blocks). We'll use an **agent span** to keep a complete request, its result, and the calls behind it together, making the interaction easier to review and evaluate.

We'll practice in the app you're already running by adding a **Wonder Toys** span above LangGraph's existing steps.

![Before: LangGraph contains agent and tools steps with model and tool calls. After: a new Wonder Toys AGENT span holds the request and answer above that same tree.](https://storage.googleapis.com/arize-phoenix-assets/assets/images/phoenix-academy/context-agent-spans-light-38295c439826.svg)

![Before: LangGraph contains agent and tools steps with model and tool calls. After: a new Wonder Toys AGENT span holds the request and answer above that same tree.](https://storage.googleapis.com/arize-phoenix-assets/assets/images/phoenix-academy/context-agent-spans-light-mobile-2292fbd1b841.svg)

Example LangGraph trees before and after adding the Wonder Toys span. Other framework spans and repeated calls are omitted.

## Before you start

To follow the exercise, continue with your setup from the previous lesson:

* Keep the same Phoenix instance running.
* Work in the same `no-observability/langchain-py` folder.
* Use the `_tracer_provider` you created in `backend/tracing.py`.

## Step 1: Add the agent span

Before adding a span, decide:

* **What to trace:** The work you want to review together. Here, that's one complete agent request.
* **What to record:** The user's request as input and the complete result as output.
* **When to finish:** After the agent's work is complete, including any streamed reply.

We'll use a [**context manager**](https://arizeai-433a7140.mintlify.site/docs/phoenix/tracing/how-to-tracing/setup-tracing/instrument.md#2-as-a-with-clause-to-trace-specific-code-blocks), a `with` block, to mark where that work starts and ends. Calls recorded while this span is active appear underneath it.

In our practice app, the reply arrives in pieces, so we'll collect the text before recording the output. The short example below shows the tracing code used in the full function:

```python title="Tracing pattern used in this lesson" wrap theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
with _tracer.start_as_current_span(
    "Wonder Toys", openinference_span_kind="agent"
) as root_span:
    root_span.set_input(question)
    # Run the agent here and append its reply to response_parts.
    root_span.set_output("".join(response_parts))
```

Here, `question` holds the user's latest message and `response_parts` holds the streamed text. In the full function, `agent.astream_events(...)` runs inside the block, so its automatically recorded **LangGraph** span appears beneath our new span.

Add the code yourself or ask a coding assistant to make the same edits:

> [tab] Add the code yourself
>
> Now let's add the span to your app. Make these edits in your local `backend/agent.py` and `backend/main.py` files:
>
> **1. Reuse your tracing setup**
>
> Open [`backend/agent.py`](https://github.com/Arize-ai/project-rosetta-stone/blob/main/no-observability/langchain-py/backend/agent.py) and add the code below after the existing imports. We'll use `_tracer` to create the span through your existing connection to Phoenix:
>
> ```python theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
> from backend.tracing import _tracer_provider
> from opentelemetry.trace import StatusCode
>
> _tracer = _tracer_provider.get_tracer(__name__)
> ```
>
> **2. Add the span around the agent's work**
>
> In the same file, replace the existing `stream_agent` function with the complete function below. It keeps the chat's streaming code and adds the span around it. The highlighted lines record the request and streamed reply.
>
> > [accordion] Copy the complete stream\_agent function
> >
> > ```python title="stream_agent" highlight={15,16,17,18,44,48,54,55} theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
> > async def stream_agent(messages: list[dict], user_id: str) -> AsyncIterator[str]:
> >     agent = get_agent()
> >     lc_messages = _lc_messages(messages, user_id)
> >
> >     had_text_before = False
> >     in_tool_call = False
> >
> >     question = next(
> >         (message.get("content", "") for message in reversed(messages)
> >          if message.get("role") == "user"),
> >         "",
> >     )
> >     response_parts = []
> >
> >     with _tracer.start_as_current_span(
> >         "Wonder Toys", openinference_span_kind="agent"
> >     ) as root_span:
> >         root_span.set_input(question)
> >         async for event in agent.astream_events(
> >             {"messages": lc_messages},
> >             version="v2",
> >             config={"recursion_limit": 25},
> >         ):
> >             kind = event["event"]
> >
> >             if kind == "on_chat_model_stream":
> >                 chunk = event["data"]["chunk"]
> >                 # Check for tool call chunks
> >                 if hasattr(chunk, "tool_call_chunks") and chunk.tool_call_chunks:
> >                     in_tool_call = True
> >                     continue
> >
> >                 # Check for text content
> >                 text = ""
> >                 if isinstance(chunk.content, str):
> >                     text = chunk.content
> >                 elif isinstance(chunk.content, list):
> >                     for block in chunk.content:
> >                         if isinstance(block, dict) and block.get("type") == "text":
> >                             text += block.get("text", "")
> >
> >                 if text:
> >                     if in_tool_call and had_text_before:
> >                         response_parts.append("\n\n")
> >                         yield f"data: {json.dumps({'text': chr(10) + chr(10)})}\n\n"
> >                     in_tool_call = False
> >                     had_text_before = True
> >                     response_parts.append(text)
> >                     yield f"data: {json.dumps({'text': text})}\n\n"
> >
> >             elif kind == "on_tool_start":
> >                 in_tool_call = True
> >
> >         root_span.set_output("".join(response_parts))
> >         root_span.set_status(StatusCode.OK)
> >
> >     yield "data: [DONE]\n\n"
> > ```
>
> **3. Adjust the practice app's trace layout**
>
> FastAPI adds an [HTTP request span](https://fastapi.tiangolo.com/advanced/opentelemetry/) above the agent span. We'll turn off that tracing in the practice app so **Wonder Toys** is the top-level span used for labels and test cases in later lessons.
>
> Open [`backend/main.py`](https://github.com/Arize-ai/project-rosetta-stone/blob/main/no-observability/langchain-py/backend/main.py) and replace `app = FastAPI(...)` with:
>
> ```python theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
> app = FastAPI(
>     title="Wonder Toys LangChain Backend",
>     telemetry={"tracing": False},
> )
> ```
>
> Other apps can keep HTTP tracing and leave the agent span underneath it.

> [tab] Use a coding assistant
>
> If your assistant doesn't have the [Phoenix tracing skill](https://arizeai-433a7140.mintlify.site/docs/phoenix/integrations/developer-tools/skills.md), follow the [coding-agent setup guide](https://arizeai-433a7140.mintlify.site/docs/phoenix/integrations/developer-tools/coding-agents.md) to install it.
>
> Open `no-observability/langchain-py` in your assistant and paste this prompt:
>
> ```text wrap theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
> Use the Phoenix tracing skill to add an AGENT span around a complete request in this app.
> First inspect backend/tracing.py, backend/agent.py, and backend/main.py, then propose the edits before changing code.
>
> After I approve the plan:
> - Reuse _tracer_provider from backend/tracing.py.
> - Use start_as_current_span in a with block around the stream_agent event loop, naming the span "Wonder Toys" with kind AGENT.
> - Use set_input for the latest shopper message's text and set_output for the complete streamed text sent to the chat.
> - Keep the span open until streaming finishes. Set StatusCode.OK after successful completion and let exceptions record ERROR.
> - Preserve the chat's streaming behavior, model, prompt, and tools.
> - For this practice app, set telemetry={"tracing": False} on the existing FastAPI app so Wonder Toys is the top-level span used in later lessons.
> - Show me the changes.
> ```
>
> Review the changes, then follow Steps 2 and 3 to send a request and check the new trace in Phoenix.

## Step 2: Send a request

Let's send a request to check that the span records the work and result we intended.

* **Restart the app.** Stop it in its terminal, then run this from `no-observability/langchain-py` to load your changes:

  ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
  UV_PYTHON=3.11 npm run dev
  ```

* **Send a request.** When the app is ready, you'll see its address next to **Local** in your terminal. Open that address in your browser and send:

  ```text wrap theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
  Find dinosaur toys for a 6-year-old under $25. Recommend at most 3 options.
  ```

Wait for the reply to finish, then return to Phoenix.

## Step 3: Inspect the result

Your new trace should now have a **Wonder Toys** span above **LangGraph**. In Phoenix:

* Open **wonder-toys → Traces** and select the latest **Wonder Toys** request.
* Select the **Wonder Toys** span. Its kind should be **AGENT** and its status **OK** after the reply finishes.
* Under **Info**, compare **Input** with the shopper's message and **Output** with the answer in the chat.
* Expand the tree to find the model and tool spans underneath it.

![Phoenix trace with the Wonder Toys agent span selected on the left and the shopper's request and answer visible under Input and Output on the right.](https://storage.googleapis.com/arize-phoenix-assets/assets/images/phoenix-academy/wonder-toys-first-trace.png)

Select the Wonder Toys agent span to read the request and answer together under Info → Input and Output.

You've completed this lesson when the **Wonder Toys** span shows the request and answer, with the recorded calls beneath it. Status **OK** means execution completed; it doesn't tell you whether the answer was correct.

For your own agent, put the span around the work you want to inspect, record its input and complete result, and check the recorded calls in Phoenix.

## Next: Understand how the agent reached its answer

In [Read model, retrieval, and tool steps](https://arizeai-433a7140.mintlify.site/docs/phoenix/learn/read-agent-run.md), we'll follow the calls beneath your new span and compare the product search results with the agent's recommendation.
