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:
| Field | Value |
|---|---|
| Span | ChatCompletion, span kind llm. Each turn is its own trace (no parent) |
| Model | the model the response names, for example gpt-6-luna |
| Tokens | prompt, completion and total, as the model host reports them |
| Session | session.id and gen_ai.conversation.id, the same for every turn of one browser session |
| Error | a 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 underif __name__ == "__main__":. If you importdemofrom another module (for example to mount it in FastAPI), callinit_tracing()there. - Turns are not grouped.
predictran without agr.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 seeAttempting to instrument while already instrumentedonce 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
ChatInterfacecallbacks. 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.
Questions & Discussion