Building Golden Datasets from Production Traces with Falcon AI
Turn production traces into a curated, ground-truthed golden dataset in one Falcon AI conversation.
Trace a live classifier, then drive one Falcon AI conversation through triage, dataset curation, ground truthing, and an exact-match eval. The result is a balanced, ground-truthed regression dataset built from your own production traces.
| Time | Difficulty | Package |
|---|---|---|
| 15 min | Intermediate | fi-instrumentation-otel |
- Future AGI account → app.futureagi.com
- API keys:
FI_API_KEYandFI_SECRET_KEY(see Get your API keys) - A traced project with traces of varied quality. If you don’t have one, instrument any agent with the first step below.
- Python 3.11
Install
pip install fi-instrumentation-otel traceai-openai openai
export FI_API_KEY="your-fi-api-key"
export FI_SECRET_KEY="your-fi-secret-key"
export OPENAI_API_KEY="your-openai-key"
Tutorial
Add tracing to your agent
Falcon AI reads your agent’s traces, so the agent has to be sending traces to Future AGI before any later step can run. OpenAIInstrumentor patches the OpenAI SDK so every API call is captured automatically. The @tracer.agent decorator on your agent’s entry point makes each classification appear as one parent span Falcon AI can filter on.
from fi_instrumentation import register, FITracer
from fi_instrumentation.fi_types import ProjectType
from traceai_openai import OpenAIInstrumentor
trace_provider = register(
project_type=ProjectType.OBSERVE,
project_name="email-triage-prod",
)
OpenAIInstrumentor().instrument(tracer_provider=trace_provider)
tracer = FITracer(trace_provider.get_tracer("email-triage-prod"))from openai import OpenAI
client = OpenAI()
# Replace this with your own agent's entry point.
# The @tracer.agent decorator makes each call show up as one parent span
# in your Future AGI Tracing project, with the OpenAI calls nested underneath.
@tracer.agent(name="triage_email")
def triage_email(email_text: str) -> str:
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "system", "content": "Classify this email into one of: urgent, billing, technical, general, spam. Reply with just the category name."},
{"role": "user", "content": email_text},
],
)
return response.choices[0].message.content
# A classifier with a thin prompt will misclassify ambiguous emails (hostile tone
# over a small issue, multi-issue emails, etc.). Run a varied batch so Falcon AI
# has both clean classifications and likely misclassifications in the next step.
print(triage_email("Production is down. Payment processing has been failing for 30 minutes."))
print(triage_email("WORST SERVICE EVER. I have been on hold for 2 hours. CALL ME BACK."))
print(triage_email("I have a billing question and also my login is not working since yesterday."))
print(triage_email("Why am I being charged $499 when I signed up for the $49 plan? Please fix this or I am canceling."))
trace_provider.force_flush()You should see four printed category labels and no errors. force_flush() blocks until the spans reach Future AGI, so once it returns, the traces are visible in your project’s Tracing tab. For broader instrumentation patterns see Manual Tracing.
Explore failures with Falcon AI
Falcon AI picks up whatever page you’re viewing as context. Open it from your project’s Tracing page (the context chip should show the project name), then type:
What categories did my agent assign across these traces, and which ones look like misclassifications?
Tip
Cmd+K (Mac) or Ctrl+K (Windows) opens Falcon AI from anywhere in the dashboard, with the current page auto-attached as a context chip.
You should see a category histogram and a list of traces where the category looks off given the email content (your wording and counts will vary). These flagged misclassifications are a strong starting point, not ground truth. You’ll confirm them in a later step.
Build the dataset with curation criteria
/build-dataset reads the traces in context and writes matching rows to a new dataset. The skill follows whatever selection criteria you give it, so the prompt below bakes in a coverage rule that mixes easy-pass rows with the misclassifications from the previous turn.
/build-dataset
Build a dataset called
email-triage-eval-v1. Pull rows from the traces in this project. Selection criteria: include at least 2 traces from each category (urgent, billing, technical, general, spam) plus the likely misclassifications you flagged in the previous turn. Total target: 12-15 rows. Columns:
email_text(text): the email body the agent classifiedpredicted_category(text): what the agent chosetrace_id(text): so we can trace any failure back
You should see a completion card linking to the new dataset, with 12-15 rows and every category represented. A dataset that is 90% successes won’t catch regressions; one that is 90% failures won’t catch false positives. The “at least 2 from each category plus the misclassifications” rule gives both classes meaningful coverage.
Add a ground truth column
predicted_category is what the agent chose. To turn the dataset into an eval, you need expected_category, what the agent should have chosen. For genuinely ambiguous rows (hostile tone over a small issue, multi-issue emails) there is no single correct answer, so this step uses a NEEDS_REVIEW value plus a review_note column to surface them for human judgment instead of poisoning the eval with arbitrary labels.
Add a column
expected_category(text) toemail-triage-eval-v1. For each row, propose the correct category based on the email text. For rows where the correct category is genuinely ambiguous (e.g., hostile tone over a small issue, multi-issue emails), use the valueNEEDS_REVIEWand add a one-sentence note in a new columnreview_note(text) explaining why.
You should see both columns populated on every row, with a split between confident expected_category values and a few rows tagged NEEDS_REVIEW. Open the dataset in Datasets → email-triage-eval-v1, click each NEEDS_REVIEW row, and decide based on your team’s routing rules. Edit the rows in the UI or ask Falcon AI to update them.
Lock in a baseline eval
/run-evaluations runs an eval template from your workspace’s catalog against every row in the dataset and returns per-row and aggregate scores. Describe the goal in plain English so Falcon AI picks the right template (here, an exact-match check between two text columns).
/run-evaluations
Run an evaluation on
email-triage-eval-v1that checks whetherpredicted_categoryexactly matchesexpected_categoryfor each row. Use the eval template from this workspace that best fits a string-equality check between two columns.
You should see a per-row pass/fail/skip verdict and an aggregate pass rate that is neither 0% nor 100%. Both the pass pattern and the fail pattern are what you want: a regression test where every row passes is not testing anything, and one where every row fails is just noisy. The dataset now has compounding value: any future prompt change can be re-scored against it in one chat message.
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| Falcon AI’s context chip shows no project, or the wrong one | The sidebar was opened from a page outside the traced project | Open Falcon AI from inside the project’s Tracing tab, not the global dashboard |
| Falcon AI says it found no traces | Spans haven’t reached Future AGI yet | Call trace_provider.force_flush() and wait a few seconds before opening the sidebar |
/build-dataset returns fewer than 12-15 rows | The project doesn’t have enough traces in one or more categories | Run more classification calls to fill the gap, or relax the “at least 2 per category” criteria |
expected_category disagrees with an obviously correct predicted_category | The row’s email text is genuinely ambiguous, or the selection criteria in the prompt was too vague | Re-run with a more specific rule, or edit the row directly in the dataset UI |
/run-evaluations picks a template that isn’t an exact-match check | The workspace has no string-equality template, or the prompt didn’t name the columns explicitly | Name predicted_category and expected_category directly in the prompt, or create a matching template in the Evaluations settings |
| The eval run shows a 0% or 100% pass rate | The dataset lacks category coverage or contains only clean or only failing rows | Rebuild the dataset with the per-category coverage rule from the dataset step |
OpenAIInstrumentor captures no spans for OpenAI calls | The client was created before .instrument() ran | Call OpenAIInstrumentor().instrument(tracer_provider=trace_provider) before creating the OpenAI() client |
Next: chain trace debugging, dataset curation, and evals into a single fix with Falcon AI End-to-End Workflow.
Questions & Discussion