Memória vetorial
Busca semântica: o agente grava textos e recupera por similaridade, não por ordem. Serve para uma base de conhecimento que não caberia no prompt inteiro.
Não confunda com ctx.state.memory
Aquele é o bucket de contexto durável, que entra sempre no prompt, na ordem que você colocou. Este aqui você grava muito e recupera só o relevante. Um é lembrete, o outro é biblioteca.
Registrar o banco
npm install @thenajs/qdrant-client// src/vector/qdrant.store.ts
import { QdrantStore } from "@thenajs/qdrant-client";
export class MeuQdrant extends QdrantStore {
constructor() {
super({ url: "http://localhost:6333", collection: "conhecimento" });
}
}// src/config.ts
export const config: ThenaConfig = {
memory: [MeuQdrant], // as classes; o framework instancia uma vez
};Um Qdrant local sobe com Docker:
docker run -d -p 6333:6333 qdrant/qdrantRequer Qdrant 1.10 ou superior
O endpoint de busca que o cliente usa estreou nessa versão.
Usar no agente
Quem quer memória pede no construtor — como as tools fazem com o WorkflowRuntime:
@Agent({ provider: LocalOllamaProvider, prompt: "./assistente.agent.md" })
export class AssistenteAgent {
constructor(@memory() private readonly mem: VectorMemory) {}
async beforePrompt(prompt: string, ctx: AgentContext) {
const pergunta = ctx.state.history.at(-1)?.content ?? "";
const achados = await this.mem.recall(pergunta, { limit: 3 });
if (!achados.length) return;
return `${prompt}\n\n## Contexto\n${achados.map((a) => `- ${a.text}`).join("\n")}`;
}
async afterResponse(resposta: string) {
await this.mem.remember(resposta);
}
}Agentes que não usam memória simplesmente não escrevem construtor.
Com mais de um store registrado, aponte qual você quer pela classe — assim a ordem do array em ThenaConfig.memory deixa de importar:
constructor(@memory(QdrantOpenAI) private readonly mem: VectorMemory) {}Os embeddings saem do provider do próprio agente. Aponte um modelo dedicado:
super({ host, model: "qwen2.5-coder:7b", embedModel: "nomic-embed-text" });As dimensões da collection são descobertas na primeira gravação — você não precisa saber que nomic-embed-text é 768.
Separar contextos
dataset particiona a mesma collection. É opcional; omitido, usa "default":
await this.memory.remember("fato duradouro", { dataset: "persistent" });
await this.memory.remember("algo desta sessão", { dataset: "sessao" });
await this.memory.recall("pergunta", { dataset: "persistent" }); // um
await this.memory.recall("pergunta"); // o "default"
await this.memory.recall("pergunta", { dataset: null }); // todos
await this.memory.forget({ dataset: "sessao" }); // limpa umDatasets não são collections separadas — são um campo indexado. É a recomendação do próprio Qdrant, e é o que permite buscar através de todos com dataset: null.
Quando buscar é decisão sua
O framework não injeta contexto sozinho. As duas formas comuns:
- Num hook (acima): o contexto sempre entra, e você controla corte e escopo.
- Numa tool: o modelo decide se precisa buscar.
@Tool({
name: "buscar_conhecimento",
description: "Busca informação na base de conhecimento da empresa.",
schema: z.object({ pergunta: z.string() }),
})
export class BuscarTool {
constructor(private readonly memory: VectorMemory) {}
async execute(@input() { pergunta }: { pergunta: string }) {
const achados = await this.mem.recall(pergunta, { limit: 5 });
return achados.length
? achados.map((a) => `[${a.score.toFixed(2)}] ${a.text}`).join("\n")
: "Nada encontrado.";
}
}Fora de um agente
import { VectorMemory } from "@thenajs/core";
const memoria = new VectorMemory({
store: new MeuQdrant(),
provider: new LocalOllamaProvider(),
});
await memoria.remember("o deploy roda pelo scripts/deploy.sh");
const achados = await memoria.recall("como faço deploy?", { limit: 3 });Outro banco
VectorStore é o contrato. Estenda e implemente seis métodos — a classe base cuida do transporte. Veja Referência · Banco vetorial.
