npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@neo4j-labs/nams-ai-provider

v0.2.0

Published

Neo4j Agent Memory (NAMS) provider for the Vercel AI SDK — persistent cross-session memory backed by Neo4j

Readme

@neo4j-labs/nams-ai-provider

Neo4j Labs Status: Experimental Community Supported

Community provider for the Vercel AI SDK that adds persistent cross-session memory to any language model, backed by the Neo4j Agent Memory Service (NAMS).

⚠️ Neo4j Labs Project

This project is part of Neo4j Labs and is actively maintained, but not officially supported. There are no SLAs or guarantees around backwards compatibility and deprecation. For questions and support, please use the Neo4j Community Forum.

On every turn, NAMS automatically retrieves relevant memories from the user's history and injects them into the prompt — then persists the response so future sessions remember it. No Neo4j infrastructure to manage.

What does it do?

Without this package, every chat session starts fresh — the model has no recollection of who the user is, what they've said before, or what decisions were made in prior conversations.

@neo4j-labs/nams-ai-provider wraps your existing AI model and transparently adds memory to every call:

  1. Before the model responds — NAMS searches its memory store for facts, preferences, and past interactions relevant to the current message, then injects them into the prompt automatically.
  2. After the model responds — NAMS persists the exchange (and optionally extracts entities into a Neo4j knowledge graph) so the next session can recall it.

The result: your AI remembers users across sessions without you changing your application logic.

User message
     │
     ▼
┌─────────────────────────────┐
│  NAMS: fetch relevant       │  ← searches long-term graph,
│  memories from Neo4j        │    past sessions, reasoning traces
└────────────┬────────────────┘
             │  memories injected into prompt
             ▼
┌─────────────────────────────┐
│  Your LLM (GPT, Claude…)    │  ← responds with full context
└────────────┬────────────────┘
             │  response
             ▼
┌─────────────────────────────┐
│  NAMS: persist & extract    │  ← stores turn, builds knowledge graph
└─────────────────────────────┘

Setup

1. Install the provider and its peer dependencies

npm install @neo4j-labs/nams-ai-provider ai @ai-sdk/provider @neo4j-labs/agent-memory zod
# from the repo root
cd typescript/packages/vercel-ai-provider
npm install
npm run build
npm pack   # then `npm install ../path/to/neo4j-labs-nams-ai-provider-0.1.0.tgz` in your app

2. Get a free API key at memory.neo4jlabs.com

MEMORY_API_KEY=sk-nams-...

Quick Start

Once installed and your API key is set, adding memory to your Vercel AI SDK app is a one-line model swap:

// Before: plain agent, no memory
import { openai } from '@ai-sdk/openai';
import {
  ToolLoopAgent,
  createUIMessageStream,
  createUIMessageStreamResponse,
  stepCountIs,
} from 'ai';

const agent = new ToolLoopAgent({
  model:        openai('gpt-5.4-mini'),
  instructions: 'You are a helpful assistant.',
  stopWhen:     stepCountIs(10),
});

const stream = createUIMessageStream({
  execute: async ({ writer }) => {
    const result = await agent.stream({ messages });
    writer.merge(result.toUIMessageStream());
  },
});

return createUIMessageStreamResponse({ stream });
// After: swap model → agent now remembers users across sessions
import { createNamsProvider } from '@neo4j-labs/nams-ai-provider';
import { openai } from '@ai-sdk/openai';
import {
  ToolLoopAgent,
  createUIMessageStream,
  createUIMessageStreamResponse,
  stepCountIs,
} from 'ai';

const nams = createNamsProvider({
  apiKey:       process.env.MEMORY_API_KEY!,
  baseProvider: openai,
  scope:        { userId: 'user-123' },  // identify the user
});

const agent = new ToolLoopAgent({
  model:        nams.languageModel('gpt-5.4-mini'),  // ← only change
  instructions: 'You are a helpful assistant.',
  stopWhen:     stepCountIs(10),
});

const stream = createUIMessageStream({
  execute: async ({ writer }) => {
    const result = await agent.stream({ messages });
    writer.merge(result.toUIMessageStream());
  },
});

return createUIMessageStreamResponse({ stream });

What happens automatically on every call:

  • Relevant memories for user-123 are fetched and prepended to the prompt
  • The model's response is saved back to memory for future sessions
  • No other code changes needed

Usage Modes

There are three ways to integrate NAMS depending on how much control you want:

| Mode | How it works | Best for | |------|-------------|----------| | Provider | Swap your model for a NAMS-wrapped one | Simplest integration, fully transparent | | Middleware | Wrap an existing model instance | When you already have a model configured | | Tools | Expose memory as explicit AI SDK tools (optionally merged with tools from an MCP server) | When you want the model to decide when to remember |

Switching modes is purely a code-level choice — all three use the same API key and environment variables (see Environment variables). Middleware and tools modes can also be combined on one agent — injected baseline context plus explicit memory tools.

