Hello! Welcome to your lesson on collecting user feedback.
In our last session, you built a robust system to handle file uploads and trigger asynchronous AI processing pipelines. Your application can now accept user inputs and generate outputs. But how do you know if those outputs are any good? And how can your application learn from its mistakes?
Today, we address that crucial next step. Our learning outcome is to collect user feedback on predictions for model improvement. This lesson closes the loop, transforming your application from a static predictor into a dynamic system that can learn and evolve.
You will learn:
- The importance of feedback loops in the machine learning lifecycle.
- How to build a simple, effective user interface in Streamlit to capture feedback.
- How to design a backend system to store this feedback, linking it to the specific predictions.
- An introduction to "Human-in-the-Loop" systems where feedback can immediately refine an AI's output.
By the end of this lesson, you will be able to implement one of the most important features of any production-grade AI application.
1. The Feedback Loop: Turning Users into Collaborators
The core idea behind collecting feedback is to create a continuous cycle of improvement for your AI model. This is often called a "feedback loop."
Collecting user feedback isn't just about finding bugs; it's a strategic part of MLOps. The article "Collecting user feedback on ML in Streamlit" highlights several key benefits.
Collecting user feedback on ML in Streamlit | by Jeff Kayne
This article provides a great summary of why collecting user feedback is so important. Let's review the main reasons.
Read the section titled 'Why collect user feedback on ML?'. As you read, think about how each point could apply to an AI application you might build.
As the article explains, user feedback helps you:
- Improve Model Performance: It provides real-world data on where your model is succeeding or failing, which is invaluable for fine-tuning.
- Enhance User Experience: It helps you understand user pain points and preferences.
- Increase Engagement: Users who feel heard are more likely to trust and use your application.
- Enable Continuous Improvement: Feedback is the fuel for ongoing model monitoring and retraining, preventing performance degradation over time.
Now, let's see how to put this theory into practice.
2. Frontend: Capturing Feedback in Streamlit
In a previous lesson, you learned to build interactive UIs with Streamlit. Now, we'll use a specific Streamlit component, st.feedback, which is designed for exactly our purpose.
The Streamlit documentation provides a clear, step-by-step tutorial on building a chat application that collects feedback. We will adapt this for our needs.
Collect user feedback about LLM responses - Streamlit Docs
The official Streamlit documentation offers the best guide for implementing a feedback mechanism. We'll focus on the core components and logic.
Please review the following sections of the tutorial: Read the Introduction to understand the st.feedback component. Study the section 'Initialize and render your chat history'. Pay close attention to how feedback is stored in st.session_state and how the save_feedback callback function works. Read the section 'Add chat input'. Focus on how a new feedback widget is added for each new assistant response.
Let's synthesize what's in the documentation with a complete, runnable example. This app simulates a model's response and allows the user to give a thumbs-up or thumbs-down.
Example: A Simple Feedback App in Streamlit
import streamlit as st
import time
import requests
import json
# --- Backend API Endpoint (run this in a separate terminal with FastAPI) ---
# We'll define the backend logic in the next section. For now, assume it exists.
FEEDBACK_API_URL = "http://127.0.0.1:8000/feedback"
# --- Streamlit App ---
# Function to simulate an LLM response
def get_model_response(prompt):
response = f'This is a simulated response to your prompt: "{prompt}"'
for char in response:
yield char
time.sleep(0.02)
# Callback function to save feedback to session_state and send to backend
def save_feedback(response_index, feedback_value):
# Save to session state
st.session_state.history[response_index]["feedback"] = feedback_value
# Prepare data to send to the backend
feedback_data = {
"prompt": st.session_state.history[response_index-1]["content"],
"response": st.session_state.history[response_index]["content"],
"feedback": "positive" if feedback_value == "thumbsup" else "negative",
"model_version": "v1.0-simulated"
}
# Send feedback to the backend API
try:
res = requests.post(FEEDBACK_API_URL, json=feedback_data)
if res.status_code == 201:
st.toast("Feedback submitted successfully!", icon="✅")
else:
st.toast(f"Error submitting feedback: {res.text}", icon="❌")
except requests.exceptions.ConnectionError:
st.error("Could not connect to the feedback API. Is the backend running?")
# Initialize chat history
if "history" not in st.session_state:
st.session_state.history = []
st.title("AI with Feedback")
# Display chat messages from history
for i, message in enumerate(st.session_state.history):
with st.chat_message(message["role"]):
st.write(message["content"])
# Add feedback widget for assistant messages
if message["role"] == "assistant":
feedback = message.get("feedback")
st.feedback(
"thumbs",
key=f"feedback_{i}",
disabled=feedback is not None, # Disable if feedback already given
on_change=save_feedback,
args=(i, st.session_state[f"feedback_{i}"]),
)
# Accept user input
if prompt := st.chat_input("Ask the AI something..."):
# Add user message to history
st.session_state.history.append({"role": "user", "content": prompt})
with st.chat_message("user"):
st.write(prompt)
# Generate and display assistant response
with st.chat_message("assistant"):
response_content = st.write_stream(get_model_response(prompt))
# Add the new response to history BEFORE creating the feedback widget
response_index = len(st.session_state.history)
st.session_state.history.append({"role": "assistant", "content": response_content})
# Add the feedback widget for the new response
st.feedback(
"thumbs",
key=f"feedback_{response_index}",
on_change=save_feedback,
args=(response_index, st.session_state[f"feedback_{response_index}"])
)
This code establishes the frontend part of our feedback loop. When a user clicks a thumb, the save_feedback callback is triggered, which not only updates the UI but also sends the feedback to a backend API.
While st.feedback is excellent, for more complex scenarios you might explore libraries like Trubrics, which offers more advanced components.

