excelle-insights / ai-whatsapp
Standalone AI assistant package — trainable, reusable across apps and LLM providers, one AI for messaging, guidance, diagnosis and analysis
Requires
- php: >=8.1
- ext-json: *
- ext-pdo: *
- guzzlehttp/guzzle: ^7.0
- vlucas/phpdotenv: ^5.7
Requires (Dev)
- robmorgan/phinx: ^0.16
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-09-21 19:20:59 UTC
README
Standalone Composer package: one trainable AI core for WhatsApp auto-reply,
in-app user guidance, diagnosis support and business analysis — same install
pattern as excelle-insights/whatsapp, working with any LLM provider
(OpenAI, OpenRouter, Ollama, Together, vLLM…), not just OpenAI.
Name history: folder/repo are now
ai-assistant; own tables areai_assistant_*. The composer nameexcelle-insights/ai-assistantand the PHP namespaceExcelleInsights\AiAssistant\…are kept until hosts migrate (then they becomeexcelle-insights/ai-assistant/…\AiAssistant\…).
No edits to vendor/excelle-insights/whatsapp required.
Full step-by-step guides live in docs/ (install, connect,
knowledge, WhatsApp channel, one-AI router, config, troubleshooting, LLM
providers + table rename).
Features
- Hooks after
WhatsappService::processWebhookPayload()— no vendor edit. - One AI for all channels: WhatsApp reply, knowledge-based user guide, vehicle-diagnosis support, business analysis (host routes per intent).
- Any LLM via
Contracts\LlmClientInterface+Support\LlmFactory(LLM_PROVIDER=openai|openrouter|together|ollama|vllm|lmstudio|custom). - Trainable per
company_id: FAQs, services, pricing, How-To guides viaai_assistant_knowledge(embeddings + keyword search) and self-learningai_assistant_training. No company model? SetAI_ASSISTANT_TENANT_ID=0and pass0everywhere —company_idis only a scoping key, seedocs/02-connect-application.md. - Own tables (
AI_ASSISTANT_TABLE_PREFIX, defaultai_assistant; legacyAI_WHATSAPP_TABLE_PREFIXstill honoured) +SystemContextProviderInterfaceto pull host data without FK tangles. Rename migration included. - Async queue
ai_assistant_queue+whatsapp_conversations/whatsapp_messagesbridge.
Requirements
- PHP >= 8.1, PDO MySQL,
ext-json - For the WhatsApp channel: host app with
excelle-insights/whatsapptables (whatsapp_business_profiles,whatsapp_messages,whatsapp_conversations,whatsapp_credentials,whatsapp_access_tokens). Other channels need no WhatsApp tables. - Host
.envLLM (OpenAI default; alternatives indocs/08-llm-providers-and-rename.md):LLM_PROVIDER=openai LLM_API_KEY=sk-proj-... # or OPENAI_API_KEY (fallback) LLM_MODEL=gpt-4o # or OPENAI_MODEL (fallback) OPENAI_WHISPER_MODEL=whisper-1 LLM_REQUEST_TIMEOUT=30 AI_RATE_LIMIT_PER_HOUR=60 - Host
.envauto-reply + tables:# Toggle AI auto-reply for inbound WhatsApp messages. false/0 disables. AI_ASSISTANT_AUTO_REPLY=true # Canonical prefix for ai_assistant_knowledge / _sessions / _queue / _training / _schema / _reference. AI_ASSISTANT_TABLE_PREFIX=ai_assistant # Legacy fallbacks (pre-rename installs). Remove after migrating. AI_WHATSAPP_TABLE_PREFIX=ai_whatsapp # Optional fallback template id to queue when the 24h session window is closed. # 0 = try direct send anyway (will fail if window closed). Set to an Approved # whatsapp_templates.template_id to queue a template instead. AI_ASSISTANT_FALLBACK_TEMPLATE=0 - Migrations need
DB_HOST/DB_NAME/DB_USER/DB_PASSWORDin host.env(runtime PDO is injected by the host, or built fromDB_DSNby the facade).
Installation
1. Require via Composer
composer require excelle-insights/ai-assistant
Or for local dev add to host composer.json:
"repositories": [ {"type": "vcs", "url": "/usr/local/var/www/ai-assistant"} ], "require": { "excelle-insights/ai-assistant": "*" }
composer update excelle-insights/ai-assistant
2. Run package migrations (own phinx.php, like whatsapp package)
# AI package tables: vendor/bin/phinx migrate -c vendor/excelle-insights/ai-assistant/phinx.php # Or from package dir: cd /usr/local/var/www/ai-assistant && vendor/bin/phinx migrate -c phinx.php
Creates (prefix from TablePrefix::get(), default ai_assistant):
{prefix}_knowledge(company_id,category,title,content,embedding,source,status){prefix}_sessions(conversation_id,company_id,history_json,expires_at){prefix}_queue(conversation_id,wa_message_id,status,payload,attempts,error){prefix}_training(company_id,question,answer){prefix}_schema(learned field descriptions){prefix}_reference(lookup summaries: services + prices, counts)
Upgrading from ai_whatsapp_*? Migration 20260922000000 renames old → new,
data preserved — see docs/08-llm-providers-and-rename.md §3.
No company_id column added to vendor whatsapp_* tables.
3. Hook inbound (host adds 2 lines, no vendor edit)
In api/whatsapp/callback.php and api/src/Controllers/WhatsappController.php::webhookProcess() after WhatsappService::processWebhookPayload($payload):
if (class_exists(\ExcelleInsights\AiAssistant\Facade\AiAssistantManager::class)) { (new \ExcelleInsights\AiAssistant\Services\AiAssistantService())->onInboundMessages($results); }
$results is the array returned by processWebhookPayload (['type'=>'message', 'wa_message_id'=>..., 'conversation_id'=>...]).
4. Provide system context (host implements interface)
// api/src/Services/MaintainaContextProvider.php use ExcelleInsights\AiAssistant\Contracts\SystemContextProviderInterface; class MaintainaContextProvider implements SystemContextProviderInterface { public function getCompanyContext(int $companyId): array { $db = \ExcelleCore\Core\Database::getInstance()->getConnection(); $row = $db->query("SELECT name FROM companies WHERE id=$companyId")->fetch(); return ['name'=>$row['name'], 'services'=> $db->query("SELECT name FROM service_types WHERE company_id=$companyId")->fetchAll(PDO::FETCH_COLUMN)]; } public function getCustomerContext(int $conversationId): array { /* last 3 service_records like AiController.php:604 */ return []; } public function getCompanyKnowledge(int $companyId, string $query, int $k=5): array { /* FTS over ai_assistant_knowledge */ return []; } public function canAutoReply(int $conversationId): bool { return true; } // business hours, opt-in }
Pass to manager (any LlmClientInterface accepted — use LlmFactory::make()
for env-driven provider selection):
use ExcelleInsights\AiAssistant\Support\LlmFactory; $ai = new \ExcelleInsights\AiAssistant\Facade\AiAssistantManager( pdo: \ExcelleCore\Core\Database::getInstance()->getConnection(), contextProvider: new MaintainaContextProvider(), openAI: LlmFactory::make() // or new \ExcelleCore\AI\OpenAIClient() to reuse host's );
If you don't provide openAI, the package builds one via LlmFactory::make()
reading LLM_* (fallback OPENAI_*) from host .env.
Any app works here: any PDO, any SystemContextProviderInterface
implementation, any LlmClientInterface (LlmFactory::make() or your own) —
ExcelleCore\* above is just the MaintainA example.
5. Train
curl -X POST https://host/api/integrations/whatsapp/ai-knowledge/import \ -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \ -d '{"csv":"category,title,content\n\"Working Hours\",\"Opening hours\",\"Mon-Fri 8am-5pm, Sat 8-12\""}'
Or via service:
use ExcelleInsights\AiAssistant\Services\KnowledgeService; use ExcelleInsights\AiAssistant\Services\TrainingService; $knowledge = new KnowledgeService($pdo, LlmFactory::make()); $training = new TrainingService($pdo, $knowledge); $training->ingest($companyId, "Return Policy", "30 days...", "manual");
File import: POST https://host/api/integrations/whatsapp/ai-knowledge/import
(CSV; see docs/03-knowledge-training.md).
Configuration
config/ai-assistant.php (publish to host config/ if needed):
return [ 'table_prefix' => $_ENV['AI_ASSISTANT_TABLE_PREFIX'] ?? $_ENV['AI_WHATSAPP_TABLE_PREFIX'] ?? 'ai_assistant', 'llm' => [ // canonical — any OpenAI-compatible provider (see Support\LlmFactory) 'provider' => $_ENV['LLM_PROVIDER'] ?? 'openai', 'api_key' => $_ENV['LLM_API_KEY'] ?? $_ENV['OPENAI_API_KEY'] ?? '', 'base_url' => $_ENV['LLM_BASE_URL'] ?? $_ENV['OPENAI_BASE_URL'] ?? null, 'model' => $_ENV['LLM_MODEL'] ?? $_ENV['OPENAI_MODEL'] ?? 'gpt-4o', 'embedding_model' => $_ENV['LLM_EMBEDDING_MODEL'] ?? 'text-embedding-3-small', 'timeout' => (int)($_ENV['LLM_REQUEST_TIMEOUT'] ?? $_ENV['OPENAI_REQUEST_TIMEOUT'] ?? 30), 'whisper_model' => $_ENV['OPENAI_WHISPER_MODEL'] ?? 'whisper-1', ], 'auto_reply' => [ 'enabled' => ($_ENV['AI_ASSISTANT_AUTO_REPLY'] ?? $_ENV['AI_WHATSAPP_AUTO_REPLY'] ?? 'true') !== 'false', 'outside_session_use_template' => true, // 24h window → queue template 'fallback_template_id' => (int)($_ENV['AI_WHATSAPP_FALLBACK_TEMPLATE'] ?? 0), ], ];
How It Works
- Inbound
POST /api/integrations/whatsapp/webhook(router flattened{field,value}) →WhatsappService::processWebhookPayload()insertswhatsapp_messagesdirection=inbound+whatsapp_conversations+events. - Hook calls
AiAssistantService::onInboundMessages()for each newwa_message_id. - Context via
SystemContextProvider+ Knowledge viaKnowledgeService::search($companyId, $messageBody, 5). - Prompt built and sent via the configured LLM (
LlmClientInterface::chat(), default model fromLlmFactory::defaultModel()). - Reply via
WhatsappApi::sendTextMessage($from, $reply)ifisSessionActive()else enqueue towhatsapp_queuewith template.
Same core answers guide/diagnosis/analysis requests — the host routes per
intent (see docs/05-one-ai-router.md).
Pushing Changes
cd /usr/local/var/www/ai-assistant git add . git commit -m "feat: ..." git push origin main # origin = git@github.com:excelle-insights/ai-assistant.git # Host picks it up: cd /path/to/host/api && composer update excelle-insights/ai-assistant
(Cutting over to composer name excelle-insights/ai-assistant happens as a
separate versioned step once hosts are ready.)
References
- Step-by-step:
docs/(esp.08-llm-providers-and-rename.md) - Black-box proposal:
BLACKBOX_DECOUPLING_PROPOSAL.md - Host AI:
api/src/Controllers/AiController.phpcallAI(), host.envOPENAI_*/LLM_* - WhatsApp vendor:
api/vendor/excelle-insights/whatsapp/src/Facade/WhatsAppManager.php,WHATSAPP_MIGRATION_NOTES.md(host docs)