Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
168 changes: 138 additions & 30 deletions docs/integrations/frameworks/vercel-ai-sdk.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,58 +5,166 @@ sidebar_position: 1

# Vercel AI SDK

:::note[Planned]
This adapter is on the roadmap. AtomicMemory's SDK already runs in any Node / edge / browser context where the Vercel AI SDK runs, but the packaged adapter (`@atomicmemory/vercel-ai`) with `streamText` middleware is not yet shipped. See the manual pattern below to compose it today.
:::danger[Source-only]
`@atomicmemory/vercel-ai` lives in the integrations repo under `adapters/vercel-ai-sdk`. It is not published to npm yet; install it from a local clone or workspace package until a registry release is cut.
:::

## Intended shape
The adapter gives Vercel AI SDK calls the same memory loop used elsewhere in AtomicMemory:

- retrieve relevant memories before a model call
- render them as a guarded system message
- ingest the completed turn after the model returns
- keep the adapter independent of `ai` package version churn

## Primitives

| API | Use when |
|---|---|
| `retrieve()` | Tool-call or multimodal flows where you inject memory into the original AI SDK message array yourself. |
| `augmentWithMemory()` | Text-only flows where prepending a system message to `Message[]` is safe. |
| `ingestTurn()` | Post-call capture. Persists the original messages plus the assistant completion. |
| `withMemory()` | Text-only convenience wrapper around `augmentWithMemory()` + your model call + `ingestTurn()`. |
| `fromModelMessage()` / `fromModelMessages()` | AI SDK v5 bridge from `ModelMessage` content parts into AtomicMemory's text-only `Message` shape. |

The adapter intentionally does **not** import from `ai`. It accepts AtomicMemory SDK `Message` objects (`role` + string `content`) and delegates the model call to your code.

## Install from source

From the integrations repo:

```bash
pnpm install
pnpm --filter @atomicmemory/vercel-ai build
```

Then depend on the local package from your workspace, or use your package manager's `file:` / workspace linking support. The current source package depends on the local `@atomicmemory/atomicmemory-sdk` checkout, so publish order matters: publish or link the SDK first, then the adapter.

## Configure memory

```ts
import { streamText } from 'ai';
import { withMemory } from '@atomicmemory/vercel-ai';
import { MemoryClient } from '@atomicmemory/atomicmemory-sdk';

const memory = new MemoryClient({
providers: { atomicmemory: { apiUrl: process.env.ATOMICMEMORY_URL } },
providers: {
atomicmemory: {
apiUrl: process.env.ATOMICMEMORY_API_URL!,
apiKey: process.env.ATOMICMEMORY_API_KEY,
},
},
defaultProvider: 'atomicmemory',
});

const result = streamText({
model, // any AI SDK model — plain string routes via AI Gateway
await memory.initialize();
```

Use one stable `scope` for both retrieval and ingest:

```ts
const scope = {
user: userId,
namespace: projectId,
};
```

## Text-only flow

Use `withMemory()` when your messages are plain string-content messages:

```ts
import { streamText } from 'ai';
import { withMemory } from '@atomicmemory/vercel-ai';

const result = await withMemory({
client: memory,
scope,
messages,
experimental_transform: withMemory(memory, {
scope: { user: userId },
ingestOnFinish: true,
}),
async run(augmented) {
const response = streamText({ model, messages: augmented });
return { text: await response.text };
},
});
```

The adapter:
## Tool-call or multimodal flow

- **Retrieves** relevant memories before the LLM call and injects them into the system prompt.
- **Ingests** the final turn after `onFinish`, using AtomicMemory's AUDN mutation so duplicates don't accumulate.
- **Honors capabilities** — if the backing provider doesn't support packaging or trust, the adapter degrades gracefully.
Do not feed flattened memory messages back into Vercel AI SDK once tool or multimodal parts are present. Flatten only for memory search / ingest, then inject the retrieved system message into your original `ModelMessage[]`.

## Manual pattern (today)
```ts
import { generateText, type ModelMessage } from 'ai';
import {
fromModelMessages,
retrieve,
ingestTurn,
} from '@atomicmemory/vercel-ai';

Until `@atomicmemory/vercel-ai` ships, compose manually around `streamText`:
const modelMessages: ModelMessage[] = [/* your real conversation */];
const flat = fromModelMessages(modelMessages);

```ts
const context = await memory.search({ query, scope });
const result = streamText({
const { systemMessage } = await retrieve(memory, {
messages: flat,
scope,
limit: 8,
});

const { text } = await generateText({
model,
system: `Prior context:\n${context.memories.map(m => m.content).join('\n')}`,
messages: systemMessage
? [{ role: 'system', content: systemMessage.content }, ...modelMessages]
: modelMessages,
});

await ingestTurn(memory, {
messages: flat,
completion: text,
scope,
});
```

## Ingest policy

`ingestTurn()` uses SDK `mode: "messages"` so AtomicMemory's AUDN mutation can add, update, delete, or no-op extracted facts instead of accumulating duplicates.

System messages are excluded by default because they usually contain application instructions, policies, or retrieved context. If your system messages are user-authored content worth remembering, opt in explicitly:

```ts
await ingestTurn(memory, {
messages,
onFinish: async ({ text }) => {
await memory.ingest({
mode: 'messages',
messages: [...messages, { role: 'assistant', content: text }],
scope,
});
},
completion: text,
scope,
includeRoles: ['system', 'user', 'assistant', 'tool'],
});
```

## Backend search note

For factual smoke tests with unique strings, names, or numeric identifiers, run `atomicmemory-core` with keyword/hybrid retrieval enabled:

```env
HYBRID_SEARCH_ENABLED=true
# or
RETRIEVAL_PROFILE=quality
```

If core runs in Docker, recreate the app container after editing `atomicmemory-core/.env`:

```bash
docker compose up -d --force-recreate app
```

Verify:

```bash
curl -s http://localhost:3050/v1/memories/health
```

The response should include `"hybrid_search_enabled": true`.

:::caution[Namespace behavior]
Use the same SDK `scope` for `retrieve()` and `ingestTurn()`. Current `atomicmemory-core` can still derive the stored `namespace` from `source_site` during extracted `messages` ingest unless the backend/SDK version explicitly forwards and persists the caller's namespace on ingest. If a smoke test writes successfully but scoped search misses the new fact, inspect the stored memory's `namespace` and verify the search `namespace_scope` matches it.
:::

## See also

- [SDK Overview](/sdk/overview)
- [Mastra integration](/integrations/frameworks/mastra)
- [Using the atomicmemory backend](/sdk/guides/atomicmemory-backend)
- [Scopes and identity](/sdk/concepts/scopes-and-identity)
2 changes: 1 addition & 1 deletion docs/integrations/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ Framework integrations expose AtomicMemory as the memory primitive inside an age

| Integration | Status | Package |
|---|---|---|
| [Vercel AI SDK](/integrations/frameworks/vercel-ai-sdk) | 🛠️ Planned | `@atomicmemory/vercel-ai` |
| [Vercel AI SDK](/integrations/frameworks/vercel-ai-sdk) | 🔧 Source-only | `@atomicmemory/vercel-ai` |
| [LangChain (JS)](/integrations/frameworks/langchain-js) | 🛠️ Planned | `@atomicmemory/langchain` |
| [Mastra](/integrations/frameworks/mastra) | 🛠️ Planned | `@atomicmemory/mastra` |
| [OpenAI Agents SDK](/integrations/frameworks/openai-agents) | 🛠️ Planned | `@atomicmemory/openai-agents` |
Expand Down