Gradio Integration with Future AGI for Chat App Tracing

Trace a Gradio chat app in Future AGI. There is no Gradio package: traceAI-openai traces the OpenAI client your chat function calls.

Trace the OpenAI-compatible client used by your Gradio chat function with traceAI-openai, then inspect each turn in Future AGI. Gradio UI events are not traced by this recipe. See the complete example app.

1. Installation

pip install gradio openai fi-instrumentation-otel traceAI-openai

Gradio requires Python 3.10 or later. fi-instrumentation-otel and traceAI-openai require Python below 3.14.

2. Set Environment Variables

export FI_API_KEY="your-futureagi-api-key"        # sent as X-Api-Key
export FI_SECRET_KEY="your-futureagi-secret-key"  # sent as X-Secret-Key
export FI_PROJECT_NAME="my-chatbot"
export OPENAI_API_KEY="your-model-key"            # the key of your model host
# optional: OPENAI_BASE_URL for any OpenAI-compatible host, OPENAI_MODEL

3. Register, instrument, and chat

import hashlib, hmac, os, secrets
from contextlib import nullcontext

import gradio as gr
from fi_instrumentation import TraceConfig, register, using_session
from fi_instrumentation.fi_types import ProjectType
from openai import OpenAI
from traceai_openai import OpenAIInstrumentor

MODEL = os.environ.get("OPENAI_MODEL", "gpt-4o-mini")


def init_tracing(trace_content: bool = False):
    provider = register(
        project_name=os.environ["FI_PROJECT_NAME"],
        project_type=ProjectType.OBSERVE,
        verbose=False,
    )
    config = TraceConfig() if trace_content else TraceConfig(hide_inputs=True, hide_outputs=True)
    OpenAIInstrumentor().instrument(tracer_provider=provider, config=config)
    return provider


client = OpenAI()  # reads OPENAI_API_KEY and OPENAI_BASE_URL

_SESSION_KEY = globals().get("_SESSION_KEY") or secrets.token_bytes(32)


def _session_id(request):
    if request is None or not request.session_hash:
        return None
    return hmac.new(_SESSION_KEY, request.session_hash.encode(), hashlib.sha256).hexdigest()[:32]


def _text(content):  # Gradio 6 passes history content as a list of parts
    if isinstance(content, str):
        return content
    return "".join(p["text"] for p in content if p.get("type") == "text")


def predict(message, history, request: gr.Request = None):
    messages = [{"role": m["role"], "content": _text(m["content"])} for m in history]
    messages.append({"role": "user", "content": message})
    session_id = _session_id(request)
    with using_session(session_id) if session_id else nullcontext():
        response = client.chat.completions.create(model=MODEL, messages=messages)
    return response.choices[0].message.content or ""


demo = gr.ChatInterface(predict, analytics_enabled=False)

if __name__ == "__main__":
    init_tracing()
    demo.launch()

Save the example as app.py, run python app.py, and send a message. Open the my-chatbot project in Observe to see the LLM span for each turn. The complete example is src/app.py in the example folder.

What is captured

One span per chat turn, from traceAI-openai:

FieldValue
SpanChatCompletion, span kind llm. Each turn is its own trace (no parent)
Modelthe model the response names, for example gpt-6-luna
Tokensprompt, completion and total, as the model host reports them
Sessionsession.id and gen_ai.conversation.id, the same for every turn of one browser session
Errora failed model call is one ChatCompletion span with status ERROR

Not captured: Gradio’s own events, queue, analytics and Spaces hosting, and gradio[mcp]. Streaming (stream=True) is the OpenAI instrumentor’s existing behavior and is not covered by this recipe.

Privacy

Content is off by default in this recipe: input.value and output.value are stored as __REDACTED__.

To turn content on, call init_tracing(trace_content=True). The span then carries the whole conversation your function sends (every history message and the new one) and the answer, on every turn. With content on, traceAI’s FI_HIDE_INPUTS and FI_HIDE_OUTPUTS environment variables apply again.

The session id is not Gradio’s session hash. A Gradio session hash is also the key to that session’s live state in the running app, so the recipe exports a keyed HMAC-SHA256 of it (32 hex characters, key random per process). Ids change after a process restart.

Not covered by the content switch: when the model call raises, the span records the error type and message, and for an HTTP error that includes the error body the model host returned. A host that echoes the prompt in its error body would put it in the span.

No Future AGI key, secret or model key is exported. The recipe never prints the prompt.

Troubleshooting

  • No span. init_tracing() did not run. In the example it runs under if __name__ == "__main__":. If you import demo from another module (for example to mount it in FastAPI), call init_tracing() there.
  • Turns are not grouped. predict ran without a gr.Request, so no session id was set.
  • Generator chat functions. Generator callbacks are outside this synchronous recipe. Gradio can run generator steps in separate context copies; do not assume a span opened around a generator remains its parent.
  • Reload mode (gradio app.py). Gradio re-runs the file in the same process; the first instrumentation stays in force. Changes to the tracing setup need a restart. You will see Attempting to instrument while already instrumented once at startup and once per save.
  • Nothing exported after kill. register() flushes on normal exit, SIGINT and SIGTERM. SIGKILL skips the flush.

Limitations

  • Supported scope is synchronous, non-streaming ChatInterface callbacks. Async callbacks, generator callbacks, streaming, hosted Spaces, and Gradio MCP operation are outside this recipe’s supported scope.

How this differs from other integrations

  • OpenAI documents the instrumentor itself. This page shows where to put it in a Gradio app and how to tag sessions.
  • There is no Gradio instrumentor: UI events and queue metrics are not traced.
Was this page helpful?

Questions & Discussion