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

# Atomic Agents

> Trace Atomic Agents agents and tools in Arize AX with the framework's built-in OpenInference instrumentor, with LLM calls nested under each agent run.

[Atomic Agents](https://github.com/eigenwise/atomic-agents) is a lightweight, modular Python framework for building agentic AI applications from small, single-purpose components. Every agent is a typed function: a Pydantic input schema in, a Pydantic output schema out, with the LLM call handled by [Instructor](https://python.useinstructor.com/).

Atomic Agents ships its own OpenInference instrumentor, `AtomicAgentsInstrumentor`. It emits an agent span for every `AtomicAgent` run and a tool span for every `BaseTool` run, including tools generated from MCP servers. LLM spans come from the provider instrumentor you already use and nest under the agent span.

## Prerequisites

* Python 3.10+
* An Arize AX account ([sign up](https://arize.com/sign-up/))
* An `OPENAI_API_KEY` from the [OpenAI Platform](https://platform.openai.com/api-keys)

## Launch Arize AX

1. Sign in to your [Arize AX account](https://app.arize.com/).
2. From **Space Settings**, copy your **Space ID** and **API Key**. You will set them as `ARIZE_SPACE_ID` and `ARIZE_API_KEY` below.

## Install

```bash theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
pip install "atomic-agents[observability]" arize-otel openinference-instrumentation-openai openai sympy
```

<Note>
  The `observability` extra installs the OpenInference dependencies the instrumentor needs. Install exactly one LLM instrumentor, for the provider SDK your Instructor client wraps (`openinference-instrumentation-openai`, `openinference-instrumentation-anthropic`, `openinference-instrumentation-litellm`, and so on). Adding `openinference-instrumentation-instructor` as well produces two layers of LLM spans for every call.
</Note>

## Configure credentials

```bash theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
export ARIZE_SPACE_ID="<your-space-id>"
export ARIZE_API_KEY="<your-api-key>"
export ARIZE_PROJECT_NAME="atomic-agents-tracing-example"
export OPENAI_API_KEY="<your-openai-api-key>"
```

## Setup tracing

```python theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
# instrumentation.py
import os

from arize.otel import register
from atomic_agents.observability import AtomicAgentsInstrumentor
from openinference.instrumentation.openai import OpenAIInstrumentor

tracer_provider = register(
    space_id=os.environ["ARIZE_SPACE_ID"],
    api_key=os.environ["ARIZE_API_KEY"],
    project_name=os.environ["ARIZE_PROJECT_NAME"],
)

AtomicAgentsInstrumentor().instrument(tracer_provider=tracer_provider)
OpenAIInstrumentor().instrument(tracer_provider=tracer_provider)
print("Arize AX tracing initialized for Atomic Agents.")
```

`AtomicAgentsInstrumentor().instrument()` also accepts an OpenInference `TraceConfig` through `config=` for masking inputs and outputs.

## Run Atomic Agents

Atomic Agents has no built-in tool-calling loop: your code calls an agent, then a tool, then another agent. Each call would otherwise be a root span of its own, so the example wraps the turn in a chain span to get one trace per question:

```python theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
# example.py

# Importing instrumentation first ensures tracing is set up
# before `atomic_agents` is imported.
from instrumentation import tracer_provider

import instructor
import openai
from atomic_agents import (
    AgentConfig,
    AtomicAgent,
    BaseIOSchema,
    BaseTool,
    BasicChatInputSchema,
    BasicChatOutputSchema,
)
from opentelemetry.trace import Status, StatusCode
from pydantic import Field
from sympy import sympify

tracer = tracer_provider.get_tracer(__name__)
client = instructor.from_openai(openai.OpenAI())


class CalculationPlanSchema(BaseIOSchema):
    """An arithmetic expression that answers the user's question."""

    expression: str = Field(
        ..., description="A Python arithmetic expression, for example '12 * 7 + 3'."
    )


class CalculatorInputSchema(BaseIOSchema):
    """Expression to evaluate."""

    expression: str = Field(...)


class CalculatorOutputSchema(BaseIOSchema):
    """Result of the evaluation."""

    result: str = Field(...)


class CalculatorTool(BaseTool[CalculatorInputSchema, CalculatorOutputSchema]):
    def run(self, params: CalculatorInputSchema) -> CalculatorOutputSchema:
        return CalculatorOutputSchema(result=str(sympify(params.expression)))


class CalculationResultSchema(BaseIOSchema):
    """The question together with the evaluated expression."""

    question: str = Field(...)
    expression: str = Field(...)
    result: str = Field(...)


# Instructor returns structured output through function tools, which
# gpt-6-luna supports on Chat Completions only with reasoning turned off.
config = AgentConfig(
    client=client,
    model="gpt-6-luna",
    model_api_parameters={"reasoning_effort": "none"},
)
planner = AtomicAgent[BasicChatInputSchema, CalculationPlanSchema](config)
answerer = AtomicAgent[CalculationResultSchema, BasicChatOutputSchema](config)
calculator = CalculatorTool()

question = (
    "If 3 friends split a 47 dollar bill and add a 15 percent tip, "
    "how much does each pay?"
)

with tracer.start_as_current_span(
    "answer_question", openinference_span_kind="chain"
) as turn:
    turn.set_input(question)
    plan = planner.run(BasicChatInputSchema(chat_message=question))
    calculation = calculator.run(CalculatorInputSchema(expression=plan.expression))
    answer = answerer.run(
        CalculationResultSchema(
            question=question,
            expression=plan.expression,
            result=calculation.result,
        )
    )
    turn.set_output(answer.chat_message)
    turn.set_status(Status(StatusCode.OK))

print(answer.chat_message)
```

### Expected output

```text wrap theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
Arize AX tracing initialized for Atomic Agents.
The total with a 15% tip is $54.05. Split three ways, that's about $18.02 per person.
```

## Verify in Arize AX

1. Open your Arize AX space and select project **`atomic-agents-tracing-example`**.
2. Open the newest trace. The chain span wraps both agent runs and the tool call, with each agent's LLM call nested under it:

```text wrap theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
answer_question                                                    (chain span)
  AtomicAgent[BasicChatInputSchema, CalculationPlanSchema].run     (agent span)
    ChatCompletion                                                 (LLM span)
  CalculatorTool                                                   (tool span)
  AtomicAgent[CalculationResultSchema, BasicChatOutputSchema].run  (agent span)
    ChatCompletion                                                 (LLM span)
```

### Check from the skill, CLI, or SDK

Confirm spans are actually reaching your Arize AX project. Use whichever fits your workflow — the skill and CLI work for any framework; the SDK check is shown for each language.

<Tabs>
  <Tab title="Arize skill (agent)">
    Install the [Arize Skills](https://github.com/Arize-ai/arize-skills) plugin and let your coding agent check for you:

    ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
    npx skills add Arize-ai/arize-skills
    ```

    Then prompt your agent:

    > Use the `arize-trace` skill to export and analyze recent traces from my project. Confirm spans are arriving, and summarize any errors or latency issues.
  </Tab>

  <Tab title="AX CLI">
    Export recent spans for your project — any rows mean traces are landing:

    ```bash theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
    ax spans export "$ARIZE_PROJECT_NAME" --space "$ARIZE_SPACE_ID" \
      --limit 5 --stdout | jq 'length'
    ```

    A non-zero count confirms spans reached Arize AX. Run `ax auth login` first if you have not authenticated. See the [`ax spans` reference](/docs/api-clients/cli/spans).
  </Tab>

  <Tab title="SDK">
    Query the project's spans and check that at least one came back.

    <CodeGroup>
      ```python Python theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
      import os
      from arize import ArizeClient

      client = ArizeClient(api_key=os.environ["ARIZE_API_KEY"])
      resp = client.spans.list(
          project=os.environ["ARIZE_PROJECT_NAME"],
          space=os.environ["ARIZE_SPACE_ID"],
          limit=5,
      )
      count = len(resp.spans)
      print(
          f"{count} span(s) found" if count else "No spans yet — recheck setup"
      )
      ```

      ```typescript TypeScript theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
      // Reads ARIZE_API_KEY from the environment.
      import { listSpans } from "@arizeai/ax-client";

      const { data: spans } = await listSpans({
        project: process.env.ARIZE_PROJECT_NAME!,
        space: process.env.ARIZE_SPACE_ID!,
        limit: 5,
      });
      const count = spans.length;
      console.log(
        count ? `${count} span(s) found` : "No spans yet — recheck setup",
      );
      ```

      ```go Go theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
      client, err := arize.NewClient(
          arize.Config{APIKey: os.Getenv("ARIZE_API_KEY")},
      )
      if err != nil {
          log.Fatal(err)
      }
      resp, err := client.Spans.List(ctx, spans.ListRequest{
          Project: os.Getenv("ARIZE_PROJECT_NAME"),
          Space:   os.Getenv("ARIZE_SPACE_ID"),
          Limit:   5,
      })
      if err != nil {
          log.Fatal(err)
      }
      fmt.Printf("%d span(s) found\n", len(resp.Spans))
      ```
    </CodeGroup>

    SDK span references: [Python](/docs/api-clients/python/version-8/client-resources/spans) · [TypeScript](/docs/api-clients/typescript/version-1/client-resources/spans) · [Go](/docs/api-clients/go/version-2/client-resources/spans).
  </Tab>
</Tabs>

## What Arize AX captures

* Agent spans named `AtomicAgent[InputSchema, OutputSchema].run`, carrying `agent.name`, the input and output schemas as JSON, and `atomic_agents.*` attributes for the model, the Instructor mode, the schema names, the history size entering the turn, the registered context providers, and a hash of the static system prompt sections
* Agent spans for sync, async, and streaming runs. Streaming runs (`run_stream`, `run_async_stream`) record the final response, or the last partial response plus `atomic_agents.stream.closed_early` when the consumer stops early
* Tool spans carrying `tool.name`, `tool.description`, `tool.parameters` (the input JSON schema), and the tool's input and output
* LLM spans from the provider instrumentor, with prompts, responses, and token counts, nested under the agent span that made the call

Wrap calls in `using_session`, `using_user`, or `using_metadata` from `openinference.instrumentation` to group spans by conversation or user. To redact agent and tool payloads, set `OPENINFERENCE_HIDE_INPUTS=true` and `OPENINFERENCE_HIDE_OUTPUTS=true`.

## Troubleshooting

* **No traces in Arize AX.** Confirm `ARIZE_SPACE_ID` and `ARIZE_API_KEY` are set in the same shell that runs `example.py`. Enable OpenTelemetry debug logs with `export OTEL_LOG_LEVEL=debug` and re-run.
* **`ModuleNotFoundError` on `atomic_agents.observability`.** Install the extra: `pip install "atomic-agents[observability]"`.
* **Every LLM call appears twice.** Both a provider instrumentor and `openinference-instrumentation-instructor` are active. Keep only the provider instrumentor.
* **Agent, tool, and LLM spans land in separate traces.** Your code calls each component directly, so each call starts its own trace. Wrap the unit of work in a parent span, as in [Run Atomic Agents](#run-atomic-agents).
* **`400` from OpenAI mentioning `reasoning_effort`.** `gpt-6-luna` accepts function tools on Chat Completions only with `reasoning_effort` set to `"none"`. Keep `model_api_parameters={"reasoning_effort": "none"}` in `AgentConfig`, or swap in a model your key can call.

## Resources

<CardGroup>
  <Card icon="book-open" href="https://eigenwise.github.io/atomic-agents/guides/observability.html" title="Atomic Agents observability guide" horizontal />

  <Card icon="github" href="https://github.com/eigenwise/atomic-agents" title="Atomic Agents GitHub" horizontal />

  <Card icon="terminal" href="https://github.com/Arize-ai/openinference/tree/main/python/instrumentation/openinference-instrumentation-openai" title="OpenInference OpenAI Instrumentor" horizontal />
</CardGroup>
