implemenation on the bot
This commit is contained in:
271
services/ai/seed/phrasings.json
Normal file
271
services/ai/seed/phrasings.json
Normal file
@@ -0,0 +1,271 @@
|
||||
{
|
||||
"_comment": "Example phrasings per intent, used to seed the intent_examples collection. These are deliberately OPERATOR language, not the developer language the regex matchers were written against — the whole point is to cover the phrasings nobody thought to write a pattern for. Do not add a phrasing here that the intent cannot actually answer.",
|
||||
|
||||
"totalOrders": [
|
||||
"how many orders today",
|
||||
"how many bookings do we have today",
|
||||
"order count for today",
|
||||
"what's today's order volume",
|
||||
"how many orders came in",
|
||||
"total orders today",
|
||||
"how many jobs today",
|
||||
"how much work came in today",
|
||||
"give me the order count",
|
||||
"how many did we get today",
|
||||
"orders received today",
|
||||
"number of orders"
|
||||
],
|
||||
|
||||
"weekOrders": [
|
||||
"how many orders this week",
|
||||
"orders for the week",
|
||||
"weekly order count",
|
||||
"how many orders last week",
|
||||
"orders this month",
|
||||
"how many bookings this month",
|
||||
"order volume this week",
|
||||
"what did we do last week",
|
||||
"how many orders in the last 7 days"
|
||||
],
|
||||
|
||||
"statusBreakdown": [
|
||||
"how many delivered orders today",
|
||||
"how many cancelled orders",
|
||||
"how many pending orders",
|
||||
"how many orders are assigned",
|
||||
"count of unassigned orders",
|
||||
"how many orders are out for delivery",
|
||||
"how many got cancelled today",
|
||||
"what's still pending",
|
||||
"how many are waiting to be picked up",
|
||||
"how many orders are in transit",
|
||||
"how many did we deliver",
|
||||
"how many orders got rejected"
|
||||
],
|
||||
|
||||
"orderQuery": [
|
||||
"delivered orders for Acme this week",
|
||||
"cancelled orders for Acme today",
|
||||
"how many orders did rider Suresh deliver today",
|
||||
"morning batch orders for Acme",
|
||||
"top 5 tenants by orders this week",
|
||||
"which tenant has the most orders",
|
||||
"busiest riders this week",
|
||||
"pending orders for Acme Foods",
|
||||
"rank tenants by order volume",
|
||||
"who delivered the most today"
|
||||
],
|
||||
|
||||
"orderTrend": [
|
||||
"orders per day this week",
|
||||
"orders per hour today",
|
||||
"daily order trend",
|
||||
"hourly breakdown of orders",
|
||||
"how are orders spread across the day",
|
||||
"show me the order trend",
|
||||
"when do most orders come in",
|
||||
"orders by day for last week",
|
||||
"what's our busiest hour"
|
||||
],
|
||||
|
||||
"orderRate": [
|
||||
"cancellation rate this week",
|
||||
"what percentage of orders get cancelled",
|
||||
"delivery success rate",
|
||||
"what percent were delivered",
|
||||
"cancellation percentage",
|
||||
"ratio of cancelled orders",
|
||||
"how often do orders get cancelled",
|
||||
"what's our delivery rate"
|
||||
],
|
||||
|
||||
"delayedOrders": [
|
||||
"which orders are delayed",
|
||||
"anything running late",
|
||||
"what's overdue",
|
||||
"how many orders are late",
|
||||
"which hubs are experiencing delays",
|
||||
"anything stuck",
|
||||
"are we behind on anything",
|
||||
"show me late deliveries",
|
||||
"orders past their delivery time",
|
||||
"what's at risk right now",
|
||||
"any SLA breaches",
|
||||
"which orders missed their promise"
|
||||
],
|
||||
|
||||
"batchCount": [
|
||||
"morning batch orders today",
|
||||
"how many in the afternoon batch",
|
||||
"evening batch count",
|
||||
"orders in the morning wave",
|
||||
"what's in the afternoon run",
|
||||
"how many orders in tonight's batch"
|
||||
],
|
||||
|
||||
"revenueTotal": [
|
||||
"total revenue today",
|
||||
"how much did we make today",
|
||||
"revenue this week",
|
||||
"what were the earnings today",
|
||||
"total charges for the week",
|
||||
"how much money came in",
|
||||
"collections today",
|
||||
"what's the total value of today's orders"
|
||||
],
|
||||
|
||||
"comparisonIntent": [
|
||||
"orders today vs yesterday",
|
||||
"compare this week to last week",
|
||||
"revenue this week compared to last week",
|
||||
"how does today compare to yesterday",
|
||||
"are we up or down on last week",
|
||||
"this month versus last month"
|
||||
],
|
||||
|
||||
"opsSummary": [
|
||||
"give me today's operations summary",
|
||||
"how are we doing today",
|
||||
"daily overview",
|
||||
"what's the situation right now",
|
||||
"summarise today",
|
||||
"ops snapshot",
|
||||
"give me the headline numbers",
|
||||
"how's everything looking",
|
||||
"today at a glance"
|
||||
],
|
||||
|
||||
"orderLookup": [
|
||||
"status of order #1234",
|
||||
"where is order DM-BK-123",
|
||||
"what happened to order 4821",
|
||||
"look up booking DM-BK-900",
|
||||
"check order #55",
|
||||
"is order 1234 delivered yet",
|
||||
"who's got order #4821"
|
||||
],
|
||||
|
||||
"parcelTrack": [
|
||||
"track consignment DM-CN-123",
|
||||
"where is parcel DM-CN-99",
|
||||
"track shipment ABC-123",
|
||||
"scan history for DM-CN-123",
|
||||
"what's happened to this consignment"
|
||||
],
|
||||
|
||||
"riderCounts": [
|
||||
"how many riders are active",
|
||||
"how many riders do we have out",
|
||||
"rider availability",
|
||||
"how many are on delivery",
|
||||
"who's available right now",
|
||||
"how many riders are free",
|
||||
"fleet status",
|
||||
"how many riders are offline"
|
||||
],
|
||||
|
||||
"riderLookup": [
|
||||
"where is rider Kumar",
|
||||
"find rider Suresh",
|
||||
"status of rider Ali",
|
||||
"look up rider Priya",
|
||||
"is Kumar online"
|
||||
],
|
||||
|
||||
"riderActivity": [
|
||||
"how is rider Kumar doing today",
|
||||
"rider Suresh performance",
|
||||
"how'd Kumar do today",
|
||||
"how many did rider Ali complete",
|
||||
"rider Priya stats",
|
||||
"how many did Kumar reject",
|
||||
"how far has rider Suresh travelled",
|
||||
"activity for rider Kumar"
|
||||
],
|
||||
|
||||
"tenantList": ["how many tenants do we have", "list all tenants", "how many clients", "show me our tenants", "tenant count"],
|
||||
|
||||
"tenantCount": [
|
||||
"orders for Acme today",
|
||||
"how many orders did Acme place",
|
||||
"Acme Foods order count",
|
||||
"how much work from Beta Kitchens"
|
||||
],
|
||||
|
||||
"tenantDetail": [
|
||||
"tell me about tenant Acme Foods",
|
||||
"details for Acme",
|
||||
"what do we know about Beta Kitchens",
|
||||
"Acme Foods locations",
|
||||
"show me tenant Acme"
|
||||
],
|
||||
|
||||
"hubStatus": [
|
||||
"current hub status",
|
||||
"how many hubs do we have",
|
||||
"are all hubs active",
|
||||
"hub overview",
|
||||
"which hubs are open"
|
||||
],
|
||||
|
||||
"hubLookup": ["status of hub Chennai", "find hub Coimbatore", "look up the Bengaluru hub"],
|
||||
|
||||
"vehicleStatus": [
|
||||
"how many vehicles are available",
|
||||
"vehicle count",
|
||||
"how many bikes do we have",
|
||||
"fleet vehicle status",
|
||||
"what vehicles are free"
|
||||
],
|
||||
|
||||
"vehicleLookup": ["find vehicle TN01AB1234", "status of vehicle TN37XY9", "look up vehicle KA05MM1"],
|
||||
|
||||
"tripsheetStatus": [
|
||||
"how many tripsheets are dispatched",
|
||||
"tripsheet count",
|
||||
"how many trips are running",
|
||||
"open tripsheets"
|
||||
],
|
||||
|
||||
"exceptionStatus": [
|
||||
"how many open exceptions",
|
||||
"any delivery exceptions",
|
||||
"what's gone wrong",
|
||||
"exception count",
|
||||
"failed deliveries"
|
||||
],
|
||||
|
||||
"consignmentStatus": ["how many consignments do we have", "consignment count", "total consignments"],
|
||||
|
||||
"customerCount": ["how many customers do we have", "customer count", "how many people have ordered", "total customers"],
|
||||
|
||||
"appUserCount": ["how many app users do we have", "staff login count", "how many console users"],
|
||||
|
||||
"pricingCount": ["how many pricing rules are configured", "pricing rule count", "how many tariffs do we have"],
|
||||
|
||||
"partnerCount": ["how many partners do we have", "partner count", "list our fleet partners"],
|
||||
|
||||
"competitorBranchCount": ["how many competitor branches are tracked", "competitor branch count", "competitive intel count"],
|
||||
|
||||
"carrierPricingCount": ["how many carrier pricing rules", "carrier tariff count", "carrier pricing entries"],
|
||||
|
||||
"createCustomer": [
|
||||
"create a customer",
|
||||
"add a new customer",
|
||||
"register a client",
|
||||
"I need to add a customer",
|
||||
"new customer please",
|
||||
"set up a customer record",
|
||||
"add customer Ramesh 9876543210"
|
||||
],
|
||||
|
||||
"createOrder": [
|
||||
"create an order",
|
||||
"book a delivery",
|
||||
"place a new order",
|
||||
"I need to create a booking",
|
||||
"new delivery please",
|
||||
"raise an order",
|
||||
"add a booking"
|
||||
]
|
||||
}
|
||||
148
services/ai/seed/run.js
Normal file
148
services/ai/seed/run.js
Normal file
@@ -0,0 +1,148 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { INTENTS_COLLECTION, DOCS_COLLECTION, resetCollection } from '../collections.js';
|
||||
import { embedMany, MODEL_ID } from '../embed.js';
|
||||
|
||||
// ==============================|| Seeding ||============================== //
|
||||
//
|
||||
// Rebuilds both collections from scratch. Idempotent — safe to re-run any time,
|
||||
// and it MUST be re-run after changing the embedding model or editing any
|
||||
// indexed document, or answers drift from the source without any error.
|
||||
|
||||
const HERE = path.dirname(fileURLToPath(import.meta.url));
|
||||
const REPO = path.resolve(HERE, '../../..');
|
||||
|
||||
// Write intents get a stricter confidence bar in the console (a semantic
|
||||
// near-miss must never open a create form), so they are flagged here.
|
||||
const WRITE_INTENTS = new Set(['createOrder', 'createCustomer']);
|
||||
|
||||
const DOMAIN = {
|
||||
orders: ['totalOrders', 'weekOrders', 'statusBreakdown', 'orderQuery', 'orderTrend', 'orderRate', 'delayedOrders', 'batchCount', 'revenueTotal', 'comparisonIntent', 'opsSummary', 'orderLookup', 'parcelTrack'],
|
||||
riders: ['riderCounts', 'riderLookup', 'riderActivity'],
|
||||
tenants: ['tenantList', 'tenantCount', 'tenantDetail'],
|
||||
fleet: ['hubStatus', 'hubLookup', 'vehicleStatus', 'vehicleLookup', 'tripsheetStatus', 'exceptionStatus', 'consignmentStatus'],
|
||||
admin: ['customerCount', 'appUserCount', 'pricingCount', 'partnerCount', 'competitorBranchCount', 'carrierPricingCount'],
|
||||
write: ['createCustomer', 'createOrder']
|
||||
};
|
||||
|
||||
const domainOf = (intentId) => Object.entries(DOMAIN).find(([, ids]) => ids.includes(intentId))?.[0] || 'other';
|
||||
|
||||
// ---- intent_examples --------------------------------------------------------
|
||||
const seedIntents = async () => {
|
||||
const raw = JSON.parse(fs.readFileSync(path.join(HERE, 'phrasings.json'), 'utf8'));
|
||||
const ids = [];
|
||||
const documents = [];
|
||||
const metadatas = [];
|
||||
|
||||
for (const [intentId, phrasings] of Object.entries(raw)) {
|
||||
if (intentId.startsWith('_')) continue;
|
||||
phrasings.forEach((phrase, i) => {
|
||||
ids.push(`${intentId}::${String(i).padStart(2, '0')}`);
|
||||
documents.push(phrase);
|
||||
metadatas.push({ intentId, domain: domainOf(intentId), isWrite: WRITE_INTENTS.has(intentId) });
|
||||
});
|
||||
}
|
||||
|
||||
const collection = await resetCollection(INTENTS_COLLECTION);
|
||||
const embeddings = await embedMany(documents);
|
||||
await collection.add({ ids, documents, metadatas, embeddings });
|
||||
return { intents: new Set(metadatas.map((m) => m.intentId)).size, vectors: ids.length };
|
||||
};
|
||||
|
||||
// ---- console_docs -----------------------------------------------------------
|
||||
//
|
||||
// Split on markdown headings, then hard-wrap long sections. The heading path is
|
||||
// prepended to every chunk so a retrieved passage carries its own context —
|
||||
// without it, a chunk reading "Don't do this" is worse than useless.
|
||||
const CHUNK_CHARS = 800;
|
||||
const OVERLAP = 100;
|
||||
|
||||
const DOC_SOURCES = [
|
||||
'express-console-api.md',
|
||||
'CLAUDE.md',
|
||||
'src/pages/api/CLAUDE.md',
|
||||
'src/pages/nearle/assistant/CLAUDE.md',
|
||||
'src/pages/nearle/assistant/ROADMAP.md',
|
||||
'src/pages/nearle/assistant/RAG_PLAN.md',
|
||||
'src/pages/nearle/dispatch/CLAUDE.md',
|
||||
'src/pages/nearle/orders/CLAUDE.md'
|
||||
];
|
||||
|
||||
const chunkMarkdown = (text, source) => {
|
||||
const out = [];
|
||||
const lines = text.split('\n');
|
||||
let heading = source;
|
||||
let buffer = [];
|
||||
|
||||
const flush = () => {
|
||||
const body = buffer.join('\n').trim();
|
||||
buffer = [];
|
||||
if (!body) return;
|
||||
for (let i = 0; i < body.length; i += CHUNK_CHARS - OVERLAP) {
|
||||
const slice = body.slice(i, i + CHUNK_CHARS).trim();
|
||||
if (slice.length > 60) out.push({ heading, text: `${heading}\n\n${slice}` });
|
||||
}
|
||||
};
|
||||
|
||||
for (const line of lines) {
|
||||
if (/^#{1,4}\s/.test(line)) {
|
||||
flush();
|
||||
heading = line.replace(/^#+\s*/, '').trim();
|
||||
continue;
|
||||
}
|
||||
buffer.push(line);
|
||||
}
|
||||
flush();
|
||||
return out;
|
||||
};
|
||||
|
||||
const seedDocs = async () => {
|
||||
const ids = [];
|
||||
const documents = [];
|
||||
const metadatas = [];
|
||||
|
||||
for (const rel of DOC_SOURCES) {
|
||||
const abs = path.join(REPO, rel);
|
||||
if (!fs.existsSync(abs)) {
|
||||
console.warn(`[seed] skipped missing ${rel}`);
|
||||
continue;
|
||||
}
|
||||
const chunks = chunkMarkdown(fs.readFileSync(abs, 'utf8'), rel);
|
||||
chunks.forEach((c, i) => {
|
||||
ids.push(`${rel}::${i}`);
|
||||
documents.push(c.text);
|
||||
metadatas.push({ source: rel, heading: c.heading, updatedAt: new Date().toISOString().slice(0, 10) });
|
||||
});
|
||||
}
|
||||
|
||||
const collection = await resetCollection(DOCS_COLLECTION);
|
||||
const embeddings = await embedMany(documents);
|
||||
// Chroma caps how much it will accept in one add; these corpora are small
|
||||
// but batch anyway so this doesn't become a surprise later.
|
||||
const BATCH = 200;
|
||||
for (let i = 0; i < ids.length; i += BATCH) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await collection.add({
|
||||
ids: ids.slice(i, i + BATCH),
|
||||
documents: documents.slice(i, i + BATCH),
|
||||
metadatas: metadatas.slice(i, i + BATCH),
|
||||
embeddings: embeddings.slice(i, i + BATCH)
|
||||
});
|
||||
}
|
||||
return { sources: DOC_SOURCES.length, vectors: ids.length };
|
||||
};
|
||||
|
||||
const main = async () => {
|
||||
console.log(`[seed] model: ${MODEL_ID}`);
|
||||
const intents = await seedIntents();
|
||||
console.log(`[seed] intent_examples: ${intents.vectors} vectors across ${intents.intents} intents`);
|
||||
const docs = await seedDocs();
|
||||
console.log(`[seed] console_docs: ${docs.vectors} chunks from ${docs.sources} sources`);
|
||||
console.log('[seed] done');
|
||||
};
|
||||
|
||||
main().catch((err) => {
|
||||
console.error('[seed] failed:', err.message);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user