npm install @sanity/contextRequires @sanity/client ^8.4.0 as a peer dependency, which comes automatically with sanity 6.12 or later.
| Entry point | Purpose |
|---|---|
@sanity/context/studio |
Deprecated Studio plugin (legacy documents) |
@sanity/context/ai-sdk |
AI SDK telemetry integration for Insights |
@sanity/context/insights |
Lower-level APIs for custom workflows |
Context configuration has moved to the Context app in the Sanity Dashboard. The plugin only keeps existing Sanity Context documents editable in Studio. See the migration guide.
Track and classify your AI agent conversations automatically. Insights saves every conversation transcript to your organization's Context store, and a classification step you run with your own AI SDK model extracts success scores, sentiment, and content gaps. Results are surfaced in Insights in the Context app in the Sanity Dashboard.
Everything rides on @sanity/client (^8.4.0) and its client.context namespace. Create one org-scoped client and pass it to the telemetry integration and the insights functions:
import {createClient} from '@sanity/client'
const client = createClient({
apiVersion: 'v2025-11-27',
token: process.env.SANITY_ORGANIZATION_TOKEN, // Organization token with Context Editor permissions. Keep server-side only
context: {organizationId: process.env.SANITY_ORGANIZATION_ID},
useCdn: false,
useProjectHostname: false,
})Connect your AI agent to save conversations automatically:
import {sanityInsightsIntegration} from '@sanity/context/ai-sdk'
import {convertToModelMessages, streamText} from 'ai'
import {openai} from '@ai-sdk/openai'
const result = await streamText({
model: openai('gpt-4o'),
// If using useChat, convert UIMessage[] to ModelMessage[] for streamText
messages: await convertToModelMessages(messages),
// On AI SDK v6, use `experimental_telemetry: {isEnabled: true, integrations: [...]}` instead
telemetry: {
integrations: [
sanityInsightsIntegration({
client,
threadId: conversationId, // Any unique string (session ID, UUID, etc.)
// The well-known mcpEndpoints key tags the conversation with an MCP endpoint name
metadata: {mcpEndpoints: process.env.SANITY_CONTEXT_ENDPOINT_NAME ?? []},
// Optional: share telemetry with Sanity to help improve Context.
// {metrics: true} shares metadata-only metrics; {conversations: true}
// shares full transcripts (implies metrics); add a contact so the
// team can reach you.
sharing: {metrics: true},
}),
],
},
})Each save is an idempotent upsert per thread: the messages replace the stored transcript wholesale, so repeated saves with the same threadId keep the transcript current.
Failures are saved too, so Insights can show them: a tool call that throws is saved with its error, and on AI SDK v7 a generation that fails is saved with the transcript so far and the error. AI SDK v6 has no hook for failed generations, so those are not saved.
Classification runs on your side, with your model and your LLM API key. The pending queue is a query over your org's Context document store: conversations with messages, no verdict, no recorded failure, and idle long enough to be considered settled. Run classifyConversations on a schedule, for example as a scheduled Sanity Function:
-
Create
functions/classify-conversations/index.ts, see the full example -
Create
sanity.blueprint.ts, see the full exampleRequirements:
- Install
@sanity/functions,@ai-sdk/anthropic,@sanity/blueprints, anddotenvalongside@sanity/clientand@sanity/context - Create a
.envnext to the blueprint withANTHROPIC_API_KEY,SANITY_ORGANIZATION_ID,SANITY_CONTEXT_ENDPOINT_NAME, andSANITY_ORGANIZATION_TOKEN
- Install
-
Deploy:
pnpm install
npx sanity login
npx sanity blueprints init
npx sanity blueprints promote
npx sanity functions test classify-conversations --with-user-token # Test locally
npx sanity blueprints deploy
npx sanity functions env add classify-conversations ANTHROPIC_API_KEY <your-key>Every classified conversation includes these standardized metrics:
| Metric | Type | Description |
|---|---|---|
successScore |
number (1-10) |
How well the agent resolved the user's needs |
sentiment |
'positive' | 'neutral' | 'negative' |
Overall user sentiment |
contentGaps |
string[] |
Topics where the agent lacked knowledge |
The recommended way to classify conversations is with classifyConversations, which handles fetching, batching, and error handling in a single call:
import {classifyConversations} from '@sanity/context/insights'
import {anthropic} from '@ai-sdk/anthropic'
const result = await classifyConversations({
client,
model: anthropic('claude-haiku-4-5'),
mcpEndpoint: process.env.SANITY_CONTEXT_ENDPOINT_NAME, // Optional: only this endpoint's conversations
limit: 100, // Optional: max conversations per run (default 100)
concurrency: 5, // Optional: parallel classifications (default 3)
settledForMinutes: 10, // Optional: idle time before a thread is classified (default 10)
})
console.log(
`${result.successCount} classified, ${result.errorCount} failed out of ${result.totalFound}`,
)For custom workflows, use the lower-level primitives directly:
| Function | Purpose |
|---|---|
classifyConversations |
Recommended: classify all pending conversations |
classifyConversation |
Classify a single conversation by thread ID |
getConversationsToClassify |
Query the pending classification queue (GROQ) |
getPreviousContentGaps |
Query known content gaps ranked by frequency |
All take the same {client} option. Saving a transcript without the AI SDK integration is client.context.conversations.save from @sanity/client directly:
await client.context.conversations.save({
threadId: 'thread-123',
messages: [
{role: 'user', content: 'Hello!'},
{role: 'assistant', content: 'Hi there! How can I help?'},
],
metadata: {mcpEndpoints: 'support-agent'},
})Reading conversations back for dashboards or reports is plain GROQ over the org's Context document store:
const recent = await client.context.fetch(
'*[_type == "sanity.context.conversation"] | order(messagesUpdatedAt desc) [0...50]',
)- Error handling: Non-blocking by design. Save and classification failures are logged but don't break the user experience. Save failures log
[sanity-insights]messages; classification failures log[classifyConversation]messages. A failed classification records the error on the conversation, which removes it from the pending queue. - Concurrency: Create a fresh
sanityInsightsIntegration()instance per request. Do not share instances across concurrent requests. - Cooldown: Conversations become eligible for classification only after they have been idle for
settledForMinutes(default 10). You own this setting; tune it to how long your threads stay active. - Costs: Classification runs in scheduled batches (every 10 minutes in the example) with your own LLM key. Adjust the schedule and
limitto control token usage. - Staging: Configure the client with
apiHost: 'https://api.sanity.work'to target the staging API. Defaults tohttps://api.sanity.io.