Skip to content
Dashboard

Introducing tracing, multi-modal attachments, JSON streaming to clients, and more.

Copy link to headingTracing

Trace visualization with Datadog and the Vercel AI SDKTrace visualization with Datadog and the Vercel AI SDK
Trace visualization with Datadog and the Vercel AI SDK
instrumentation.ts
import { registerOTel } from '@vercel/otel';
export function register() {
registerOTel({ serviceName: 'your-project-nameapp' });
}

const result = await generateText({
model: anthropic('claude-3-5-sonnet-20240620'),
prompt: 'Write a short story about a cat.',
experimental_telemetry: { 
isEnabled: true,
functionId: 'my-awesome-function',
metadata: {
something: 'custom',
someOtherThing: 'other-value',
},
},
});

Copy link to headingMulti-Modal File Attachments

Sending image and text attachments with useChat

Copy link to headingFileList

const { input, handleSubmit, handleInputChange } = useChat();
const [files, setFiles] = useState<FileList | undefined>(undefined);
return (
<form
onSubmit={(event) => {
handleSubmit(event, {
experimental_attachments: files,
});
}}
>
<input
type="file"
onChange={(event) => {
if (event.target.files) {
setFiles(event.target.files);
}
}}
multiple
/>
<input type="text" value={input} onChange={handleInputChange} />
</form>
);

Copy link to headingURLs

const { input, handleSubmit, handleInputChange } = useChat();
const [attachments] = useState<Attachment[]>([
{
name: 'earth.png',
contentType: 'image/png',
url: 'https://example.com/earth.png',
}
]);
return (
<form
onSubmit={event => {
handleSubmit(event, {
experimental_attachments: attachments,
});
}}
>
<input type="text" value={input} onChange={handleInputChange} />
</form>
)

Copy link to headinguseObject hook

Extracting and streaming an expense from plain text with useObject
app/api/expense/schema.ts
import { z } from 'zod';
export const expenseSchema = z.object({
expense: z.object({
category: z
.string()
.describe(
'Category of the expense. Allowed categories: ' +
'TRAVEL, MEALS, ENTERTAINMENT, OFFICE SUPPLIES, OTHER.',
),
amount: z.number().describe('Amount of the expense in USD.'),
date: z
.string()
.describe('Date of the expense. Format yyyy-mmm-dd, e.g. 1952-Feb-19.'),
details: z.string().describe('Details of the expense.'),
}),
});
export type PartialExpense = DeepPartial<typeof expenseSchema>['expense'];
export type Expense = z.infer<typeof expenseSchema>['expense'];

app/api/expense/route.ts
import { anthropic } from '@ai-sdk/anthropic';
import { streamObject } from 'ai';
import { expenseSchema } from './schema';
// Allow streaming responses up to 30 seconds
export const maxDuration = 30;
export async function POST(req: Request) {
const { expense }: { expense: string } = await req.json();
const result = await streamObject({
model: anthropic('claude-3-5-sonnet-20240620'),
system:
'You categorize expenses into one of the following categories: ' +
'TRAVEL, MEALS, ENTERTAINMENT, OFFICE SUPPLIES, OTHER.' +
// provide date (including day of week) for reference:
'The current date is: ' +
new Date()
.toLocaleDateString('en-US', {
year: 'numeric',
month: 'short',
day: '2-digit',
weekday: 'short',
})
.replace(/(\w+), (\w+) (\d+), (\d+)/, '$4-$2-$3 ($1)') +
'. When no date is supplied, use the current date.',
prompt: `Please categorize the following expense: "${expense}"`,
schema: expenseSchema,
onFinish({ object }) {
// you could save the expense to a database here
},
});
return result.toTextStreamResponse();
}

app/expense-tracker/page.tsx
'use client';
import { experimental_useObject as useObject } from 'ai/react';
import {
Expense,
expenseSchema,
PartialExpense,
} from '../api/expense/schema';
import { useState } from 'react';
export default function Page() {
const [expenses, setExpenses] = useState<Expense[]>([]);
const { submit, isLoading, object } = useObject({
api: '/api/expense',
schema: expenseSchema,
onFinish({ object }) {
if (object != null) {
setExpenses(prev => [object.expense, ...prev]);
}
},
});
return (
<div>
<form onSubmit={e => {
e.preventDefault();
const input = e.currentTarget.expense as HTMLInputElement;
if (input.value.trim()) {
submit({ expense: input.value });
e.currentTarget.reset();
}
}}
>
<input type="text" name="expense" placeholder="Enter expense details"/>
<button type="submit" disabled={isLoading}>Log expense</button>
</form>
{isLoading && object?.expense && (
<ExpenseView expense={object.expense} />
)}
{expenses.map((expense, index) => (
<ExpenseView key={index} expense={expense} />
))}
</div>
);
}

app/expense-tracker/page.tsx
const ExpenseView = ({ expense }: { expense: PartialExpense | Expense }) => (
<div>
<div>{expense?.date ?? ''}</div>
<div>${expense?.amount?.toFixed(2) ?? ''}</div>
<div>{expense?.category ?? ''}</p></div>
<div>{expense?.details ?? ''}</div>
</div>
);

Copy link to headingAdditional LLM Settings

Copy link to headingConclusion

Copy link to headingContributors

Ready to deploy?