Tags are key–value pairs that can be attached to any span, trace, or session. They power the facet filters in the console so you can slice-and-dice your telemetry by user, plan, model, tenant, or anything else that matters to your business. For complete tagging API documentation, see the Python SDK Reference or TypeScript SDK Reference.

1. Tag once, inherit everywhere

When you add a tags dictionary to the first span you create, every child span automatically gets the same tags. That means you set them once and they flow down the entire call-stack.
import zeroeval as ze

@ze.span(
    name="handle_request",
    tags={
        "user_id": "42",          # who triggered the request
        "tenant": "acme-corp",    # multi-tenant identifier
        "plan": "enterprise"      # commercial plan
    }
)
def handle_request():
    authenticate()
    fetch_data()
    process()

    # Two nested child spans – they automatically inherit *all* the tags
    with ze.span(name="fetch_data"):
        ...

    with ze.span(name="process", tags={"stage": "post"}):
        ...

2. Tag a single span

If you want to tag only a single span (or override a tag inherited from a parent) simply provide the tags argument on that specific decorator or context manager.
import zeroeval as ze

@ze.span(name="top_level")
def top_level():
    # Child span with its own tags – *not* inherited by siblings
    with ze.span(name="db_call", tags={"table": "customers", "operation": "SELECT"}):
        query_database()

    # Another child span without tags – it has no knowledge of the db_call tags
    with ze.span(name="render"):
        render_template()
Under the hood these tags live only on that single span, they are not copied to siblings or parents.

3. Granular tagging (session, trace, or span)

You can add granular tags at the session, trace, or span level after they’ve been created:
import uuid
from langchain_core.messages import HumanMessage
import zeroeval as ze

DEMO_TAGS = {"example": "langgraph_tags_demo", "project": "zeroeval"}

SESSION_ID = str(uuid.uuid4())
SESSION_INFO = {"id": SESSION_ID, "name": "Tags Demo Session"}

with ze.span(
    name="demo.root_invoke",
    session=SESSION_INFO,
    tags={**DEMO_TAGS, "run": "invoke"},
):
    # 1️⃣ Tag the *current* span only
    current_span = ze.get_current_span()
    ze.set_tag(current_span, {"phase": "pre-run"})

    # 2️⃣ Tag the whole trace – root + all children (past *and* future)
    current_trace = ze.get_current_trace()
    ze.set_tag(current_trace, {"run_mode": "invoke"})

    # 3️⃣ Tag the entire session
    current_session = ze.get_current_session()
    ze.set_tag(current_session, {"env": "local"})

    result = app.invoke({"messages": [HumanMessage(content="hello")], "count": 0})