3. Backend: Persisting Feedback for Analysis
The feedback collected in Streamlit's session state is temporary. To make it useful for model improvement, we must store it persistently. This is where your FastAPI skills come in.
We need an API endpoint that accepts the feedback data and saves it. A simple approach is to save it to a structured file like a JSON or CSV. A more robust solution would use a database.
Here is a simple FastAPI backend that receives the feedback from our Streamlit app and appends it to a JSON file.
# feedback_api.py
from fastapi import FastAPI, HTTPException, status
from pydantic import BaseModel
import json
from datetime import datetime
app = FastAPI()
# Pydantic model for incoming feedback data
class FeedbackPayload(BaseModel):
prompt: str
response: str
feedback: str # e.g., "positive" or "negative"
model_version: str
FEEDBACK_FILE = "feedback_log.json"
@app.post("/feedback", status_code=status.HTTP_201_CREATED)
def store_feedback(payload: FeedbackPayload):
"""
Receives feedback from the frontend and stores it.
"""
# Structure the data to be logged
log_entry = {
"timestamp": datetime.utcnow().isoformat(),
"prompt": payload.prompt,
"response": payload.response,
"feedback": payload.feedback,
"model_version": payload.model_version
}
try:
# Read existing data and append new entry
try:
with open(FEEDBACK_FILE, "r") as f:
logs = json.load(f)
except (FileNotFoundError, json.JSONDecodeError):
logs = []
logs.append(log_entry)
# Write back to the file
with open(FEEDBACK_FILE, "w") as f:
json.dump(logs, f, indent=2)
except Exception as e:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Failed to write feedback: {e}"
)
return {"message": "Feedback stored successfully", "data": log_entry}
To run this system:
- Save the FastAPI code as
feedback_api.pyand run it:uvicorn feedback_api:app --reload. - Save the Streamlit code as
app.pyand run it in a separate terminal:streamlit run app.py.
Now, when you submit feedback in the Streamlit app, a new entry will be added to feedback_log.json. This log file becomes your new dataset for analysis and future model fine-tuning.
4. Advanced Concept: Active Feedback with LangGraph
So far, we've focused on collecting feedback for future model improvement. But what if feedback could improve the result right now? This concept is often called Human-in-the-Loop (HITL), and it's particularly powerful in generative AI.
The idea is to treat human feedback not just as a label, but as an instruction to retry or refine. LangGraph, a library for building stateful, multi-agent applications, is perfect for this.
This video demonstrates how to build a system where a user can provide corrective feedback to an AI, which then generates a better response in the same session.
Testing Human in the Loop with LangGraph Example – Building a Fullstack Feedback Loop with FastAPI
The video 'Testing Human in the Loop with LangGraph Example' shows an advanced application of our topic. It demonstrates how user feedback can be used to immediately guide an AI's response, rather than just being logged for later.
Watch the following segments to understand this powerful pattern: Concept Demo (00:00:00 - 00:03:22): See the final application in action. Notice how the user can 'approve' a response or 'send feedback' with a comment to get a revised answer. LangGraph Implementation (00:05:50 - 00:09:27): Pay attention to the state definition, which includes a human_comment and a status. See how the conditional router uses this status to either finalize the process or loop back to the AI for a rewrite. FastAPI Endpoints (00:14:43 - 00:17:59): This connects the concept to your full-stack skills. Observe how two simple endpoints, start and resume, are used to manage the conversational state of the graph.
This HITL approach creates a collaborative session between the user and the AI, where the final output is a joint effort. It's a powerful pattern for tasks like writing assistance, code generation, and complex problem-solving.
Conclusion
You have now learned how to create a complete feedback loop in your AI application. This is a critical skill that bridges the gap between a prototype and a production-ready system that can adapt and improve.
Key Takeaways:
- Feedback is essential: It's the primary mechanism for monitoring model performance, identifying weaknesses, and gathering data for continuous improvement.
- Frontend implementation is straightforward: With tools like Streamlit's
st.feedback, you can build intuitive UIs for feedback collection with minimal code. - Backend persistence is key: You must design a system (e.g., a FastAPI endpoint and a database or log file) to store feedback in a structured format, linking it to the original prompt, response, and model version.
- Active feedback is the next frontier: With frameworks like LangGraph, you can build Human-in-the-Loop systems where user feedback immediately refines AI outputs, creating a more interactive and collaborative experience.
Preview of the Next Lesson:
Now that your application is interacting more deeply with users—accepting prompts and feedback—security becomes paramount. In the next lesson, we will "Apply security best practices: input validation and prompt injection prevention," ensuring your AI application is not only smart but also safe and robust against malicious inputs.