Collect Feedback
Incorperate real user feedback into your evaluation pipeline
Overview
Confident AI allows you to collect feedback from end users that are interacting with your LLM app. A thumbs up/down after a chatbot reply, a star rating at the end of a support conversation, a "was this helpful?" prompt — all of these are signals about how your AI is actually performing in production, and they're often the earliest warning you'll get that something regressed. End user feedback can be left on:
- Traces
- Spans, and
- Threads
When you send an annotation of a user feedback, you'll get the opportunity to incorporate them into a dataset, so the conversations your users flagged become the test cases you evaluate against next.
How It Works
To collect feedback, you need to:
- Setup a custom UI for users to enter their rating (thumbs up/down or 5 star system), and optionally expected outcome/output, and explanation
- Either collect the trace UUID, span UUID, or thread ID you'd like to leave feedback for
- Send the feedback to Confident AI via the Evals API
Since the thread ID is something you provide (click here if unsure) during LLM tracing, it is generally easier to setup feedback collection on threads than on traces and spans.
Collect Single-Turn Feedback
Get the OpenTelemetry identifiers
Read the trace and span IDs while your application span is active, and store them alongside the response so your feedback UI can refer back to the correct request later.
main.py from openai import OpenAI from confident_trace import init, span, shutdown from opentelemetry import trace init() client = OpenAI() def llm_app(query: str): with span("llm_app", type="agent"): res = client.chat.completions.create( model="gpt-4o", messages=[{"role": "user", "content": query}] ).choices[0].message.content current_span = trace.get_current_span() context = current_span.get_span_context() trace_id = f"{context.trace_id:032x}" if context.is_valid else None span_id = f"{context.span_id:016x}" if context.is_valid else None return res, trace_id, span_id try: output, TRACE_ID, SPAN_ID = llm_app("Write me a poem.") finally: shutdown()src/index.ts import OpenAI from "openai"; import { init, withSpan } from "confident-trace"; import { trace } from "@opentelemetry/api"; const runtime = init(); const openai = new OpenAI(); const llmApp = async (query: string) => { return withSpan({ name: "llm_app", type: "agent" }, async () => { const res = await openai.chat.completions.create({ model: "gpt-4o", messages: [{ role: "user", content: query }], }); const { traceId, spanId } = trace.getActiveSpan()!.spanContext(); return { output: res.choices[0].message.content, traceId, spanId }; }); }; try { const { output, traceId: TRACE_ID, spanId: SPAN_ID } = await llmApp("Write me a poem."); } finally { await runtime.shutdown(); }Remember to launch your entry point with the Node preload (
node --import tsx --import confident-trace/register src/index.ts).Send annotation for trace/span
In a separate workflow — typically the handler behind your thumbs up/down button — send the feedback with DeepEval using the OpenTelemetry IDs you collected. The annotation API still names these fields
trace_uuidandspan_uuid.Thumbs Rating from deepeval.annotation import send_annotation send_annotation( trace_uuid=TRACE_ID, rating=1, # span_uuid=SPAN_ID, # you can only set trace_uuid or span_uuid )5 Star Rating from deepeval.annotation.api import AnnotationType from deepeval.annotation import send_annotation send_annotation( trace_uuid=TRACE_ID, type=AnnotationType.FIVE_STAR_RATING, rating=5 # span_uuid=SPAN_ID, # you can only set trace_uuid or span_uuid )Thumbs Rating import { sendAnnotation } from "deepeval/annotation"; sendAnnotation({ traceUuid: TRACE_ID, rating: 1, // spanUuid: SPAN_ID, // you can only set traceUuid or spanUuid });5 Star Rating import { sendAnnotation, AnnotationType } from "deepeval/annotation"; sendAnnotation({ traceUuid: TRACE_ID, type: AnnotationType.FIVE_STAR_RATING, rating: 5, // spanUuid: SPAN_ID, // you can only set traceUuid or spanUuid });
Collect Multi-Turn Feedback
Setup thread ID
Define a thread ID and configure your traced LLM app to associate all related traces to this thread. Set it on the trace inside your application span —
update_trace/updateTracewrites to the outermost span of the current trace, so it works from anywhere in the request.main.py from openai import OpenAI from confident_trace import init, span, update_trace, shutdown init() THREAD_ID = "YOUR-THREAD-ID" client = OpenAI() def llm_app(query: str) -> str: with span("llm_app", type="agent"): response = client.chat.completions.create( model="gpt-4o", messages=[{"role": "user", "content": query}] ).choices[0].message.content update_trace(thread_id=THREAD_ID, input=query, output=response) return response try: llm_app("Write me a poem.") finally: shutdown()src/index.ts import OpenAI from "openai"; import { init, withSpan, updateTrace } from "confident-trace"; const runtime = init(); const THREAD_ID = "YOUR-THREAD-ID"; const openai = new OpenAI(); const llmApp = async (query: string) => { return withSpan({ name: "llm_app", type: "agent" }, async () => { const res = await openai.chat.completions.create({ model: "gpt-4o", messages: [{ role: "user", content: query }], }); const output = res.choices[0].message.content; updateTrace({ threadId: THREAD_ID, input: query, output }); return output; }); }; try { await llmApp("Write me a poem."); } finally { await runtime.shutdown(); }Send annotation for thread
Post the thread-level feedback to the Evals API using the thread IDs you defined. Because the thread ID is yours, there's nothing to look up — the only thing to keep in mind is that the traces for that thread need to have been ingested first, which usually means waiting a few seconds after the response is sent.
Thumbs Rating from deepeval.annotation import send_annotation send_annotation( thread_id=THREAD_ID, rating=1, )5 Star Rating from deepeval.annotation.api import AnnotationType from deepeval.annotation import send_annotation send_annotation( thread_id=THREAD_ID, type=AnnotationType.FIVE_STAR_RATING, rating=5 )Thumbs Rating import { sendAnnotation } from "deepeval/annotation"; sendAnnotation({ threadId: THREAD_ID, rating: 1, });5 Star Rating import { sendAnnotation, AnnotationType } from "deepeval/annotation"; sendAnnotation({ threadId: THREAD_ID, type: AnnotationType.FIVE_STAR_RATING, rating: 5, });
Next Steps
Once feedback is flowing in, put it to work:
Annotation Queues
Route traces, spans, and threads to your team for structured review inside the platform.
Eval Alignment
Use human feedback to check and tune how well your metrics agree with real users.
Last updated on