This comprehensive guide covers integrating OwlMetric with Next.js applications for AI cost tracking and analytics. We'll cover both the Proxy and Direct SDK methods, with special focus on the AI SDK integration.
This method provides the best integration with Vercel's AI SDK and automatic telemetry tracking.
npm install @owlmetric/tracker @vercel/otel @opentelemetry/sdk-logs @opentelemetry/api-logs @opentelemetry/instrumentation ai
Create instrumentation.ts in your Next.js root directory:
// instrumentation.ts
import { registerOTel } from "@vercel/otel";
import { OwlMetricTraceExporter } from "@owlmetric/tracker/owlmetric_trace_exporter";
export function register() {
registerOTel({
serviceName: "my-nextjs-app",
traceExporter: new OwlMetricTraceExporter(),
});
}
Update next.config.ts:
// next.config.ts
const nextConfig = {
experimental: {
instrumentationHook: true,
},
};
export default nextConfig;
// app/api/chat/route.ts
import { openai } from "@ai-sdk/openai";
import { anthropic } from "@ai-sdk/anthropic";
import { google } from "@ai-sdk/google";
import { streamText } from "ai";
export const maxDuration = 30;
export async function POST(req: Request) {
const { messages, provider = "openai" } = await req.json();
// Choose provider dynamically
const model = {
openai: openai("gpt-4o"),
anthropic: anthropic("claude-3-sonnet-20240229"),
google: google("gemini-1.5-pro"),
}[provider];
const result = streamText({
model,
messages,
experimental_telemetry: {
isEnabled: true,
metadata: {
xOwlToken: process.env.OWLMETRIC_TOKEN,
userId: "user-123", // Optional: track specific users
sessionId: "session-456", // Optional: track sessions
},
},
});
return result.toDataStreamResponse();
}
// app/chat/page.tsx
"use client";
import { useChat } from "ai/react";
export default function ChatPage() {
const { messages, input, handleInputChange, handleSubmit } = useChat({
api: "/api/chat",
body: {
provider: "openai", // Can be changed dynamically
},
});
return (
<div className="flex flex-col w-full max-w-md py-24 mx-auto stretch">
{messages.map((m) => (
<div key={m.id} className="whitespace-pre-wrap">
{m.role === "user" ? "User: " : "AI: "}
{m.content}
</div>
))}
<form onSubmit={handleSubmit}>
<input
className="fixed bottom-0 w-full max-w-md p-2 mb-8 border border-gray-300 rounded shadow-xl"
value={input}
placeholder="Say something..."
onChange={handleInputChange}
/>
</form>
</div>
);
}
Perfect for existing Next.js applications with minimal changes.
// app/api/chat/route.ts
import OpenAI from "openai";
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
baseURL: 'https://owlmetric.com/api/proxy',
defaultHeaders: {
'x-owlmetric': process.env.OWLMETRIC_API_KEY,
}
});
export async function POST(req: Request) {
const { messages } = await req.json();
const completion = await openai.chat.completions.create({
model: "gpt-4",
messages,
stream: true,
});
// Convert to ReadableStream for Next.js
const encoder = new TextEncoder();
const stream = new ReadableStream({
async start(controller) {
for await (const chunk of completion) {
const content = chunk.choices[0]?.delta?.content || "";
controller.enqueue(encoder.encode(content));
}
controller.close();
},
});
return new Response(stream, {
headers: {
"Content-Type": "text/plain; charset=utf-8",
},
});
}
For maximum control and minimal latency.
npm install @owlmetric/tracker
// app/api/chat/route.ts
import OpenAI from "openai";
import { createTrackedClient } from "@owlmetric/tracker";
const client = createTrackedClient(OpenAI, {
apiKey: process.env.OPENAI_API_KEY,
owlmetricToken: process.env.OWLMETRIC_OPENAI_TOKEN,
});
export async function POST(req: Request) {
const { messages } = await req.json();
const completion = await client.chat.completions.create({
model: "gpt-4",
messages,
stream: true,
});
// Convert to ReadableStream for Next.js
const encoder = new TextEncoder();
const stream = new ReadableStream({
async start(controller) {
for await (const chunk of completion) {
const content = chunk.choices[0]?.delta?.content || "";
controller.enqueue(encoder.encode(content));
}
controller.close();
},
});
return new Response(stream, {
headers: {
"Content-Type": "text/plain; charset=utf-8",
},
});
}
Create .env.local:
# Provider API Keys
OPENAI_API_KEY=sk-your-openai-key
ANTHROPIC_API_KEY=sk-ant-your-anthropic-key
GOOGLE_GENERATIVE_AI_API_KEY=your-google-key
# OwlMetric Token
OWLMETRIC_TOKEN=pt_your_owlmetric_token
# Provider API Keys (optional, can be stored in OwlMetric)
OPENAI_API_KEY=sk-your-openai-key
# OwlMetric Proxy Key
OWLMETRIC_API_KEY=pk_your_project_key
# Provider API Keys
OPENAI_API_KEY=sk-your-openai-key
ANTHROPIC_API_KEY=sk-ant-your-anthropic-key
# OwlMetric Tracking Tokens
OWLMETRIC_OPENAI_TOKEN=pt_your_openai_token
OWLMETRIC_ANTHROPIC_TOKEN=pt_your_anthropic_token
// app/api/chat/route.ts
import { openai } from "@ai-sdk/openai";
import { anthropic } from "@ai-sdk/anthropic";
import { google } from "@ai-sdk/google";
import { streamText } from "ai";
type Provider = "openai" | "anthropic" | "google";
export async function POST(req: Request) {
const { messages, provider, model } = await req.json();
const providerModels = {
openai: openai(model || "gpt-4o"),
anthropic: anthropic(model || "claude-3-sonnet-20240229"),
google: google(model || "gemini-1.5-pro"),
};
const result = streamText({
model: providerModels[provider as Provider],
messages,
experimental_telemetry: {
isEnabled: true,
metadata: {
xOwlToken: process.env.OWLMETRIC_TOKEN,
provider,
model,
userAgent: req.headers.get("user-agent"),
},
},
});
return result.toDataStreamResponse();
}
// app/api/chat-with-tools/route.ts
import { openai } from "@ai-sdk/openai";
import { streamText, tool } from "ai";
import { z } from "zod";
export async function POST(req: Request) {
const { messages } = await req.json();
const result = streamText({
model: openai("gpt-4o"),
messages,
tools: {
getWeather: tool({
description: "Get weather for a location",
parameters: z.object({
location: z.string().describe("The location to get weather for"),
}),
execute: async ({ location }) => {
// Simulate weather API call
return {
location,
temperature: 72,
condition: "sunny",
};
},
}),
},
experimental_telemetry: {
isEnabled: true,
metadata: {
xOwlToken: process.env.OWLMETRIC_TOKEN,
feature: "function-calling",
},
},
});
return result.toDataStreamResponse();
}
// app/api/generate/route.ts
import { generateText } from "ai";
import { openai } from "@ai-sdk/openai";
export async function POST(req: Request) {
const { prompt, maxTokens = 500 } = await req.json();
const { text, usage } = await generateText({
model: openai("gpt-4"),
prompt,
maxTokens,
experimental_telemetry: {
isEnabled: true,
metadata: {
xOwlToken: process.env.OWLMETRIC_TOKEN,
endpoint: "generate",
},
},
});
return Response.json({
text,
usage,
timestamp: new Date().toISOString(),
});
}
// middleware.ts
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
export function middleware(request: NextRequest) {
// Track API requests to AI endpoints
if (request.nextUrl.pathname.startsWith("/api/chat")) {
const response = NextResponse.next();
// Add correlation ID for request tracking
const correlationId = crypto.randomUUID();
response.headers.set("x-correlation-id", correlationId);
return response;
}
}
export const config = {
matcher: "/api/chat/:path*",
};
For applications using the Pages Router:
// pages/api/chat.ts
import type { NextApiRequest, NextApiResponse } from 'next';
import { openai } from "@ai-sdk/openai";
import { streamText } from "ai";
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
if (req.method !== 'POST') {
return res.status(405).json({ error: 'Method not allowed' });
}
const { messages } = req.body;
const result = streamText({
model: openai("gpt-4"),
messages,
experimental_telemetry: {
isEnabled: true,
metadata: {
xOwlToken: process.env.OWLMETRIC_TOKEN,
},
},
});
// Convert to Node.js response stream
const stream = result.toAIStreamResponse();
stream.body?.pipeTo(
new WritableStream({
write(chunk) {
res.write(chunk);
},
close() {
res.end();
},
})
);
}
// app/api/chat/route.ts
import { openai } from "@ai-sdk/openai";
import { streamText } from "ai";
import { unstable_cache } from "next/cache";
const getCachedResponse = unstable_cache(
async (messagesHash: string, messages: any[]) => {
const result = await streamText({
model: openai("gpt-4"),
messages,
experimental_telemetry: {
isEnabled: true,
metadata: {
xOwlToken: process.env.OWLMETRIC_TOKEN,
cached: false,
},
},
});
return result.text;
},
["ai-chat"],
{ revalidate: 3600 } // Cache for 1 hour
);
// lib/rate-limit.ts
import { Ratelimit } from "@upstash/ratelimit";
import { Redis } from "@upstash/redis";
const ratelimit = new Ratelimit({
redis: Redis.fromEnv(),
limiter: Ratelimit.slidingWindow(10, "1 m"), // 10 requests per minute
});
export async function checkRateLimit(identifier: string) {
const { success, limit, reset, remaining } = await ratelimit.limit(identifier);
return {
success,
limit,
reset,
remaining,
};
}
// app/api/chat/route.ts
import { checkRateLimit } from "@/lib/rate-limit";
export async function POST(req: Request) {
const ip = req.headers.get("x-forwarded-for") ?? "unknown";
const { success, remaining } = await checkRateLimit(ip);
if (!success) {
return new Response("Rate limit exceeded", {
status: 429,
headers: {
"X-RateLimit-Remaining": remaining.toString(),
},
});
}
// Continue with AI request...
}
Deploy to Vercel:
npx vercel
Set Environment Variables:
vercel env add OWLMETRIC_TOKEN
vercel env add OPENAI_API_KEY
Configure for Production:
// next.config.ts
const nextConfig = {
experimental: {
instrumentationHook: true,
},
env: {
OWLMETRIC_ENVIRONMENT: process.env.NODE_ENV,
},
};
For other deployment platforms, ensure:
Add custom metadata for better tracking:
const result = streamText({
model: openai("gpt-4"),
messages,
experimental_telemetry: {
isEnabled: true,
metadata: {
xOwlToken: process.env.OWLMETRIC_TOKEN,
userId: session?.user?.id,
feature: "chat",
version: "v1.2.0",
environment: process.env.NODE_ENV,
userAgent: req.headers.get("user-agent"),
},
},
});
// app/api/chat/route.ts
export async function POST(req: Request) {
try {
const result = streamText({
model: openai("gpt-4"),
messages,
experimental_telemetry: {
isEnabled: true,
metadata: {
xOwlToken: process.env.OWLMETRIC_TOKEN,
},
},
});
return result.toDataStreamResponse();
} catch (error) {
console.error("AI API Error:", error);
// Track error in OwlMetric
// Error details are automatically captured by the telemetry system
return new Response("Internal Server Error", { status: 500 });
}
}
Instrumentation not working:
instrumentation.ts is in the root directoryinstrumentationHook: true is set in next.config.tsTelemetry not appearing in dashboard:
OWLMETRIC_TOKEN environment variable is setpt_)Streaming not working:
toDataStreamResponse() for App RouterTest AI SDK Integration:
curl -X POST http://localhost:3000/api/chat \
-H "Content-Type: application/json" \
-d '{"messages":[{"role":"user","content":"Hello"}]}'
Test Instrumentation:
Check Next.js logs for OpenTelemetry initialization messages.
Security:
Performance:
Monitoring:
Cost Optimization: