Skip to content
Draft
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
2 changes: 2 additions & 0 deletions TODO.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
- have only done the generate-text example so far
- how to add root attributes to the span
5 changes: 5 additions & 0 deletions examples/telemetry-nextjs-ai-sdk-v7/.env-example
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
AXIOM_URL="https://api.axiom.co"
AXIOM_TOKEN="xaat-******"
AXIOM_DATASET="my_dataset"

OPENAI_API_KEY="sk-proj-******"
41 changes: 41 additions & 0 deletions examples/telemetry-nextjs-ai-sdk-v7/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/versions

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# local env files
.env
.env*.local

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts
1 change: 1 addition & 0 deletions examples/telemetry-nextjs-ai-sdk-v7/.npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
public-hoist-pattern[]=*require-in-the-middle*
23 changes: 23 additions & 0 deletions examples/telemetry-nextjs-ai-sdk-v7/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Next.js with Opentelemetry example

This is a reference example for using Axiom with the Vercel AI SDK v7 with Next.js. It uses the AI SDK's built-in OpenTelemetry integration and does not require `axiom/ai` middleware or wrappers. The example shows steps for:

- Setting up an OpenTelemetry tracer under `src/instrumentation.ts` that points to Axiom
- Registering AI SDK v7 OpenTelemetry integration under `src/instrumentation.node.ts`
- Configuring an OpenAI model under `src/shared/openai.ts`
- Using runtime context and tool context with `generateText()` under `src/app/generate-text/page.tsx`
- Running a `ToolLoopAgent` demo with direct tools and subagents under `src/app/tools-and-subagents/page.tsx`

## How to use

You will need an Axiom dataset and API key, so go ahead and create those on [Axiom's Console](https://app.axiom.co/datasets).

Then prepare your environment variables

- Copy the environment file: `cp .env-example .env`
- In the new `.env` file, set OpenAI API key, and Axiom API key and dataset name
- Install deps: `pnpm install`
- Run development server `pnpm dev`
- Visit `http://localhost:3000`
- Once the app loads a trace should be sent automatically to Axiom
- Visit Axiom console and navigate to your dataset stream, a list of spans will be visible.
8 changes: 8 additions & 0 deletions examples/telemetry-nextjs-ai-sdk-v7/eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { config } from '@repo/eslint-config';

export default [
{
ignores: ['.next/**'],
},
...config,
];
4 changes: 4 additions & 0 deletions examples/telemetry-nextjs-ai-sdk-v7/next.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/** @type {import('next').NextConfig} */
const nextConfig = {};

export default nextConfig;
39 changes: 39 additions & 0 deletions examples/telemetry-nextjs-ai-sdk-v7/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
{
"name": "example-instrumentations-nextjs-v7",
"private": true,
"type": "module",
"scripts": {
"dev": "next",
"build": "next build",
"start": "next start",
"format": "prettier --write .",
"format:check": "prettier --check .",
"lint": "eslint './**/*.{js,ts}'",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@ai-sdk/openai": "4.0.0-canary.72",
"@ai-sdk/otel": "1.0.0-canary.117",
"@ai-sdk/react": "4.0.0-canary.174",
"@ai-sdk/rsc": "3.0.0-canary.172",
"@opentelemetry/api": "^1.9.1",
"@opentelemetry/exporter-jaeger": "^2.7.1",
"@opentelemetry/exporter-trace-otlp-http": "^0.218.0",
"@opentelemetry/resources": "^2.7.1",
"@opentelemetry/sdk-trace-node": "^2.7.1",
"@opentelemetry/semantic-conventions": "^1.41.1",
"ai": "7.0.0-canary.171",
"next": "latest",
"react": "^19.2.6",
"react-dom": "^19.2.6",
"zod": "catalog:"
},
"devDependencies": {
"@repo/eslint-config": "workspace:*",
"@types/node": "^22.19.19",
"@types/react": "^19.2.15",
"@types/react-dom": "^19.2.3",
"eslint": "catalog:",
"typescript": "catalog:"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { convertToModelMessages, streamText } from 'ai';
import { gpt4oMini } from '@/shared/openai';

export async function POST(request: Request) {
const { messages } = await request.json();

const result = streamText({
model: gpt4oMini,
messages: await convertToModelMessages(messages),
telemetry: {
functionId: 'stream-text-react',
},
});

return result.toUIMessageStreamResponse();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export default function Loading() {
return (
<div>
<p>Loading...</p>
</div>
);
}
104 changes: 104 additions & 0 deletions examples/telemetry-nextjs-ai-sdk-v7/src/app/generate-text/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import { generateText, stepCountIs, tool, zodSchema } from 'ai';
import { z } from 'zod';
import { gpt4oMini } from '@/shared/openai';

type DirectionsInput = {
from: string;
to: string;
};

type DirectionsContext = {
userId: number;
routePreference: 'fastest' | 'scenic';
};

type DirectionsOutput = {
from: string;
to: string;
directions: string;
routePreference: DirectionsContext['routePreference'];
toolCallId: string;
userId: number;
};

export const dynamic = 'force-dynamic';

export default async function Page() {
const userId = 123;
const requestId = crypto.randomUUID();

const res = await generateText({
runtimeContext: {
requestId,
userId,
},
telemetry: {
functionId: 'generate-text-directions',
includeRuntimeContext: {
requestId: true,
},
includeToolsContext: {
findDirections: {
routePreference: true,
},
},
},
model: gpt4oMini,
stopWhen: stepCountIs(5),
system:
'You are a helpful AI assistant. You must always use the findDirections tool when asked to find directions.',
messages: [
{
role: 'user',
content: 'How do I get from Paris to Berlin?',
},
],
tools: {
findDirections: tool<DirectionsInput, DirectionsOutput, DirectionsContext>({
description: 'Find directions to a location',
inputSchema: zodSchema(
z.object({
from: z.string().describe('The location to start from'),
to: z.string().describe('The location to find directions to'),
}),
),
contextSchema: zodSchema(
z.object({
userId: z.number(),
routePreference: z.enum(['fastest', 'scenic']),
}),
),
execute: async (params, { abortSignal, context, toolCallId }) => {
const { from, to } = params;
// Simulate API call delay
await new Promise((resolve) => setTimeout(resolve, 500));

abortSignal?.throwIfAborted();

// Return mock directions data
return {
from,
to,
directions: `To get from ${from} to ${to}, use a teleporter.`,
routePreference: context.routePreference,
toolCallId,
userId: context.userId,
};
},
}),
},
toolsContext: {
findDirections: {
userId,
routePreference: 'fastest',
},
},
});

return (
<div>
<p>{res.text}</p>
<pre>messages: {JSON.stringify(res.response.messages, null, 2)}</pre>
</div>
);
}
14 changes: 14 additions & 0 deletions examples/telemetry-nextjs-ai-sdk-v7/src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import type { Metadata } from 'next';

export const metadata: Metadata = {
title: 'Next.js with Vercel AI SDK v7 telemetry',
description: 'Next.js with Vercel AI SDK v7 OpenTelemetry',
};

export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>{children}</body>
</html>
);
}
24 changes: 24 additions & 0 deletions examples/telemetry-nextjs-ai-sdk-v7/src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import Link from 'next/link';

export default function Page() {
return (
<div>
<h1>Axiom AI Examples</h1>
<p>Choose an example to explore:</p>
<ul>
<li>
<Link href="/generate-text">Generate Text Example</Link>
</li>
<li>
<Link href="/tools-and-subagents">Tools and Subagents Example</Link>
</li>
<li>
<Link href="/stream-text-ai-sdk-react">Stream Text Example (@ai-sdk/react)</Link>
</li>
<li>
<Link href="/stream-text-ai-sdk-rsc">Stream Text Example (@ai-sdk/rsc, legacy)</Link>
</li>
</ul>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
'use client';

import { useState } from 'react';
import { useChat } from '@ai-sdk/react';
import { DefaultChatTransport } from 'ai';

export default function StreamText2() {
const [input, setInput] = useState<string>('');

const { messages, sendMessage } = useChat({
transport: new DefaultChatTransport({
api: '/api/stream-text-ai-sdk-react',
}),
});

const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();

if (input.trim()) {
sendMessage({ text: input });
setInput('');
}
};

return (
<div className="max-w-2xl mx-auto p-6">
<h1 className="text-2xl font-bold mb-2">Stream Text 2</h1>
<p className="text-gray-600 mb-6">Demo using AI SDK telemetry with Response streams</p>

<form onSubmit={handleSubmit} className="mb-6">
<div className="flex gap-2">
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="Enter your prompt..."
className="flex-1 px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
<button
type="submit"
disabled={!input.trim()}
className="px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 disabled:opacity-50"
>
Send
</button>
</div>
</form>

<div className="space-y-4">
{messages.map((message) => (
<div
key={message.id}
className={`p-3 rounded-lg ${
message.role === 'user' ? 'bg-blue-50 ml-8' : 'bg-gray-50 mr-8'
}`}
>
<div className="font-semibold text-sm mb-1">
{message.role === 'user' ? 'You' : 'AI'}
</div>
<div className="whitespace-pre-wrap">
{message.parts.map((part, index) => {
if (part.type === 'text') {
return <span key={index}>{part.text}</span>;
}
return null;
})}
</div>
</div>
))}
</div>
</div>
);
}
Loading
Loading