@dupply/sdk
v3.0.0
Published
Platform-agnostic AI agent SDK for chat applications — WhatsApp (via WAHA), Telegram, Messenger, or any custom channel.
Maintainers
Readme
Dupply SDK (@dupply/sdk)
Construa agentes de IA em qualquer plataforma de chat, WhatsApp, Telegram, Messenger, webchat ou canal interno, sem reescrever sua lógica de negócio.
npm install @dupply/sdkO SDK é agnóstico ao canal e agnóstico ao LLM. O núcleo (engine, memória, tools, RAG, roteamento) não conhece nenhuma plataforma específica. Você liga o canal por um IAdapter e o modelo por um ILLMProvider. O único adapter de canal embutido é o WahaAdapter (WhatsApp via WAHA), mas qualquer chat funciona escrevendo um adapter pequeno.
Glossário (peças do SDK)
| Peça | O que é | Interface | Embutidos |
|------|---------|-----------|-----------|
| Adapter | Transporte de chat. Envia/recebe mensagens de uma plataforma. | IAdapter | WahaAdapter (WhatsApp/WAHA), ConsoleAdapter (terminal) |
| Provider | Provedor de LLM. Gera respostas. | ILLMProvider | OpenAIProvider, GeminiProvider |
| Engine | Orquestra o pipeline: memória, intenção, RAG, tools, áudio, envio. | DupplyEngine | núcleo |
| Memory | Histórico de conversa por usuário. | IMemory | InMemoryMemory (default), RedisMemory |
| KnowledgeBase | Documentos para RAG. | IKnowledgeBase | SimpleVectorKnowledgeBase |
| Tool | "Habilidade" que o LLM pode chamar (function calling). | ITool | você define |
| Client | Fachada que recebe webhooks e despacha para as engines. | DupplyClient | núcleo |
Resumindo: Adapter = por onde a conversa entra e sai, Provider = quem pensa, Engine = quem coordena, Memory/KnowledgeBase/Tool = recursos que a engine usa.
Instalação
npm install @dupply/sdkPré-requisitos: Node.js >= 18, TypeScript >= 5. O pacote é ESM ("type": "module") e também publica um build CJS.
Dependências de infra são opcionais (peerDependencies): o SDK só precisa de openai, @google/genai e zod (já incluídos). Para usar recursos opcionais, instale o que for usar:
| Recurso | Pacote a instalar |
|---------|-------------------|
| Memória persistente em Redis (RedisMemory) | redis |
| Fila / deduplicação distribuída (QueueService) | bullmq + ioredis |
| Servidor HTTP de exemplo (Express) | express (+ cors se quiser) |
A memória padrão é in-memory (InMemoryMemory), não requer Redis.
Quickstart mínimo (roda no terminal, sem WhatsApp)
Este exemplo usa o ConsoleAdapter: você digita no terminal e a IA responde. Não precisa de WAHA nem Redis, só uma chave da OpenAI. Salve como quickstart.ts e rode com npx tsx quickstart.ts.
import {
DupplyEngine,
OpenAIProvider,
ConsoleAdapter,
type ITool,
} from '@dupply/sdk';
// 1. Provider de LLM (implementa ILLMProvider)
const llm = new OpenAIProvider(process.env.OPENAI_API_KEY!, 'gpt-4o-mini');
// 2. Adapter de canal (implementa IAdapter). ConsoleAdapter = stdin/stdout.
const adapter = new ConsoleAdapter({ botLabel: 'Assistente' });
// 3. Engine. provider:'openai' liga o LLM real (default é 'mock').
const engine = new DupplyEngine(adapter, llm, {
provider: 'openai',
apiKey: process.env.OPENAI_API_KEY!, // necessário p/ tools + RAG + IntentRouter
systemPrompt: 'Você é um assistente prestativo. Responda de forma curta.',
});
// 4. Uma tool simples (function calling). O LLM decide quando chamar.
const horasTool: ITool = {
name: 'get_current_time',
description: 'Retorna a hora atual. Use quando perguntarem que horas são.',
parameters: { type: 'object', properties: {}, required: [] },
async execute() {
return { now: new Date().toLocaleTimeString('pt-BR') };
},
};
engine.registerTool(horasTool);
// 5. Escuta o adapter (modo polling). Digite uma mensagem e tecle Enter.
await engine.listen();Pontos importantes confirmados no código:
- O construtor da engine é
new DupplyEngine(adapter, llm, config, memory?). providernoconfigdefault é'mock'(responde uma string fixa de eco). Use'openai'para acionar ollmde verdade.apiKeynoconfighabilita os subsistemas que usam OpenAI diretamente (IntentRouter e ContextOrchestrator). ComOpenAIProvider, o cliente HTTP é reusado automaticamente, então passarapiKeyé o caminho recomendado.engine.listen()consome o canal via polling (bom paraConsoleAdapter/dev). Em produção com webhooks, use oDupplyClient(abaixo).
Quickstart de produção (WhatsApp via WAHA + webhook)
import express from 'express';
import {
DupplyClient,
DupplyEngine,
OpenAIProvider,
WahaAdapter,
} from '@dupply/sdk';
// 1. Adapter WhatsApp via WAHA. Note: usa `session`, NÃO `instance`.
const adapter = new WahaAdapter({
apiUrl: 'http://localhost:3000',
apiKey: process.env.WAHA_API_KEY!,
session: 'default',
});
// 2. Provider
const llm = new OpenAIProvider(process.env.OPENAI_API_KEY!, 'gpt-4o-mini');
// 3. Engine
const engine = new DupplyEngine(adapter, llm, {
provider: 'openai',
apiKey: process.env.OPENAI_API_KEY!,
systemPrompt: 'Você é um atendente da Empresa X.',
});
// 4. Client (recebe webhooks e despacha para a engine)
const client = new DupplyClient(adapter);
client.registerEngine(engine);
// 5. Webhook. Funciona com Express, Fastify, Hono, Next.js, etc.
const app = express();
app.use(express.json());
app.post('/webhook', async (req, res) => {
await client.handleWebhook(req.body);
res.json({ ok: true });
});
app.listen(3001);Para validar a assinatura HMAC do webhook (recomendado em produção), passe webhookSecurity ao DupplyClient e use client.handleWebhookSecure(rawBody, headers). Veja docs/14-errors.md e o handler em docs/01-architecture.md.
Trocar de canal ou de modelo é só trocar uma linha
// Canal: terminal (dev) → WhatsApp (prod) → seu canal custom
const a1 = new ConsoleAdapter();
const a2 = new WahaAdapter({ apiUrl, apiKey, session: 'default' });
const a3 = new MeuTelegramAdapter(token); // implemente IAdapter
// Modelo: OpenAI → Gemini → seu provider custom (ex: Claude)
const m1 = new OpenAIProvider('sk-...', 'gpt-4o-mini');
const m2 = new GeminiProvider('AIza...', 'gemini-2.5-flash');
const m3 = new MeuClaudeProvider('sk-ant-...'); // implemente ILLMProvider
const engine = new DupplyEngine(a2, m2, { provider: 'openai', apiKey: 'sk-...' });A lógica de IA, tools, memória e RAG não muda. Veja docs/02-adapters.md (adapter custom) e docs/03-llm-providers.md (provider custom).
Recursos
Tools (Function Calling)
engine.registerTool({
name: 'consultar_pedido',
description: 'Verifica status de um pedido. Use quando o cliente perguntar sobre o pedido.',
parameters: {
type: 'object',
properties: { pedido_id: { type: 'string' } },
required: ['pedido_id'],
},
async execute({ pedido_id }, ctx) {
return await db.getOrder(pedido_id as string); // string ou objeto serializável
},
});O ctx é um ToolContext com senderId, channelId e message. Veja docs/06-tools.md.
RAG (Base de Conhecimento)
import { SimpleVectorKnowledgeBase } from '@dupply/sdk';
const kb = new SimpleVectorKnowledgeBase();
await kb.addDocument('Política de reembolso: 7 dias corridos a partir da compra.', { source: 'faq' });
const engine = new DupplyEngine(adapter, llm, {
provider: 'openai',
apiKey: 'sk-...',
knowledgeBase: kb,
});A interface é addDocument(content, metadata?) / search(query, topK?) / clear(). Veja docs/07-knowledge-base.md.
Memória
import { RedisMemory } from '@dupply/sdk'; // requer `npm install redis`
// In-memory é o default (basta não passar memory):
const engineDev = new DupplyEngine(adapter, llm, config);
// Redis (produção): construtor (redisUrl, options?)
const memory = new RedisMemory('redis://localhost:6379', { ttlSeconds: 86400, maxMessages: 20 });
await memory.connect();
const enginePrd = new DupplyEngine(adapter, llm, config, memory);Veja docs/05-memory.md.
Multi-tenancy, um bot, múltiplas personas
engine
.registerInstance({ instanceId: 'loja_sp', systemPrompt: 'Atendente da Loja SP.' })
.registerInstance({ instanceId: 'loja_rj', systemPrompt: 'Atendente da Loja RJ.', tools: [toolRJ] });
// Roteamento automático pelo channelId da mensagemVeja docs/08-multi-tenancy.md.
Token Tracking, saiba quanto cada canal custa
import { UsageTracker } from '@dupply/sdk';
const tracker = new UsageTracker();
tracker.onTokensConsumed(e => {
console.log(`${e.instanceId}: ${e.totalTokens} tokens, $${e.costUsd?.toFixed(6)}`);
});
const llm = new OpenAIProvider('sk-...', 'gpt-4o', { usageTracker: tracker });Veja docs/10-token-tracking.md.
Transcrição de áudio (Whisper)
// Automático no OpenAIProvider: mensagens de voz são transcritas (whisper-1) antes do pipeline.
const llm = new OpenAIProvider('sk-...'); // transcribeAudio já incluídoO GeminiProvider não implementa transcribeAudio, então áudios são ignorados quando ele é o provider.
Structured Outputs (Zod)
import { z } from 'zod';
const LeadSchema = z.object({
nome: z.string(),
interesse: z.enum(['basico', 'pro', 'enterprise']),
message: z.string(), // se o schema tiver `message` (ou `resposta`), ele é enviado ao usuário
});
engine.registerInstance({ instanceId: 'captacao', outputSchema: LeadSchema });
const engine = new DupplyEngine(adapter, llm, {
provider: 'openai',
apiKey: 'sk-...',
onStructuredOutput: (data, meta) => saveLead(data, meta.userId), // tipado, validado
});Veja docs/11-structured-outputs.md.
Escrever seu próprio adapter (qualquer canal)
Estenda BaseAdapter (que já gerencia o registro de handlers) e implemente os métodos de envio:
import { BaseAdapter, type MediaOptions } from '@dupply/sdk';
export class MeuWebchatAdapter extends BaseAdapter {
readonly name = 'MeuWebchatAdapter';
async initialize(): Promise<void> { /* conectar à plataforma */ }
async sendText(to: string, text: string): Promise<void> {
await minhaPlataforma.send(to, text);
}
async sendMedia(to: string, media: string | Buffer, options?: MediaOptions): Promise<void> {
await minhaPlataforma.sendFile(to, media, options?.caption);
}
async sendPoll(to: string, name: string, options: string[], multiple?: boolean): Promise<void> {
await minhaPlataforma.sendPoll(to, name, options, multiple);
}
// Quando uma mensagem chega da sua plataforma, normalize e emita:
onIncoming(raw: any): void {
this.emitMessage({
id: raw.messageId,
from: raw.userId,
text: raw.text,
timestamp: Math.floor(Date.now() / 1000),
});
}
}getMediaAsBase64?(messageRaw) é opcional, implemente apenas se quiser visão/áudio. Veja o contrato completo e um exemplo de Telegram em docs/02-adapters.md.
Documentação
| Guia | Conteúdo | |------|---------| | Arquitetura | Fluxo completo, tipos centrais, camadas | | Adapters | IAdapter, WahaAdapter, ConsoleAdapter, adapter custom | | LLM Providers | OpenAIProvider, GeminiProvider, provider custom | | Engine | Pipeline, EngineOptions, callbacks, multi-tenancy | | Memória | InMemoryMemory, RedisMemory, interface IMemory | | Tools | ITool, ToolRegistry, ToolContext | | Knowledge Base | RAG, SimpleVectorKnowledgeBase, custom | | Multi-tenancy | registerInstance(), isolamento por canal | | Intent Router | Roteamento de baixo custo | | Token Tracking | UsageTracker, billing | | Structured Outputs | Zod schemas, strict mode | | Fila | BullMQ, deduplicação distribuída | | Connection Watcher | Reconexão automática, heartbeat | | Erros | Tipos de erro, retry, tratamento |
Licença
MIT, veja LICENSE.
