Built an AI-powered conversational chatbot for MOSAIC, helping 660,000+ annual visitors find immigration and settlement services through natural language interactions.
An intelligent assistant helping immigrants and refugees discover settlement services
Developed an AI-powered conversational chatbot that converts natural language queries into intelligent program recommendations, eliminating the need for users to navigate complex service directories.
Vector embeddings with Neo4j for semantic program matching
Multi-turn dialogue with context retention across sessions
GPT-4o Mini achieving $16/year operational cost
Non-technical staff can update programs via CSV upload
End-to-end pipeline from user query to intelligent response
Natural language queries are converted into 1536-dimensional vector embeddings using OpenAI's embedding model for semantic search.
Neo4j graph database stores 50+ programs with metadata including services, eligibility criteria, locations, and vector embeddings.
Cosine similarity search retrieves the most relevant programs based on semantic meaning, not just keyword matching.
GPT-4o Mini generates conversational responses grounded in retrieved program data with optimized prompts.
Session-based memory tracks context across multiple exchanges for coherent multi-turn dialogue.
Retrieval-Augmented Generation combines search results with LLM reasoning for accurate, grounded responses.
Modern tools and frameworks powering the solution
Measurable outcomes and cost efficiency
Key milestones in building the MOSAIC AI chatbot
Evaluated LLM options (GPT-4, Falcon-7B, Llama-2), collected training data, and designed knowledge graph schema.
Built vector embedding system with Neo4j, implemented semantic search with cosine similarity.
Integrated GPT-4o Mini, optimized prompts for accuracy, implemented conversation memory.
Deployed React frontend, Flask backend with Docker, set up CI/CD with GitHub Actions.
Completed admin portal, documentation, and handed off to MOSAIC for production use.
How this experience transformed me as an engineer
Before joining the team, I had already built a personal immigration chatbot using RAG and vector databases—a solo project where I controlled every decision. I understood the technical concepts, but I had never experienced what it meant to write code that others would read, extend, and depend on.
Working alone, I could take shortcuts. My entire chatbot lived in one massive .py file—no classes, no modules, no separation of concerns. Variable names didn't need to be self-explanatory. I didn't practice OOP or think about code organization because there was no one else who needed to understand my code. Git was just a backup tool, not a collaboration platform.
Joining as an AI Engineer meant I had to learn fast. Suddenly, my code needed to integrate with a React frontend, follow established patterns, and be reviewable by teammates. I quickly learned Git workflows—branching, pull requests, rebasing, resolving merge conflicts. I discovered why modularization matters when you're working with cypher.py, llmchain.py, vector.py, and agent.py instead of one monolithic file.
SFU Blueprint taught me that professional software engineering is fundamentally about collaboration. The best code isn't clever—it's clear. I experienced the satisfaction of implementing features that integrated seamlessly with my teammates' work, and the humility of having my code reviewed and improved by others.
One of my final tickets challenged me to solve a critical reliability problem: what happens when the AI generates an unhelpful response? Rather than letting users receive vague or off-topic answers, I designed a fallback system that uses a secondary AI evaluation to detect unhelpful responses and automatically triggers a vector search fallback.
# Response Evaluation Template
RESPONSE_EVALUATION_TEMPLATE = """
You are an AI assistant tasked with determining if a given response is helpful or unhelpful.
A helpful response provides specific, relevant information that answers the user's question.
An unhelpful response is vague, off-topic, or indicates a lack of information.
User's question: {question}
Response to evaluate: {response}
Is this response helpful or unhelpful? Reply with only one word: "HELPFUL" or "UNHELPFUL".
"""
def is_unhelpful_response(question, response):
# Quick check for the exact phrase
if response.strip() == "I don't know the answer.":
print("[*]AI Exact Match: 'I don't know the answer.' Deemed unhelpful.")
return True
# If it's not the exact phrase, use AI to evaluate response...
print("No exact match. Using AI to evaluate response...")
evaluation = response_evaluation_chain.run(question=question, response=response)
is_unhelpful = evaluation.strip().upper() == "UNHELPFUL"
print(f"AI evaluation result: {'Unhelpful' if is_unhelpful else 'Helpful'}")
return is_unhelpful
def cypher_qa(query):
try:
result = cypher_qa_chain(query)
if is_unhelpful_response(query, result['result']):
print("Response deemed unhelpful. Falling back to vector search...")
return kg_qa(query) # Fallback to semantic search
return result['result']
except (ValueError, CypherSyntaxError) as e:
print(f"Cypher query failed: {e}")
print("Falling back to vector search...")
return kg_qa(query)
This solution taught me to think about edge cases and user experience holistically. It wasn't enough to make the chatbot work—it needed to fail gracefully and still provide value when the primary method didn't succeed.
I built the backend logic to generate clickable hyperlinks using GPT-4o, ensuring users could navigate directly to relevant resources. But the real learning came from collaborating with frontend engineers to render these links properly in the UI—not just as raw URLs, but as styled, accessible hyperlinks.
This feature taught me that backend and frontend are not separate worlds. Understanding how my API responses would be consumed helped me design better data structures and communicate more effectively with the frontend team.
Writing code that others can understand, maintain, and extend. Learning to give and receive constructive code reviews.
Designing systems with separation of concerns. Understanding when to abstract and when to keep things simple.
Moving beyond basic commits to branching strategies, pull requests, rebasing, and resolving merge conflicts.
Tracing issues across the entire stack—from database queries to API responses to frontend rendering.
"The codebase has since been handed off to MOSAIC, and I no longer have repository access. But the lessons I learned—about collaboration, code quality, and building for others—these stay with me. Every line of code I write now is informed by this experience."