rimentation.
3. Knowledge Layer: RAG pipeline ingests website content into a Postgres vector store for semantic retrieval.
4. Infrastructure: Railway hosts the application and manages the Postgres database with zero configuration.
Implementation Steps
1. Initialize Hexabot Project
Start by scaffolding the project using the Hexabot CLI. This generates a type-safe configuration structure for your workflow.
// hexabot.config.ts
import { defineFlow, createNode, createEdge } from '@hexabot/core';
export const supportFlow = defineFlow({
id: 'customer_support_v1',
nodes: [
createNode('entry', {
type: 'trigger',
handler: 'onMessageReceived'
}),
createNode('rag_retrieval', {
type: 'ai_retrieval',
config: {
provider: 'openrouter',
model: 'meta-llama/llama-3-8b-instruct:free',
retrieval: {
source: 'website_docs',
topK: 3,
similarityThreshold: 0.75
}
}
}),
createNode('response_generation', {
type: 'ai_completion',
config: {
provider: 'openrouter',
model: 'meta-llama/llama-3-8b-instruct:free',
systemPrompt: 'You are a helpful support agent. Use the provided context to answer questions.'
}
})
],
edges: [
createEdge('entry', 'rag_retrieval'),
createEdge('rag_retrieval', 'response_generation')
]
});
Rationale: Defining the flow as code enables version control and CI/CD integration. The rag_retrieval node abstracts the complexity of vector search, allowing Hexabot to handle the retrieval step automatically before passing context to the generation node.
2. Configure OpenRouter Integration
Create a wrapper for the OpenRouter API to manage requests and handle rate limiting. This abstraction allows you to switch models or providers without modifying the workflow definition.
// services/openrouter.ts
import axios from 'axios';
export class OpenRouterGateway {
private baseUrl = 'https://openrouter.ai/api/v1';
private apiKey: string;
constructor(apiKey: string) {
this.apiKey = apiKey;
}
async generateCompletion(prompt: string, context: string[], model: string) {
const systemMessage = `Context: ${context.join('\n')}\n\nAnswer based on the context provided.`;
const response = await axios.post(`${this.baseUrl}/chat/completions`, {
model,
messages: [
{ role: 'system', content: systemMessage },
{ role: 'user', content: prompt }
],
temperature: 0.2
}, {
headers: {
'Authorization': `Bearer ${this.apiKey}`,
'HTTP-Referer': process.env.APP_URL,
'X-Title': 'SupportBot'
}
});
return response.data.choices[0].message.content;
}
}
Rationale: Using a dedicated service class encapsulates API logic and error handling. The temperature is set low to ensure deterministic responses, which is critical for support scenarios where accuracy outweighs creativity.
3. Implement RAG Ingestion Pipeline
The RAG pipeline transforms your website content into vector embeddings stored in Postgres. This step ensures the bot can retrieve relevant information dynamically.
// services/rag-pipeline.ts
import { createClient } from '@supabase/supabase-js';
import { RecursiveCharacterTextSplitter } from 'langchain/text_splitter';
export class RAGPipeline {
private db: any;
private splitter: RecursiveCharacterTextSplitter;
constructor(dbClient: any) {
this.db = dbClient;
this.splitter = new RecursiveCharacterTextSplitter({
chunkSize: 512,
chunkOverlap: 50
});
}
async ingest(content: string) {
const chunks = await this.splitter.splitText(content);
for (const chunk of chunks) {
const embedding = await this.generateEmbedding(chunk);
await this.db.from('knowledge_base').insert({
content: chunk,
embedding,
metadata: { source: 'website', timestamp: new Date() }
});
}
}
private async generateEmbedding(text: string) {
// Implementation using OpenRouter or local embedding model
// Returns vector array
}
}
Rationale: Semantic chunking with overlap preserves context boundaries, improving retrieval accuracy. Storing metadata allows for filtering and auditing of the knowledge base.
4. Deploy to Railway
Configure Railway for deployment using a railway.json file. This ensures consistent environment variables and database provisioning.
{
"build": {
"builder": "NIXPACKS",
"buildCommand": "npm run build"
},
"deploy": {
"startCommand": "npm start",
"healthcheckPath": "/health",
"restartPolicyType": "ON_FAILURE",
"restartPolicyMaxRetries": 10
}
}
Rationale: Railway's Nixpacks builder automatically detects Node.js projects and handles dependencies. The healthcheck ensures the application is fully initialized before receiving traffic.
Pitfall Guide
Avoid these common mistakes to ensure a robust and maintainable deployment.
-
RAG Chunking Mismatch
- Issue: Using fixed-size chunks without overlap can split sentences or concepts, leading to incomplete context retrieval.
- Fix: Implement semantic chunking with a configurable overlap (e.g., 50-100 tokens) to preserve context boundaries.
-
OpenRouter Rate Limiting
- Issue: Free-tier models on OpenRouter may have strict rate limits, causing request failures during peak traffic.
- Fix: Implement request queuing and exponential backoff in the
OpenRouterGateway. Monitor usage via the OpenRouter dashboard.
-
Postgres Vector Drift
- Issue: Embedding models may be updated, causing inconsistencies between existing vectors and new queries.
- Fix: Version your embedding models and implement a re-indexing strategy when models change. Store the embedding model version in metadata.
-
Hexabot State Loss
- Issue: Ephemeral storage in containerized environments can lead to lost conversation state if not persisted.
- Fix: Configure Hexabot to use Postgres for session storage. Ensure the database connection is stable and retry logic is in place.
-
Prompt Injection Vulnerabilities
- Issue: Malicious users may attempt to inject prompts that bypass safety filters or extract sensitive data.
- Fix: Sanitize user inputs in Hexabot nodes. Use system prompts that explicitly forbid executing user instructions. Implement content moderation if necessary.
-
Ignoring Fallback Flows
- Issue: The bot may fail to retrieve relevant context, leading to generic or unhelpful responses.
- Fix: Design fallback nodes in Hexabot that trigger when retrieval confidence is low. Route these to human support or suggest alternative actions.
-
Hardcoded API Keys
- Issue: Embedding API keys in source code poses a security risk and complicates environment management.
- Fix: Use environment variables for all secrets. Leverage Railway's secret management to inject keys securely at runtime.
Production Bundle
Action Checklist
Decision Matrix
| Scenario | Recommended Approach | Why | Cost Impact |
|---|
| High Traffic Support | Use OpenRouter with caching layer | Reduces API calls and latency | Low (Free tier + cache) |
| Custom Model Requirements | Deploy local model via Ollama | Full control over model behavior | Medium (Compute costs) |
| Multi-Channel Deployment | Use Hexabot's channel integrations | Unified workflow across platforms | Low (No extra cost) |
| Strict Data Privacy | Self-host Postgres and Hexabot | Data remains within your infrastructure | High (Server costs) |
Configuration Template
Use this template for environment configuration in Railway.
# .env.template
HEXABOT_PORT=3000
OPENROUTER_API_KEY=sk-or-xxxxxxxxxxxxxxxx
DATABASE_URL=postgresql://user:password@host:port/dbname
APP_URL=https://your-app.railway.app
RAG_CHUNK_SIZE=512
RAG_OVERLAP=50
Quick Start Guide
- Install Dependencies: Run
npm install @hexabot/core axios @supabase/supabase-js langchain.
- Configure Environment: Copy
.env.template to .env and fill in your API keys and database URL.
- Initialize Workflow: Execute
hexabot init to generate the default flow configuration.
- Deploy: Push code to GitHub and connect repository to Railway for automatic deployment.
- Verify: Access the deployed URL and test the chat widget with sample queries to ensure RAG retrieval is functioning.