Most chatbots fail for one simple reason: they ignore what’s actually happening with the user. Imagine asking for help while browsing your account page, but the bot replies with a generic FAQ. It’s frustrating because it ignores details like where you are in the app, what plan you’re on, or what you just did.
That’s where context comes in. When a chatbot understands those details, it stops feeling like an obstacle and starts acting like a real assistant.
In this article, we’ll walk through how to build a chatbot that does exactly that using retrieval-augmented generation (RAG) with LangChain.js. By blending your knowledge base with real-time context from your app, you can deliver answers that match each user’s situation, whether they’re on a free trial, a premium plan, or navigating a specific feature. By the end, you’ll have a working setup that adapts to your users, rather than forcing them to adapt to the bot.
Before we get into writing code, it’s worth clarifying why RAG is such a big deal compared to the usual chatbot setups.
Both approaches miss the mark because they ignore all the rich context your app already has.
RAG takes a different approach by splitting the problem into two steps:
It’s like working with a sharp research assistant: they don’t memorize every detail of your product, but they know exactly where to look when you ask a question and then explain it clearly. That way, your chatbot stays grounded in the facts that matter, without losing its conversational edge.
That combination makes RAG especially powerful in apps where both speed and accuracy matter.
LangChain.js is a JavaScript framework that makes it easier to build apps with large language models. The original LangChain focused on Python, but the JS version brings the same capabilities to the frontend and Node.js, perfect if you’re building web apps and don’t want to jump through backend hoops.
Here are the key building blocks you’ll use:
Before diving into code, let’s look at what the architecture for our smart chatbot would be. A context-aware RAG chatbot typically works like this:
To keep the system practical in real apps, storage and embeddings deserve attention:
Let’s actually build this thing. We’ll put together a SaaS dashboard with a support chatbot that knows where users are and what they’re trying to do.
# Create new Next.js project npx create-next-app@latest smart-chatbot --typescript --tailwind --eslint cd smart-chatbot # Install LangChain.js and supporting packages npm install langchain @langchain/openai @langchain/community npm install @langchain/core npm install faiss-node # For production vector storage
Add your OpenAI key in .env.local
:
NEXT_PUBLIC_OPENAI_API_KEY=your_openai_api_key_here
Your chatbot is only as good as the knowledge you give it. That means clear, structured docs. Each Document
should describe a feature or flow, and metadata will help retrieval later.
import { Document } from '@langchain/core/documents'; const knowledgeBase = [ new Document({ pageContent: ` The Dashboard shows key metrics: active users, revenue, and system health. Free plan: - Real-time metrics - Basic customization - Export to PDF Pro plan: - 12 custom widgets - Advanced filtering - Custom date ranges To customize widgets, click the gear icon on a card.`, metadata: { source: "dashboard", type: "feature_guide", user_levels: ["free", "pro", "enterprise"] } }), // …add analytics, billing, troubleshooting, etc. ];
A few rules of thumb when writing your docs:
source
, user_levels
) to sharpen retrieval.Now let’s wire up the heart of the chatbot: a LangChain.js pipeline that retrieves relevant docs and feeds them into the model with user context.
import { OpenAI } from '@langchain/openai'; import { PromptTemplate } from '@langchain/core/prompts'; import { RunnableSequence, RunnablePassthrough } from '@langchain/core/runnables'; import { StringOutputParser } from '@langchain/core/output_parsers'; import { MemoryVectorStore } from '@langchain/vectorstores/memory'; import { OpenAIEmbeddings } from '@langchain/openai'; import { formatDocumentsAsString } from 'langchain/util/document'; class ContextAwareRAGChatbot { constructor(openaiApiKey) { this.llm = new OpenAI({ openAIApiKey, temperature: 0.7, modelName: "gpt-3.5-turbo", maxTokens: 500 }); this.embeddings = new OpenAIEmbeddings({ openAIApiKey, modelName: "text-embedding-ada-002" }); this.initializeVectorStore(); this.createRAGChain(); } async initializeVectorStore() { this.vectorStore = await MemoryVectorStore.fromDocuments( knowledgeBase, this.embeddings ); } createRAGChain() { const retriever = this.vectorStore.asRetriever({ k: 3 }); const contextPrompt = PromptTemplate.fromTemplate(` You are a helpful assistant for "DashboardPro". CONTEXT: - Current page: {currentRoute} - Plan: {userPlan} - User: {userName} Use the context and knowledge base below to answer. If you’re missing details, be upfront. KNOWLEDGE: {context} QUESTION: {question} RESPONSE: `); this.ragChain = RunnableSequence.from([ { context: (input) => this.contextualRetriever(input), question: new RunnablePassthrough(), currentRoute: (input) => input.currentRoute || 'unknown', userPlan: (input) => input.userPlan || 'Free', userName: (input) => input.userName || 'there' }, contextPrompt, this.llm, new StringOutputParser() ]); } }
This is where we make retrieval smarter than “just semantic search.” We’ll boost relevance based on the user’s current page and plan.
async contextualRetriever(input) { const { question, currentRoute, userPlan } = input; const similarDocs = await this.vectorStore.similaritySearch(question, 5); const boostedDocs = similarDocs.map(doc => { let score = 0; if (doc.metadata.source === currentRoute) score += 0.3; if (doc.metadata.user_levels?.includes(userPlan.toLowerCase())) score += 0.2; return { ...doc, score }; }); const ranked = boostedDocs .sort((a, b) => (b.score || 0) - (a.score || 0)) .slice(0, 3); if (userPlan === 'Free' && question.toLowerCase().includes('upgrade')) { ranked.push(new Document({ pageContent: \`Pro ($29/month) unlocks advanced analytics and integrations. Enterprise ($99/month) adds unlimited usage and dedicated support.\`, metadata: { source: "billing", type: "upgrade_info" } })); } return formatDocumentsAsString(ranked); }
Why this works:
With the RAG logic in place, let’s expose it in a chat interface. Here’s the skeleton hook:
function useContextAwareChatbot(apiKey) { const [chatbot, setChatbot] = useState(null); useEffect(() => { if (!chatbot && apiKey) { setChatbot(new ContextAwareRAGChatbot(apiKey)); } }, [apiKey, chatbot]); const sendMessage = async (message, userContext) => { if (!chatbot) return null; return chatbot.ragChain.invoke({ question: message, ...userContext }); }; return { sendMessage }; }
Then wrap it in a React component with your chat UI.
BufferMemory
so the bot doesn’t forget the thread.async chat(question, userContext) { try { return await this.ragChain.invoke({ question, ...userContext }); } catch (err) { console.error('Chatbot error:', err); return `Something went wrong. You can: - Check our docs (/docs) - Email [email protected] - Try rephrasing your question`; } }
The architecture we’ve built enables several powerful use cases that go beyond traditional chatbots. Here are a few ideas you can experiment with:
Instead of forcing users to describe what went wrong, the chatbot can automatically reference recent errors from the app:
// Track user errors in your app const userContext = { recentErrors: [ { type: 'api_connection_failed', timestamp: '2024-01-15T10:30:00Z' }, { type: 'chart_render_error', page: 'analytics', timestamp: '2024-01-15T11:15:00Z' } ] }; // Enhance the query with recent errors if (input.recentErrors?.length > 0) { const errorContext = input.recentErrors.map(err => `Recent error: ${err.type} on ${err.page || 'unknown page'}` ).join('. '); enhancedQuery = `${question}. Context: User recently experienced: ${errorContext}`; }
Now the chatbot can say something like: “It looks like you recently had trouble with charts not rendering on the Analytics page. Let’s go through how to fix that.”
Guiding new users through onboarding becomes smoother when the bot knows their current step:
// Track onboarding progress const onboardingContext = { completedSteps: ['account_created', 'first_login'], currentStep: 'data_connection', timeInApp: '5 minutes' }; // Provide contextual onboarding help if (onboardingContext.currentStep === 'data_connection' && question.includes('connect')) { // Prioritize documents about connecting data sources }
Instead of generic help, the chatbot tailors guidance to the exact stage of onboarding.
Nobody likes fighting with a form. Context-aware assistance can help fix validation issues in real time:
// Context-aware form assistance const formContext = { currentForm: 'billing_settings', completedFields: ['company_name', 'email'], validationErrors: ['invalid_credit_card'] }; if (formContext.validationErrors.length > 0) { // Provide targeted guidance for fixing invalid inputs }
The chatbot can highlight what’s wrong and guide users step by step.
RAG chatbots don’t have to wait for questions — they can surface relevant features proactively:
// Suggest relevant features based on usage const usageContext = { mostUsedFeatures: ['dashboard', 'basic_analytics'], planUpgradeOpportunities: ['advanced_filtering', 'custom_reports'], timeAsCustomer: '3 months' }; if (usageContext.timeAsCustomer > '1 month' && !usageContext.mostUsedFeatures.includes('analytics')) { // Suggest analytics features or upgrade opportunities }
Instead of leaving users to stumble across advanced features, the chatbot nudges them toward tools that match their needs.
LangChain.js is modular, but you’ll still want to be mindful of imports and loading strategies:
// Import only what you need import { OpenAI } from '@langchain/openai'; import { MemoryVectorStore } from '@langchain/vectorstores/memory'; // Avoid: import * from 'langchain' // Lazy load the chatbot const [chatbot, setChatbot] = useState(null); const initializeChatbot = useCallback(async () => { const { ContextAwareRAGChatbot } = await import('./chatbot'); const instance = new ContextAwareRAGChatbot(apiKey); setChatbot(instance); }, [apiKey]);
Avoid unnecessary API calls by caching embeddings and responses:
// Cache embeddings and responses class CachedRAGChatbot extends ContextAwareRAGChatbot { constructor(apiKey) { super(apiKey); this.responseCache = new Map(); this.embeddingCache = new Map(); } async chat(question, userContext) { const cacheKey = `${question}-${userContext.currentRoute}-${userContext.user.plan}`; if (this.responseCache.has(cacheKey)) { return this.responseCache.get(cacheKey); } const response = await super.chat(question, userContext); this.responseCache.set(cacheKey, response); return response; } }
Instrumentation helps track successes and failures:
// Add comprehensive error tracking class MonitoredRAGChatbot extends ContextAwareRAGChatbot { async chat(question, userContext) { const startTime = Date.now(); try { const response = await super.chat(question, userContext); this.logInteraction({ question, responseTime: Date.now() - startTime, userContext, success: true }); return response; } catch (error) { this.logError({ error: error.message, question, userContext, responseTime: Date.now() - startTime }); throw error; } } }
Simply hooking up an LLM to a chat window doesn’t make for a smart assistant. The real value comes when the chatbot understands context: where users are, what they’re trying to do, and what information they’ve already encountered. The RAG architecture we’ve explored shows how to get there by combining your knowledge base with real-time application context to create responses that are accurate, relevant, and genuinely helpful.
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowWalk through a practical example of n8n’s Eval feature, which helps developers reduce hallucinations and increase reliability of AI products.
Secure AI-generated code with proactive prompting, automated guardrails, and contextual auditing. A practical playbook for safe AI-assisted development.
Explore the vibe coding hype cycle, the risks of casual “vibe-driven” development, and why prompt engineering deserves a comeback as a critical skill for building better, more reliable AI applications.
Shipping modern frontends is harder than it looks. Learn the hidden taxes of today’s stacks and practical ways to reduce churn and avoid burnout.