How does this relate to @neo4j-labs/agent-memory/middleware/vercel-ai? The core SDK ships a minimal middleware for the AI SDK 4.x-era LanguageModelV1Middleware shape that injects current-conversation context. This package targets AI SDK v7 / LanguageModelV4 and adds a registrable ProviderV4, cross-session retrieval, optional graph extraction, explicit memory tools, and MCP tool merging. New projects should prefer this package.


Provider Mode (ProviderV4)

Drop NAMS into any Vercel AI SDK project as a standard ProviderV4. Memory is fully transparent — no tools, no system prompt changes needed.

import { createNamsProvider } from '@neo4j-labs/nams-ai-provider';
import { openai } from '@ai-sdk/openai';
import {
  ToolLoopAgent,
  createUIMessageStream,
  createUIMessageStreamResponse,
  stepCountIs,
} from 'ai';

// One instance per user session
const nams = createNamsProvider({
  apiKey:       process.env.MEMORY_API_KEY!,
  baseProvider: openai,               // any @ai-sdk/* provider
  scope:        { userId: session.userId },
});

const agent = new ToolLoopAgent({
  model:        nams.languageModel('gpt-5.4-mini'),
  instructions: 'You are a helpful assistant.',
  stopWhen:     stepCountIs(1),       // no tools needed in provider mode
});

const stream = createUIMessageStream({
  execute: async ({ writer }) => {
    const result = await agent.stream({ messages });
    writer.merge(result.toUIMessageStream());
  },
});

return createUIMessageStreamResponse({ stream });

Works with the provider registry:

import { createProviderRegistry as createRegistry } from 'ai';

const registry = createRegistry({
  nams: createNamsProvider({
    apiKey:       process.env.MEMORY_API_KEY!,
    baseProvider: openai,
    scope:        { userId },
  }),
});

const agent = new ToolLoopAgent({
  model:    registry.languageModel('nams:gpt-5.4-mini'),
  stopWhen: stepCountIs(1),
});

Middleware Mode

Wrap any existing model instance with memory — useful when you already configure your model elsewhere and just want to decorate it:

import { createNams } from '@neo4j-labs/nams-ai-provider';
import { openai } from '@ai-sdk/openai';
import {
  ToolLoopAgent,
  createUIMessageStream,
  createUIMessageStreamResponse,
  stepCountIs,
} from 'ai';

const nams  = createNams({ apiKey: process.env.MEMORY_API_KEY! });
const model = nams.wrap(openai('gpt-5.4-mini'), { userId: session.userId });

const agent = new ToolLoopAgent({ model, stopWhen: stepCountIs(1) });

const stream = createUIMessageStream({
  execute: async ({ writer }) => {
    const result = await agent.stream({ messages });
    writer.merge(result.toUIMessageStream());
  },
});

return createUIMessageStreamResponse({ stream });

Tools Mode

Expose query_memory and store_memory as explicit AI SDK tools. The model decides when to call them, and the calls are visible in your UI — useful for debugging or when you want the user to see memory activity:

import { createNams, enforceQueryMemory } from '@neo4j-labs/nams-ai-provider';
import { openai } from '@ai-sdk/openai';
import {
  ToolLoopAgent,
  createUIMessageStream,
  createUIMessageStreamResponse,
  stepCountIs,
} from 'ai';

const nams  = createNams({ apiKey: process.env.MEMORY_API_KEY! });
const tools = nams.tools({ userId: session.userId });

const agent = new ToolLoopAgent({
  model:        openai('gpt-5.4-mini'),
  instructions:
    'Before answering, consult memory with query_memory. When the conversation ' +
    'contains facts or preferences worth remembering, call store_memory before ' +
    'giving your final answer.',
  tools,
  prepareStep:  enforceQueryMemory(),
  stopWhen:     stepCountIs(10),
});

const stream = createUIMessageStream({
  execute: async ({ writer }) => {
    const result = await agent.stream({ messages });
    writer.merge(result.toUIMessageStream());
  },
});

return createUIMessageStreamResponse({ stream });

Tool descriptions and instructions are advisory — models routinely skip "bookkeeping" tool calls. enforceQueryMemory() makes retrieval mechanical instead: it checks the executed tool calls each step and, until query_memory has run, holds the model at toolChoice: 'required' — it can still call other tools in any order, but cannot finish with a text-only answer before querying memory. After graceSteps steps (default: 3) without a query, the next step forces query_memory directly, so the loop can never exhaust its budget without the query having run; { graceSteps: 0 } forces it as the literal first step. Keep graceSteps at least two below your stopWhen budget so the forced query and the final answer both fit. There is no equivalent forcing hook for store_memory (the loop ends when the model emits final text) — if persistence must be guaranteed, check steps in onFinish and call store_memory.execute() yourself, or use provider / middleware mode where both directions happen unconditionally in code.

Tools Mode with MCP (optional)

Still tools mode — not a separate mode. toolsWithMcp() connects to an MCP server and merges its tools with the NAMS memory tools, so one agent can remember and use external tooling. Requires the optional peer @ai-sdk/mcp (npm install @ai-sdk/mcp) — it is loaded lazily, only when an MCP config is passed.

