Vibe Engineering: Docusarus + Azure AI Search + Azure OpenAI = GenAI-based support portal in a few…
How we built a self-service documentation chatbot with human escalation in 2 days for vibebrowser.app using Azure’s AI stack
Author: Dzianis Vashchuk | Site: Medium | Published: 2025-12-30T04:19:53Z
Vibe Engineering: Docusarus + Azure AI Search + Azure OpenAI = GenAI-based support portal in a few minutes How we built a self-service documentation chatbot with human escalation in 2 days for …
How we built a self-service documentation chatbot with human escalation in 2 days for vibebrowser.app using Azure’s AI stackLive URL: https://docs.vibebrowser.appThe ProblemEvery SaaS product faces the same support challenge: users have questions at all hours, documentation exists but nobody reads it, and support teams get overwhelmed with repetitive queries. We needed a solution that could:Answer questions 24/7 using our existing documentationKnow when to escalate to humansCost less than $30/month to runDeploy automatically when docs changeSystem ArchitectureVIBE BROWSER DOCUMENTATION PORTAL https://docs.vibebrowser.app+| || +| | | | | | | || | Docusaurus || | Static Site | | Apps (Standard) | | (gpt-5-mini) | || | + Chat Widget | | + Managed Functions | | | || | | | | +| +| | || v || +| | | || | Azure AI Search | || | (Free tier) | || | Vector + Semantic | || | | || +| || +| | | || | Azure Communication || | Services (Email) | || | | || +| |+Request FlowHere’s how a user question flows through the system:User Types Question | v+| Chat Widget || (React) |+ | | POST /api/chat v+| Azure || Function |+ | | 1. Extract user query v+| Azure AI || Search |<+ | | 2. Return top 5 relevant docs v+| Azure OpenAI || gpt-5-mini |+ | | 3. Generate response with context v+| Response + || Suggestions |+ | | (if user clicks escalate) v+| /api/escalate |+ | v+| Azure Comm || Services |+ | v Email to Support Team (with full transcript)The Tech StackDocumentation StorageWe use Docusaurus for documentation with a simple structure:services/docusarus/+-- docs/ | +-- intro.md | +-- using-copilot.md | +-- providers.md | +-- settings.md | +-- troubleshooting.md | +-- refunds.md | +-- getting-started/| +-- extension.md | +-- configuration.md |+-- docusaurus-azure-chat/ | +-- src/components/ | +-- api/ | +-- chat/index.js | +-- escalate/index.js |+-- scripts/| +-- index-to-azure-search.ts |+-- terraform/ +-- main.tf Each markdown file uses simple frontmatter:---title: Getting Startedsidebar_position: 1slug: /getting-started---Your content here...The Indexing PipelineWhen documentation changes, we need to update the search index. The indexer:Parses all markdown files from the docs/ directoryExtracts frontmatter for titles and metadataChunks content into ~2000 character piecesCleans formatting (removes code blocks, markdown syntax)Uploads to Azure AI Search in batches of 100docs/*.md | | | | v v v v Extract ~2000 Remove Azure AI title + chars markdown Search metadata each syntax IndexThe search index schema supports both vector and semantic search:{ "fields": [ {"name": "id", "type": "Edm.String", "key": true}, {"name": "title", "type": "Edm.String", "searchable": true}, {"name": "content", "type": "Edm.String", "searchable": true}, {"name": "url", "type": "Edm.String", "filterable": true}, {"name": "content_vector", "type": "Collection(Edm.Single)", "dimensions": 1536, "vectorSearchProfile": "hnsw-cosine"} ], "semantic": { "configurations": [{ "prioritizedFields": { "titleField": {"fieldName": "title"}, "contentFields": [{"fieldName": "content"}] } }] }}Deployment PipelineWe use a single deployment script that handles everything:./deploy.sh [--skip-resources] [--skip-build] [--skip-index]deploy.sh execution flow:+| Check Azure Login |+ | v+| Create Resources | <| - Azure OpenAI || - AI Search || - Static Web Apps |+ | v+| Fetch Credentials | <+ | v+| Create Search Index | <+ | v+| Build Docusaurus | <+ | v+| Deploy to SWA | <+ | v+| Index Documentation | <+GitHub Actions IntegrationFor CI/CD, detect changes in the docs directory using path filters:name: Deploy Docson: push: branches: [main] paths: - 'services/docusarus/docs/' - 'services/docusarus/docusaurus-azure-chat/'jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Azure Login uses: azure/login@v1 with: creds: ${{ secrets.AZURE_CREDENTIALS }} - name: Deploy run: | cd services/docusarus ./deploy.shThe paths filter ensures deployments only trigger when documentation actually changes, not on every commit to the repository.The Chat Agent PromptThe key to good AI support is a well-crafted system prompt:const systemPrompt = You are a helpful documentation assistant for Vibe Browser.IMPORTANT GUIDELINES:1. If you find relevant information in the docs, provide a clear answer with specific steps when applicable.2. If the question is about account management, billing, subscription cancellation, refunds, or technical issues NOT covered in the docs, respond helpfully and suggest escalation.3. If the user seems frustrated, confused, or explicitly asks to speak with someone, proactively suggest the "Talk to a Human" option.4. For technical troubleshooting not in docs, provide what help you can and suggest escalation for complex issues.SUGGESTION FORMAT:At the end of EVERY response, include 1-3 clickable suggestions wrapped in <suggestion> tags:- <suggestion>Talk to a Human</suggestion> - for support escalation- <suggestion>How do I install the extension?</suggestion> - follow-up- <suggestion>What models are available?</suggestion> - related topicAlways include at least one suggestion. For support-related queries, always include "Talk to a Human" as a suggestion.DOCUMENTATION:${docsContext};Key principles:Be helpful first — Try to answer from docs before escalatingDetect frustration — Proactive human escalation for upset usersAlways provide next steps — Suggestions keep the conversation flowingKnow your limits — Billing/account issues always offer escalationHuman Escalation Flow+| | | | | || User clicks || "Talk to Human" | | Modal opens | | support team || | | | | |+ | v + | Collects: | | - User email | | - Message | | - Chat history | | - Page URL | +The escalation email includes:Ticket ID — Generated from timestamp + random stringFull chat transcript — HTML formatted for readabilityUser’s email — Set as reply-to for direct responsePage URL — Context about where the user wasconst emailMessage = { senderAddress: "noreply@vibebrowser.app", subject: `[Docs Support recipients: { to: [{ address: supportEmail }] }, replyTo: [{ address: userEmail }] // Direct reply to user};Email Infrastructure with TerraformWe use Terraform to manage email infrastructure:resource "azurerm_communication_service" "docs" { name = "vibebrowser-docs-comm" resource_group_name = data.azurerm_resource_group.vibe.name data_location = "United States"}resource "azurerm_email_communication_service" "docs" { name = "vibebrowser-docs-email" resource_group_name = data.azurerm_resource_group.vibe.name data_location = "United States"}resource "azurerm_email_communication_service_domain" "custom" { name = "vibebrowser.app" email_service_id = azurerm_email_communication_service.docs.id domain_management = "CustomerManaged"}DNS records are automatically created in Cloudflare:Domain verification TXT recordSPF record (merged with existing email config)DKIM CNAME records (2x for redundancy)Why Azure Made This EasyAzure Static Web Apps — Zero-config hosting with built-in Azure FunctionsAzure AI Search Free Tier — Vector search without upfront costsAzure OpenAI — Enterprise-grade LLM with predictable pricingAzure Communication Services — Email without managing SMTP serversTerraform azurerm provider — Infrastructure as code for everythingThe entire infrastructure deploys in ~10 minutes and costs under $30/month.ResultsAfter deploying this system:80% of questions answered without human interventionAverage response time under 2 secondsSupport ticket volume reduced by 60%User satisfaction improved (they get answers at 3 AM)Quick StartPrerequisitesAzure CLI: brew install azure-cliNode.js 18+Deploy./deploy.sh# Or skip steps if resources already exist:./deploy.sh --skip-resources # Skip Azure resource creation./deploy.sh --skip-build # Skip npm build./deploy.sh --skip-index # Skip documentation indexingTestcurl -X POST "https://docs.vibebrowser.app/api/chat" \ -H "Content-Type: application/json" \ -d '{"messages":[{"role":"user","content":"How do I configure my API key?"}]}'API EndpointsEndpointMethodDescription/api/chatPOSTChat with documentation/api/escalatePOSTHuman escalation with transcript/api/healthGETHealth checkEnvironment VariablesSet automatically by deploy.sh:AZURE_OPENAI_ENDPOINT=AZURE_OPENAI_API_KEY=...AZURE_OPENAI_DEPLOYMENT=gpt-5-miniAZURE_SEARCH_ENDPOINT=https://vibebrowser-docs-search.search.windows.netAZURE_SEARCH_API_KEY=...AZURE_SEARCH_INDEX_NAME=docs-indexAZURE_COMMUNICATION_CONNECTION_STRING=...SUPPORT_EMAIL=support@example.comModel Constraints (gpt-5-mini)Use max_completion_tokens (not max_tokens)Temperature fixed at 1.0 (custom values not supported)Custom DomainConfigured via Cloudflare DNS:CNAME: docs -> yellow-forest-0f1c0f40f.6.azurestaticapps.netBuilt with Azure Static Web Apps, Azure OpenAI, Azure AI Search, and Azure Communication Services.