Skip to content

Define AI span primitive contract for FastAPI AI extensions #112

Description

@ulises-jeremias

Problem

fastapi-mlflow-tracing (#81, in flight) will ship generic configure_mlflow_tracing(app) + reusable _maybe_start_span(name, kind) / set_attribute(k, v) primitives, intentionally without AI-specific wrappers. The FastAPI AI extensions under #73 (fastapi-ai-chat #77, fastapi-rag-pgvector #78, fastapi-langgraph-chat #79, fastapi-mcp-client #80, fastapi-ai-guardrails #82) are expected to call those primitives to emit LLM/AI spans, but there is no shared contract for what attributes those spans must carry or how the AI extensions connect to the base tracing layer.

Without this contract:

  • every AI extension reinvents its own span schema — fastapi-ai-chat logs {tokens, model}, fastapi-langgraph-chat logs {tool_name, cost}, breaking cross-extension observability;
  • fastapi-ai-guardrails (Add fastapi-ai-guardrails extension #82) cannot express "guardrail blocked this LLM call" in a way the parent LLM span recognizes;
  • test assertions on tracing become extension-specific and non-portable.

Solution

Add an "AI span primitive contract" section to docs/AI_ML_AUTHORING.md (the taxonomy doc landed by #72 / #104) describing:

  1. Span kinds the AI extensions must use:

  2. Required attributes for llm_inference (Type, semantics, unit):

    llm.provider        : str            # "openai" | "anthropic" | "ollama" | ...
    llm.model           : str            # exact model id, e.g. "gpt-4o-mini"
    llm.input_tokens    : int            # token count in
    llm.output_tokens   : int            # token count out
    llm.latency_ms      : float          # wall-clock from request to last chunk
    llm.error           : str | None     # exception type if failed, None on success
    llm.stream          : bool           # true if streaming response
    # optional but recommended:
    llm.temperature     : float
    llm.tool_name       : str | None     # set when the LLM call resolved to a tool
    
  3. Span shape:

    • Each kind opens with start_span(kind, name="__main__") from the base helper (Rahuls primitives in Add fastapi-mlflow-tracing extension #81) and closes with .end(), recording latency automatically.
    • Guardrail rejections set llm.error = "guardrail_blocked" and guardrail.reason on the same llm_inference span, not a separate one — the span tree stays linear.
  4. Privacy:

    • Never log llm.input_text / llm.output_text / raw messages by default; add an explicit LLM_TRACE_PAYLOAD=true env opt-in (off in CI, documented in MLFLOW_TRACING_GUIDE.md).
    • PII redaction surface stays in fastapi-ai-guardrails (Add fastapi-ai-guardrails extension #82), not here.

Deliverables

Acceptance criteria

Parent

Part of #71 → child of #73 (FastAPI AI extensions track). Depends on #81 for the primitive API surface.

Non-Goals

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions