@shopeasy/knowledge
v0.1.0
Published
Gerencia a base de conhecimento (Markdown) do ShopEasy e monta o melhor contexto textual possível para enviar a uma IA sem RAG/vetores.
Maintainers
Readme
@shopeasy/knowledge
Biblioteca TypeScript responsável por gerenciar a base de conhecimento (Markdown) do ShopEasy e montar automaticamente o melhor contexto textual possível para enviar a uma IA que não possui RAG nem banco vetorial — o modelo só recebe texto puro.
A biblioteca organiza os arquivos .md, indexa tudo com busca textual (MiniSearch) e monta um contexto enxuto, relevante e dentro de um limite de caracteres.
Por que não RAG / embeddings?
O bot do ShopEasy usa um modelo que recebe apenas um bloco de texto como contexto. Não há banco vetorial nem busca semântica por embeddings — a relevância é calculada por busca textual (TF-IDF/BM25 via MiniSearch) sobre título, headings, keywords, nome do arquivo e conteúdo. Isso mantém a solução simples, previsível, sem custo de infraestrutura extra e fácil de depurar.
Instalação
npm install @shopeasy/knowledgeEstrutura esperada dos documentos
knowledge/
├── about.md
├── ai-guidelines.md # pinned: true — instruções de comportamento, sempre incluídas
├── payments.md
├── plans.md
├── features.md
├── technology.md
└── updates.mdCada arquivo é um único assunto, com frontmatter opcional:
---
title: Pagamentos
category: Payments
keywords: [pix, cartao, cripto]
---
# Pagamentos
O ShopEasy aceita PIX, cartão e criptomoedas.title— opcional. Se ausente, usa o primeiro heading; se não houver heading, deriva do nome do arquivo.id— opcional. Se ausente, o id é o caminho do arquivo relativo à pastaknowledge/, sem extensão (payments.md→payments,products/games.md→products/games).keywords— opcional. Se ausente, é derivado automaticamente do título e dos headings.pinned— opcional (boolean, padrãofalse). Documentos compinned: truesão sempre incluídos porbuildContext/buildPrompt, independente da busca — ideal para instruções de comportamento da IA (vejaknowledge/ai-guidelines.md).- Qualquer outro campo do frontmatter fica disponível em
document.metadata.
Uso básico
import { Knowledge } from "@shopeasy/knowledge";
const knowledge = new Knowledge({ directory: "./knowledge" });
await knowledge.initialize();
knowledge.search("pix");
// -> [{ document: {...}, score: 4.2, terms: ["pix"] }, ...]
knowledge.getDocument("payments");
// -> KnowledgeDocument | undefined
knowledge.listDocuments();
// -> KnowledgeDocument[]
const { text, documents, characters, tokens, truncated } = knowledge.buildContext({
query: "Como configurar pagamentos?",
maxTokens: 3000, // ou maxCharacters: 12000
});
// `text` é o bloco pronto para ser enviado como contexto ao modelo.
const { system } = knowledge.buildPrompt({ query: "Como configurar pagamentos?", maxTokens: 3000 });
// `system` já vem com um preâmbulo de instrução + o contexto acima — pronto para
// `client.messages.create({ system, ... })` (Anthropic) ou uma system message (OpenAI).
await knowledge.close(); // encerra o watcher, se houverAPI
new Knowledge(options: KnowledgeOptions)
| Opção | Tipo | Padrão | Descrição |
| ----------- | ---------------- | ----------- | -------------------------------------------------------------------- |
| directory | string | — | Pasta com os arquivos .md/.mdx. |
| watch | boolean | true | Recarrega automaticamente (hot reload) quando um arquivo muda. |
| encoding | BufferEncoding | "utf-8" | Encoding usado para ler os arquivos. |
| tokenizer | (text: string) => number | estimativa interna | Contador de tokens usado por maxTokens e pelo campo tokens. Passe um tokenizer real (tiktoken, @anthropic-ai/tokenizer, gpt-tokenizer...) para contagem exata; por padrão usa uma estimativa sem dependências (estimateTokens, exportado pela lib). |
await knowledge.initialize(): Promise<void>
Carrega todos os documentos e (se watch: true) inicia o monitoramento com chokidar. Deve ser chamado antes de qualquer outro método.
knowledge.search(query: string, options?: { limit?: number }): SearchResult[]
Busca textual considerando nome do arquivo, título, headings, keywords e conteúdo — ordenada por relevância (mais relevante primeiro). limit (padrão 10) limita a quantidade de resultados.
interface SearchResult {
document: KnowledgeDocument;
score: number;
terms: string[];
}A busca é preparada para perguntas em linguagem natural, não só palavras-chave soltas:
- Acentos são ignorados dos dois lados (indexação e consulta) —
"nao"encontra"não","comissao"encontra"comissão". - Stopwords em PT-BR/EN são descartadas (
"o","a","como","para","com","the","is"...), então uma pergunta como"Como funciona o pagamento com pix?"busca efetivamente porfunciona,pagamento,pix— sem o ruído das palavras funcionais diluir o ranking. - Prefixo e fuzzy matching estão habilitados (
pagamcasa compagamento; pequenos erros de digitação ainda encontram o termo certo).
knowledge.getDocument(id: string): KnowledgeDocument | undefined
Retorna um documento pelo id (frontmatter id, ou o caminho do arquivo sem extensão).
knowledge.listDocuments(): KnowledgeDocument[]
Retorna todos os documentos carregados.
knowledge.buildContext(options: BuildContextOptions): BuiltContext
interface BuildContextOptions {
query: string;
maxCharacters?: number; // pelo menos um dos dois é obrigatório
maxTokens?: number; // preferido: modelos de IA têm limite de contexto em tokens, não em caracteres
maxDocuments?: number; // opcional, sem limite por padrão
}
interface BuiltContext {
text: string; // texto final pronto para a IA
documents: KnowledgeDocument[]; // documentos efetivamente incluídos, na ordem em que aparecem
characters: number; // tamanho de `text` em caracteres
tokens: number; // tamanho de `text` estimado (ou exato, com tokenizer customizado) em tokens
truncated: boolean; // true se algum documento foi cortado ou deixado de fora
}Pesquisa os documentos relevantes para query, inclui os documentos pinned primeiro (independente da busca), remove duplicados, ordena por relevância e monta um único texto respeitando maxCharacters ou maxTokens:
# Documento: Instruções para IA
...
---
# Documento: Pagamentos
...
---
# Documento: Cupons
...
---Se o conteúdo de um documento não couber inteiro no espaço restante, ele é truncado em um limite de palavra (nunca corta uma palavra ao meio) e recebe o marcador [conteúdo truncado]. Documentos que não cabem de forma alguma são simplesmente deixados de fora — nunca excede o orçamento pedido.
// Por caracteres:
knowledge.buildContext({ query: "pix", maxCharacters: 4000 });
// Por tokens (recomendado ao montar o prompt de um modelo de IA):
knowledge.buildContext({ query: "pix", maxTokens: 1000 });knowledge.buildPrompt(options: BuildPromptOptions): AIPrompt
Açúcar sintático sobre buildContext: devolve tudo que buildContext devolve, mais um campo system — o texto pronto para virar o system prompt de uma chamada de IA (Anthropic, OpenAI ou qualquer SDK que aceite uma string de instrução + contexto).
interface BuildPromptOptions extends BuildContextOptions {
preamble?: string; // sobrescreve a instrução padrão do ShopEasy
}
interface AIPrompt extends BuiltContext {
system: string; // preamble + contexto, pronto para a chamada ao modelo
}const { system } = knowledge.buildPrompt({
query: userMessage,
maxTokens: 3000,
});
const response = await anthropic.messages.create({
model: "claude-sonnet-5",
system,
messages: [{ role: "user", content: userMessage }],
});Documentos com pinned: true (como knowledge/ai-guidelines.md) sempre entram no system, então instruções de comportamento não dependem de a pergunta do usuário "bater" com elas na busca.
await knowledge.close(): Promise<void>
Encerra o watcher (se estiver ativo). Seguro de chamar mesmo com watch: false.
Hot reload
Com watch: true (padrão), a biblioteca usa chokidar para monitorar a pasta knowledge/:
- arquivo alterado → apenas aquele documento é reprocessado (frontmatter, headings, keywords, conteúdo) e reindexado;
- arquivo criado → é carregado e indexado imediatamente;
- arquivo removido → é removido do índice e do cache.
Nada disso reinicia a aplicação nem recarrega os outros documentos.
Arquitetura
Cada responsabilidade vive em seu próprio módulo (princípio da responsabilidade única):
src/
├── loader/markdown-loader.ts # varre o diretório e lê os arquivos .md/.mdx (fs)
├── parser/document-parser.ts # gray-matter + unified/remark-parse -> KnowledgeDocument
├── store/document-store.ts # cache em memória (Map) por id e por arquivo
├── search/search-index.ts # MiniSearch: normalização (acentos/stopwords) e busca por relevância
├── context/context-builder.ts # monta o texto final respeitando maxCharacters/maxTokens + pinned
├── tokenizer.ts # estimateTokens (padrão) + tipo TokenCounter plugável
├── watcher/knowledge-watcher.ts # chokidar: notifica add/change/unlink por arquivo
├── knowledge.ts # orquestra tudo e expõe a API pública (buildContext/buildPrompt)
└── index.ts # exports públicosKnowledge (src/knowledge.ts) é a única classe que conhece todas as peças; cada peça individual (MarkdownLoader, DocumentParser, DocumentStore, SearchIndex, ContextBuilder, KnowledgeWatcher) é testável isoladamente e não depende das demais.
Escalabilidade
- O carregamento inicial lê os arquivos em paralelo e o índice do MiniSearch foi desenhado para milhares de documentos.
- O hot reload reprocessa apenas o arquivo alterado, não a base inteira.
buildContext/buildPromptnunca materializam mais texto do que o necessário: param de percorrer os resultados assim que o orçamento (caracteres ou tokens) acaba.
Tecnologias utilizadas
- TypeScript (strict)
- gray-matter — parsing de frontmatter
- unified + remark-parse — parsing de Markdown (headings)
- minisearch — busca textual por relevância
- chokidar — watch de arquivos para hot reload
Não é usado LangChain, LlamaIndex, banco vetorial ou embeddings.
Desenvolvimento
npm install
npm run build # gera dist/ (ESM + CJS + .d.ts) via tsup
npm test # roda a suíte de testes (Vitest)
npm run typecheck # apenas checa tipos
npm run example # roda examples/basic-usage.ts com tsxTestes
A suíte cobre cada módulo isoladamente e um fluxo de integração ponta a ponta (incluindo hot reload real em um diretório temporário):
test/parser.test.ts— frontmatter, título/keywords/id/pinned derivados, headings.test/loader.test.ts— leitura recursiva de diretórios.test/search.test.ts— ranqueamento, normalização de acentos/stopwords,update()/remove().test/context-builder.test.ts— formatação, deduplicação, truncamento, limite de caracteres/tokens/documentos.test/tokenizer.test.ts— estimativa de tokens.test/knowledge.test.ts— API pública (buildContext,buildPrompt, documentospinned) e hot reload (add/change/unlink).
