The "Firehose" Problem
Imagine being the only person standing between a mountain of customer inquiries and a peaceful day at the office. That was the reality for the customer service lead at Thomas & Piron during peak activity periods.
The phone wouldn't stop ringing, the inbox was overflowing, and frustration was mounting—both for the customers waiting for answers and the team trying to provide them. High churn was becoming a real threat, and burnout was looming.
We needed a solution. Not just a band-aid, but a digital reinforcement.
Contents
Enter the AI Agent
We didn't just build a chatbot; we built a capable digital assistant. This agent wasn't designed to replace the human touch but to handle the heavy lifting so the human team could focus on what really matters.
Core Capabilities
The agent understands natural language in English, French, Dutch, and more. No more language barriers standing in the way of support.
For simple queries, it performs Retrieval-Augmented Generation (RAG) on internal documentation, instantly finding answers buried in PDFs or manuals.
For complex or sensitive actions, the agent pauses and asks for human approval. It prepares the work, but you pull the trigger.
It doesn't just talk; it does. It can create tickets directly in the CRM and schedule calls in the human agent's calendar.
Code Highlight: The "Human-in-the-Loop" Pattern
One of the most critical features was ensuring the AI didn't go rogue. Here is a simplified snippet of how we defined a Semantic Kernel function that requires human approval before finalizing an action.
[KernelFunction, Description("Schedules a meeting with a human agent.")]
public async Task<string> ScheduleMeetingAsync(
[Description("The email of the customer")] string customerEmail,
[Description("The preferred date and time")] DateTime preferredTime
)
{
// 1. Check availability
if (!await _calendarService.IsAvailableAsync(preferredTime))
{
return "Slot not available. Please suggest another time.";
}
// 2. Create a 'Pending' appointment requiring approval
var appointmentId = await _crmService.CreatePendingAppointmentAsync(
customerEmail,
preferredTime,
requiresApproval: true
);
// 3. Notify the human agent
await _notificationService.SendApprovalRequestAsync(appointmentId);
return $"Meeting request created (ID: {appointmentId}). Waiting for agent approval.";
}Under the Hood: The Tech Stack
I served as the Full-Stack Developer on this project, handling everything from the React frontend to the .NET backend. Here is the arsenal we used:
The "Brain" (RAG Pipeline)
Building a RAG system that actually works is more than just throwing text into a database. We had to get creative with ingestion and retrieval.
// Example of the retrieval pipeline with reranking
public async Task<List<DocumentChunk>> RetrieveAndRerankAsync(string userQuery)
{
// 1. Vector Search (Recall)
var embedding = await _embeddingGenerator.GenerateEmbeddingAsync(userQuery);
var initialResults = await _qdrantClient.SearchAsync(embedding, limit: 20);
// 2. Reranking (Precision)
// We use a cross-encoder model to score relevance between query and documents
var rerankedResults = await _reranker.RerankAsync(userQuery, initialResults);
// 3. Filter and Return Top K
return rerankedResults
.Where(r => r.RelevanceScore > 0.75)
.Take(5)
.ToList();
}The Impact
The results spoke for themselves. By deploying this agent, we managed to turn the tide during peak season.
Conclusion
This project wasn't just about using the latest AI buzzwords; it was about solving a real human problem with technology. We took a stressed-out support process and injected it with intelligence, efficiency, and a bit of .NET 9 magic.
Now, the customer service lead can actually take a lunch break. đŸ¥ª
Inspired by this project?
I can help you build something similar — let's chat about your idea and the next steps.
Subscribe to my newsletter
Get updates about new projects and occasional tips and tricks — just useful stuff.
Read another project
If you'd like to explore more, here's another project you might enjoy.