import { createNams, enforceQueryMemory } from '@neo4j-labs/nams-ai-provider';
import { openai } from '@ai-sdk/openai';
import { ToolLoopAgent, stepCountIs } from 'ai';

const nams = createNams({ apiKey: process.env.MEMORY_API_KEY! });

const { tools, close } = await nams.toolsWithMcp(
  { userId: session.userId },
  {
    url:        'https://mcp.example.com/mcp',
    headers:    { Authorization: `Bearer ${token}` },
    toolPrefix: 'mcp_',
  },
);

const agent = new ToolLoopAgent({
  model:       openai('gpt-5.4-mini'),
  tools,       // query_memory + store_memory + mcp_* server tools
  prepareStep: enforceQueryMemory(),  // memory queried before answering, MCP tools in any order
  stopWhen:    stepCountIs(10),
});

try {
  const result = await agent.generate({ prompt: 'What did we decide last week?' });
  console.log(result.text);
} finally {
  await close(); // closes the MCP connection (no-op when MCP wasn't configured)
}

When the MCP config is omitted, toolsWithMcp(scope) behaves exactly like tools(scope) with a no-op close.

Combining tools mode with middleware (hybrid)

Middleware and tools modes are not mutually exclusive — one createNams instance can serve both. The middleware-wrapped model injects baseline memory context on every call, while the tools let the model run targeted searches and explicit writes on top. This mirrors the "core memory injected each turn + memory tool for on-demand search" pattern from the AI SDK custom memory tool guide:

const nams  = createNams({ apiKey: process.env.MEMORY_API_KEY! });
const scope = { userId: session.userId };

const agent = new ToolLoopAgent({
  model:    nams.wrap(openai('gpt-5.4-mini'), scope), // baseline context injected every call
  tools:    nams.tools(scope),                        // targeted search + explicit writes
  stopWhen: stepCountIs(10),
});

In this setup, skip enforceQueryMemory() — the middleware already guarantees retrieval unconditionally in code, so forcing query_memory as well would just spend an extra step re-fetching similar context. The enforcement hook is for pure tools mode, where the tool call is the only retrieval path.


Configuration

createNamsProvider({
  // Required
  apiKey:               string,
  baseProvider:         (modelId: string) => LanguageModelV4,
  scope:                { userId: string, conversationId?: string },

  // Optional
  endpoint?:            string,   // Default: https://memory.neo4jlabs.com/v1
  workspaceId?:         string,
  logger?:              NamsLogger, // warn/error sink for non-fatal errors. Default: console
  maxMemories?:         number,   // Max memories retrieved and injected into the prompt per turn (capped at 12). Default: 6
  persistInteractions?: boolean,  // Save each turn. Default: true
  extractionModel?:     LanguageModel, // Enables graph entity extraction
});

Environment variables

No environment variable selects or changes the mode — provider, middleware, and tools modes are chosen entirely in code, and all three read the same variables:

| Variable | Required | Used for | |----------|----------|----------| | MEMORY_API_KEY | yes | NAMS API key — pass it as apiKey (the examples and snippets read it from the environment) | | OPENAI_API_KEY | yes* | Read by @ai-sdk/openai; *swap for whichever @ai-sdk/* provider key your base model needs | | NAMS_DEMO_USER | no | Overrides the demo userId in the runnable examples | | NAMS_DEMO_MODEL | no | Overrides the demo model id (default: gpt-5.4-mini) in the runnable examples |


Graph Extraction (optional)

Pass extractionModel to build a real Neo4j entity graph from memories instead of storing flat text:

const nams = createNamsProvider({
  apiKey:          process.env.MEMORY_API_KEY!,
  baseProvider:    openai,
  scope:           { userId },
  extractionModel: openai('gpt-5.4-mini'),
});

"User is named Alex, works at TechCorp" becomes (Alex)-[:WORKS_AT]->(TechCorp) in the graph.

Note: relationship persistence depends on backend support. Where the NAMS API does not yet expose a relationship endpoint (the hosted REST API currently doesn't), extracted entities are stored and relationship writes are skipped with a warning — the graph gains edges automatically once the endpoint is available.


Memory Sources

NAMS searches four sources in parallel per turn:

| Source | What it stores | |--------|---------------| | Long-term graph | Facts, preferences, patterns (Neo4j entities + relationships) | | Current conversation | Messages in the active session (vector search) | | Cross-session | Messages from past conversations for the same user | | Reasoning traces | Prior step-by-step reasoning from agent runs |


Development

npm install        # install dev dependencies
npm test           # run the vitest suite (provider, tools, ProviderV4 surface)
npm run typecheck  # tsc --noEmit
npm run build      # tsup → dist/

Note on conversation caching: each provider / tools instance keeps its own conversation-id cache (scoped per MemoryClient). Create one instance per user session; instances never share cached conversation ids.

Links

Support

License

Apache-2.0 — see LICENSE.