AI Technical Sales Co-Pilot
Describe a prospect's tech stack in plain English — get back a structured Cloudflare migration plan, component mapping, cost comparison, and 3-phase migration path. Grounded in a 80-chunk RAG knowledge base of real product documentation.
The Problem
Before a customer call, a Solutions Engineer needs to know: which Cloudflare products map to what the prospect is currently using, what the migration complexity looks like, and how to frame the cost comparison. That research takes 30-60 minutes manually — and the quality varies by how well the SE knows each product area.
The alternative is a hallucinating chatbot that confidently makes up product names and pricing. That's worse than doing it manually.
What I Built
Two agents with a keyword-based orchestrator that routes requests without an LLM call:
Research Agent — given a company name, it searches the web (DuckDuckGo), scrapes their homepage for tech stack signals, fetches recent news (NewsAPI), and synthesizes a structured company profile with Cloudflare opportunities. Takes 10-15 seconds. Replaces 30-60 minutes of manual pre-call research.
Architecture Agent — given a tech stack description ("They use AWS Lambda, CloudFront, S3, and Auth0"), it extracts every component via keyword map (no LLM needed for extraction), queries a 80-chunk Vectorize knowledge base for each component in parallel, maps each to its Cloudflare equivalent with migration complexity, generates a 3-phase migration plan, and estimates cost savings. Takes 25-40 seconds for 3-5 components.
The knowledge base has 80 hand-written chunks covering 20 Cloudflare products — 4 chunk types per product: overview, competitor comparison, pricing tiers, and use cases. Grounding the architecture agent in this KB means it never invents product names or pricing figures.
Architecture
POST /api/v1/copilot
│
▼
ORCHESTRATOR
├─ Keyword classifier (regex, 0ms) ─→ skips LLM for obvious patterns
├─ LLM fallback (Workers AI, 5-10s) ─→ for ambiguous messages
├─ Route to agent
├─ KV: store session history (fire-and-forget)
└─ D1: write audit log (fire-and-forget)
│
├──────────────────┬──────────────────┐
▼ ▼
Research Agent Architecture Agent
├─ Web search ├─ Keyword extraction (regex map)
├─ Web scraper ├─ Vectorize RAG (parallel queries)
└─ News API └─ 3-phase migration plan Technical Deep Dive
Orchestrator design: keyword-first, LLM-fallback
Most requests contain obvious patterns ("Research Stripe", "They use Lambda"). A regex keyword classifier handles these in 0ms — no LLM call. Only genuinely ambiguous messages trigger a Workers AI classification call (~5-10s). This optimization cuts response time by 5-10s for 80%+ of requests. The architecture agent also extracts tech stack components via keyword map rather than LLM — same idea applied to component extraction.
Parallel Vectorize queries
For a 5-component tech stack, the architecture agent fires 5 Vectorize queries simultaneously with Promise.all. Each query returns the top-2 relevant chunks for that component. Results are deduplicated and capped at 10 total chunks to keep the prompt under ~4,000 tokens. Total Vectorize query time: ~1-3s regardless of component count.
Knowledge base construction
80 chunks covering 20 Cloudflare products, each with 4 chunk types: overview (what it is, key features), comparison (vs specific named competitors), pricing (exact tiers with real dollar amounts), and use-case (startup and enterprise scenarios). Hand-written rather than scraped — scraped docs produce inconsistent chunks that embed poorly. Each chunk is ~250-400 words, sized for the BGE-base-en-v1.5 embedding model's optimal input length.
Session memory with KV
Conversation history is stored per session ID in KV. Each session key stores a JSON array of turns. KV reads are sub-10ms globally, and history is injected into the prompt on every request for continuity across multiple questions in the same research session.
Live Endpoints
GET /health Health check — returns version and active agents POST /api/v1/copilot Main endpoint — send a message, get agent output GET /session/:id Retrieve conversation history for a session # Research a company
curl -X POST https://ai-sales-copilot.stephenmack96.workers.dev/api/v1/copilot \
-H "Content-Type: application/json" \
-d '{"session_id":"demo-001","message":"Research Stripe"}'
# Design an architecture
curl -X POST https://ai-sales-copilot.stephenmack96.workers.dev/api/v1/copilot \
-H "Content-Type: application/json" \
-d '{"session_id":"demo-002","message":"They use Lambda, CloudFront, S3, and Auth0"}' Cloudflare Products Used
Workers — Hono router, orchestrator, agent dispatch, web scraping, news API integration. All compute at the edge with zero cold starts.
Workers AI — Llama 3 8B for chat generation, BGE-base-en-v1.5 for embedding 80 product chunks. LLM fallback for intent classification when keyword matching is insufficient.
Vectorize — 80 vectors, cosine similarity. Parallel queries per component, results merged by score and capped at 10 chunks for prompt injection.
KV — Session history storage. One key per session ID, JSON array of conversation turns. Sub-10ms reads globally.
D1 — Audit log: every agent run logged with latency, session ID, and model used.
Performance
| Query | Components | Latency |
|---|---|---|
| Single component lookup | 1 | ~24s |
| Vercel + Supabase + Auth0 | 3 | ~36s |
| Lambda + CloudFront + S3 + RDS + VPN | 5 | ~42s |
Latency is dominated by Workers AI (Llama 3 8B) generation time. Routing, embedding, and Vectorize queries account for under 3s total. Streaming (not implemented in this version) would reduce perceived latency significantly.
What I Learned
- • Keyword classifiers beat LLMs for intent routing. 80%+ of requests match obvious patterns. A regex classifier adds 0ms overhead vs 5-10s for an LLM call. Only use the LLM when the classifier genuinely can't decide.
- • Hand-write your KB chunks. Scraping product docs produces inconsistent chunk quality that embeds poorly. 80 hand-written chunks with consistent structure outperform 500 scraped chunks in retrieval quality.
- • Store content in vector metadata at upsert time. Retrieving chunk text after a Vectorize search without storing it in metadata requires a second lookup — a round trip you can eliminate entirely.
- • Fire-and-forget is correct for audit writes. KV session writes and D1 audit logs don't need to block the response.
ctx.waitUntil()orPromise.allSettled()keeps them non-blocking but guarantees they complete.
Career Relevance
- • First production LLM application — deployed, live, and callable. The knowledge base design and RAG retrieval pattern is the foundation that SE Intel (the next project) builds on.
- • Demonstrates production RAG design: parallel Vectorize queries, chunk capping for prompt budget management, metadata-stored content for zero round-trips.
- • The orchestrator pattern — keyword classifier first, LLM fallback — is the right engineering tradeoff for cost and latency at scale. Not every decision needs an LLM.