AgentOpsAgentOps 0.4.21 · Python 3.10+
0%
1
Curious builder0 XP earned · 300 to level 2
0 daysFinish a lesson to begin
Badge collection0 of 6 unlocked
11 small wins to finish your path

Traced support desk with privacy tests

The final project combines a traced support desk, redaction before any decorated code, and tests for the trace shape and that no card number is exported.

Exampledesk.py
import re

import agentops
from agentops.sdk.decorators import agent, operation, tool

ORDERS = {"A-1001": "shipped on 12 March", "A-1002": "waiting for stock"}
CARD = re.compile(r"\b\d(?:[ -]?\d){12,15}\b")


def redact(text):
    return CARD.sub("[card removed]", text)


@tool(cost=0.002)
def lookup_order(order_id):
    return ORDERS.get(order_id, "no such order")


@agent(name="desk")
class Desk:
    @operation
    def answer(self, clean_ticket):
        order = re.search(r"A-\d{4}", clean_ticket)
        if not order:
            return "Could you send your order number?"
        return f"Order {order.group()}: {lookup_order(order.group())}"


def handle(ticket):
    clean = redact(ticket)  # before any decorated function sees the ticket
    with agentops.start_trace("support-ticket", tags=["desk"]):
        return Desk().answer(clean)
  • redact runs first, undecorated, so the raw ticket never reaches a span (lessons 4 and 8).
  • The trace is started by hand around the agent, with a tag (lesson 6).
  • lookup_order is a tool with a cost (lesson 3).
Exampleapp.py
import agentops

import local_collector
from desk import handle

url = local_collector.start()
agentops.init(endpoint=url, exporter_endpoint=f"{url}/v1/traces", auto_start_session=False, log_level="ERROR")

for ticket in ["Where is A-1001?", "Card 4111 1111 1111 1111 charged twice on A-1002", "Hello?"]:
    print(handle(ticket))

local_collector.flush()
local_collector.tree()
Example
python app.py

Three tickets, three traces. The ticket without an order number never called the tool, so its trace has no tool span.

Three tickets through the traced desk
support-ticket.sessionorder numberticketmay hold a cardredact(ticket)not decorateddesk.agent@agent on the Desk classanswer.task@operationlookup_order.tool@tool, with a costlocal_collectorthe spans, as a tree
Hover or tap a piece to see what it is and which lesson built it.
Send a ticket

Testing the trace and the card number

Exampletest_desk.py
import agentops
import pytest

import local_collector
from desk import handle


@pytest.fixture(scope="module")
def collector():
    url = local_collector.start()
    agentops.init(endpoint=url, exporter_endpoint=f"{url}/v1/traces", auto_start_session=False, log_level="ERROR")
    return local_collector


def spans_for(collector, ticket):
    collector.SPANS.clear()
    handle(ticket)
    collector.flush()
    return collector.SPANS


def test_trace_has_the_expected_shape(collector):
    names = [span["name"] for span in spans_for(collector, "Where is A-1001?")]
    assert names == ["lookup_order.tool", "answer.task", "desk.agent", "support-ticket.session"]


def test_card_numbers_never_leave_the_process(collector):
    spans = spans_for(collector, "Card 4111 1111 1111 1111 charged twice on A-1002")
    assert spans
    assert not any("4111" in str(span["attributes"]) for span in spans)
Example
pytest -q -p no:warnings

The first test pins the trace's structure, so a refactor that drops a decorator is noticed. The second searches every exported attribute for the card number. Both run against the local collector, so they run in CI with no key and no network.

With the hosted service

In app.py, replace the collector and endpoints with agentops.init(api_key=...). Keep the tests on the local collector: they check what your code sends, which does not depend on where it goes.

Where AgentOps goes further

TopicWhat it is for
Framework integrationsAutomatic spans for CrewAI, LangGraph, OpenAI Agents SDK and others.
Other providersAnthropic, Gemini, LiteLLM and more, instrumented the same way.
track_endpointTracing Flask request handlers.
TypeScript SDKAgentOps for Node.js agents.
Public API and MCP serverReading traces back from the service.

Every expert started right here.