WeLe Agentic AI: LangGraph multi-agent CRM assistant with voice
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
// ============================================
|
||||
// WeLe Agentic AI — service entrypoint.
|
||||
//
|
||||
// Runs alongside the CRM rather than inside it: the CRM stays a CRM, and the
|
||||
// assistant can be restarted, scaled or given a second channel without
|
||||
// touching it.
|
||||
// ============================================
|
||||
import express from 'express';
|
||||
import helmet from 'helmet';
|
||||
import cors from 'cors';
|
||||
import config from './config/index.js';
|
||||
import logger from './utils/logger.js';
|
||||
import { connectMongo, isMongoConnected } from './data/mongo.js';
|
||||
import { initRedis, getRedis, redisMode } from './data/redis.js';
|
||||
import agentRoutes, { artifactRouter } from './gateway/routes.js';
|
||||
import { ensureDir, sweep } from './output/artifactStore.js';
|
||||
import crmApi from './tools/http/crmApi.js';
|
||||
import { describeChains } from './orchestration/llm.js';
|
||||
import { attachVoice } from './gateway/voice.js';
|
||||
|
||||
const app = express();
|
||||
|
||||
app.use(helmet({ contentSecurityPolicy: false, crossOriginResourcePolicy: false }));
|
||||
app.use(cors({ origin: true, credentials: true }));
|
||||
app.use(express.json({ limit: '2mb' }));
|
||||
|
||||
app.use((req, _res, next) => {
|
||||
if (req.path !== '/health') logger.debug(`${req.method} ${req.path}`);
|
||||
next();
|
||||
});
|
||||
|
||||
app.get('/health', async (_req, res) => {
|
||||
const crm = await crmApi.health();
|
||||
const redisOk = await getRedis().ping().then(() => true).catch(() => false);
|
||||
const healthy = isMongoConnected();
|
||||
res.status(healthy ? 200 : 503).json({
|
||||
ok: healthy,
|
||||
service: 'wele-agentic-ai',
|
||||
mongo: isMongoConnected() ? 'connected' : 'disconnected',
|
||||
redis: redisOk ? redisMode() : 'unavailable',
|
||||
crm_api: crm.reachable ? 'reachable' : `unreachable (${crm.error || crm.status})`,
|
||||
models: describeChains(),
|
||||
uptime_s: Math.round(process.uptime()),
|
||||
});
|
||||
});
|
||||
|
||||
app.use('/api/agent', agentRoutes);
|
||||
app.use('/artifacts', artifactRouter);
|
||||
|
||||
app.use((_req, res) => res.status(404).json({ ok: false, error: 'Not found' }));
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
app.use((err, _req, res, _next) => {
|
||||
logger.error(`unhandled: ${err.stack || err.message}`);
|
||||
res.status(err.status || 500).json({ ok: false, error: err.message || 'Internal error' });
|
||||
});
|
||||
|
||||
async function start() {
|
||||
const configured = Object.entries(config.llm.providers).filter(([, p]) => p.apiKey).map(([n]) => n);
|
||||
if (!configured.length) {
|
||||
logger.error('No provider API key set (GMI_API_KEY / OPENROUTER_API_KEY) — the assistant cannot run. Add one to .env.');
|
||||
process.exit(1);
|
||||
}
|
||||
logger.info(` providers : ${configured.join(', ')}`);
|
||||
|
||||
await connectMongo();
|
||||
initRedis();
|
||||
await ensureDir();
|
||||
|
||||
// Housekeeping for expired generated files.
|
||||
sweep();
|
||||
setInterval(sweep, 6 * 3600_000).unref();
|
||||
|
||||
const server = app.listen(config.port, () => {
|
||||
logger.info(`🤖 WeLe Agentic AI on http://localhost:${config.port}`);
|
||||
const chains = describeChains();
|
||||
logger.info(` supervisor: ${chains.supervisor.join(' → ')}`);
|
||||
logger.info(` agents: ${chains.agent.join(' → ')}`);
|
||||
logger.info(` CRM API =${config.crmApi.base}`);
|
||||
});
|
||||
|
||||
// Voice is a WebSocket upgrade on the same port, so the browser needs no
|
||||
// second origin and the CRM token works unchanged.
|
||||
attachVoice(server);
|
||||
|
||||
const shutdown = (sig) => {
|
||||
logger.info(`${sig} — shutting down`);
|
||||
server.close(() => process.exit(0));
|
||||
setTimeout(() => process.exit(1), 10_000).unref();
|
||||
};
|
||||
process.on('SIGINT', () => shutdown('SIGINT'));
|
||||
process.on('SIGTERM', () => shutdown('SIGTERM'));
|
||||
}
|
||||
|
||||
start().catch((err) => {
|
||||
logger.error(`failed to start: ${err.stack || err.message}`);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user