AI SDK 6 стварае для праграмы на TypeScript выразную мяжу паміж рашэннямі мадэлі і паўнамоцтвамі праграмы. Мадэль можа выбраць тыпізаваны інструмент, атрымаць яго вынік і працягнуць размову. Ваш код усё роўна правярае ўваходныя даныя, ужывае дазволы, вырашае, ці патрэбны чалавек для пабочнага эфекту, і запісвае вынік. Гэтае раздзяленне з’яўляецца карысным прынцыпам праектавання вытворчага агента. У артыкуле выкарыстоўваюцца стабільныя API AI SDK 6, а не навейшы API пацвярджэнняў з AI SDK 7. Усталюйце ai@6 разам з пакетам правайдара з галіны 6.x, напрыклад @ai-sdk/openai@3, і зафіксуйце версіі ў lock-файле.
Цыкл, які вы будуеце
Адзін выклік мадэлі можа вярнуць тэкст або выклік інструмента. Цыкл інструментаў дадае новы выклік мадэлі пасля выканання інструмента. Тады мадэль інтэрпрэтуе вынік і вырашае, ці патрэбны яшчэ адзін інструмент. Кожная генерацыя мадэлі з’яўляецца крокам. Цыкл спыняецца, калі мадэль перастае запытваць інструменты, у выкліканага інструмента няма execute, патрэбна пацвярджэнне або спрацоўвае ўмова stopWhen. Вынік змяшчае фінальны тэкст, выклікі і вынікі інструментаў, паведамленні адказу, выкарыстанне і steps. Таму цыкл можна правяраць, а не ўспрымаць як чорную скрыню.
ToolLoopAgent пакуе такія паводзіны ў паўторна выкарыстоўвальны аб’ект. Яго канструктар патрабуе LanguageModel і прымае instructions, tools, stopWhen, output, prepareStep, maxRetries, тайм-аўты і callback-функцыі. У AI SDK 6 стандартная ўмова спынення роўная stepCountIs(20). Для абмежаванага працэсу задайце меншую мяжу. Яна кіруе коштам і даступнасцю, але не замяняе аўтарызацыю.
Тыпізаваны ToolLoopAgent
Памочнік tool выводзіць тып аргументаў execute з inputSchema. Схема адпраўляецца правайдару і выкарыстоўваецца для праверкі аргументаў, якія перадала мадэль. Але мадэль ад гэтага не становіцца даверанай. Валідная форма можа змяшчаць чужы ўліковы запіс, забаронены шлях, небяспечны URL або недазволеную суму.
npm install [email protected] @ai-sdk/[email protected] @ai-sdk/[email protected] zod
import { openai } from '@ai-sdk/openai';
import { ToolLoopAgent, stepCountIs, tool } from 'ai';
import { z } from 'zod';
const getWeather = tool({
description: 'Get the current weather for a city',
inputSchema: z.object({
city: z.string().min(1).max(80),
}),
execute: async ({ city }) => ({
city,
temperatureC: 18,
condition: 'cloudy',
}),
});
const agent = new ToolLoopAgent({
model: openai('gpt-4o-mini'),
instructions: 'Use getWeather for current weather. Never invent a tool result.',
tools: { getWeather },
stopWhen: stepCountIs(4),
maxRetries: 2,
});
const result = await agent.generate({
prompt: 'What is the weather in Kyiv?',
});
console.log(result.text);
console.log(result.steps.length);
Інструмент павінен выконваць адну аперацыю і вяртаць невялікі серыялізавальны вынік. У апісанні дакладна пакажыце адзінкі, дазволы, актуальнасць даных і паводзіны пры памылцы. Выкарыстоўвайце strict: true, калі правайдар падтрымлівае строгія выклікі і схема сумяшчальная. Строгі рэжым павышае надзейнасць, але не з’яўляецца аўтарызацыяй. toolChoice: 'auto' пакідае выбар мадэлі, required патрабуе даступны інструмент, none адключае інструменты, а { type: 'tool', toolName: 'getWeather' } выбірае названы інструмент. activeTools можа звузіць спіс інструментаў для аднаго кроку.
Яўныя выклікі і абмежаванне цыклу
Выкарыстоўвайце generateText, калі патрэбны разавы выклік або непасрэдны кантроль гісторыі паведамленняў. Дадайце stopWhen, калі вынікі інструментаў трэба перадаць назад мадэлі. Без гэтага параметра generateText выканае адну генерацыю і верне выклік інструмента без аўтаматычнага працягу.
import { openai } from '@ai-sdk/openai';
import { generateText, stepCountIs, tool } from 'ai';
import { z } from 'zod';
const lookupOrder = tool({
description: 'Look up an order by its public order number',
inputSchema: z.object({ orderNumber: z.string().regex(/^ORD-[0-9]{6}$/) }),
execute: async ({ orderNumber }) => ({
orderNumber,
status: 'shipped',
}),
});
const result = await generateText({
model: openai('gpt-4o-mini'),
tools: { lookupOrder },
stopWhen: stepCountIs(3),
prompt: 'Check order ORD-104209 and explain its status.',
});
console.log(result.text);
console.log(result.steps.flatMap(step => step.toolCalls));
Убудаваныя ўмовы: stepCountIs(count), hasToolCall(toolName) і isLoopFinished(). Масіў спыняе цыкл, калі спрацоўвае любая ўмова. У isLoopFinished() няма максімуму, таму выкарыстоўвайце яго толькі разам са знешнім бюджэтам, тайм-аўтам, скасаваннем і квотай правайдара. Уласны StopCondition атрымлівае { steps } і можа спыняцца па стане бізнесу або вымераным бюджэце токенаў. Умовы правяраюцца, калі апошні крок змяшчае вынікі інструментаў. Пры спалучэнні выклікаў інструментаў са структураваным вывадам зарэзервуйце дадатковы крок для яго генерацыі.
maxRetries паўтарае няўдалыя выклікі мадэлі. Ён не робіць execute ідэмпатэнтным. Інструмент, які адпраўляе ліст, спісвае сродкі або стварае запіс, павінен мець ключ ідэмпатэнтнасці і сам выяўляць дублікаты. Для аддаленага MCP-кліента паўторы па змаўчанні выключаны і задаюцца праз maxRetries у createMCPClient. Паўтарайце толькі сеткавыя памылкі і абмежаванне хуткасці. Не паўтарайце без праверкі неідэмпатэнтны запыт tools/call.
Структураваны вывад для TypeScript
У AI SDK 6 generateObject і streamObject пазначаныя састарэлымі. Замест іх выкарыстоўвайце generateText і streamText з параметрам output. Output.object прымае схему Zod, Valibot або JSON Schema. Поўная адказная структура разбіраецца і правяраецца да таго, як будзе гатовы result.output. Частковыя аб’екты зручныя для інтэрфейсу, але не з’яўляюцца канчатковым рашэннем валідацыі.
import { openai } from '@ai-sdk/openai';
import { generateText, Output } from 'ai';
import { z } from 'zod';
const reportSchema = z.object({
sentiment: z.enum(['positive', 'neutral', 'negative']),
score: z.number().min(0).max(1),
keyPoints: z.array(z.string().min(1)).max(8),
});
const { output } = await generateText({
model: openai('gpt-4o-mini'),
output: Output.object({
schema: reportSchema,
name: 'review_report',
description: 'A concise, evidence-based review report',
}),
prompt: 'Analyze: The battery lasts all day, but the charger is bulky.',
});
console.log(output.sentiment, output.score, output.keyPoints);
Валідацыя мае два ўзроўні. Правайдар можа патрабаваць фармат адказу, а AI SDK разбірае атрыманы JSON і правярае яго схемай. Праграма ўсё роўна павінна праверыць бізнес-правілы, напрыклад прыналежнасць ідэнтыфікатара бягучаму арандатару або права пэўнай ацэнкі запускаць вяртанне грошай. Рабіце схемы закрытымі і вузкімі. Абмяжоўвайце радкі, масівы і лікі, выкарыстоўвайце пералікі. Невядомыя каманды трэба адхіляць, а не перадаваць адвольны JSON прывілеяванаму адаптару.
З інструментамі мадэль можа спачатку выканаць пошук, а потым стварыць справаздачу. Укажыце stopWhen: stepCountIs(4) або іншы яўны бюджэт, бо крок структураванага вываду ўваходзіць у той самы шматкрокавы паток. Калі разбор не ўдаўся, перахапіце памылку SDK, захавайце ідэнтыфікатар карэляцыі і метаданыя правайдара, а карыстальніку вярніце бяспечны адказ з магчымасцю паўтору. Не даручайце другой мадэлі аўтарызаваць пашкоджаны вывад.
Струменевая перадача тэксту і падзей
streamText дае асінхронныя патокі для тэксту і структураванага вываду. ToolLoopAgent.stream вяртае StreamTextResult пасля падрыхтоўкі выкліку, таму перад чытаннем textStream дачакайцеся выніку. Тэкставы паток змяшчае згенераваны тэкст, а поўны вынік і callback-функцыі паказваюць выклікі і вынікі інструментаў.
import { openai } from '@ai-sdk/openai';
import { ToolLoopAgent } from 'ai';
const agent = new ToolLoopAgent({
model: openai('gpt-4o-mini'),
instructions: 'Answer clearly and briefly.',
});
const stream = await agent.stream({
prompt: 'Explain why typed tool inputs matter.',
});
for await (const chunk of stream.textStream) {
process.stdout.write(chunk);
}
Для структураванага патоку выкарыстоўвайце streamText з Output.object і чытайце partialOutputStream. onStepFinish дазваляе захаваць завершаны крок, запісаць выкарыстанне і паказаць падзею аўдыту. Паток можа скончыцца часткай tool-approval-request, tool-error або tool-output-denied, а не толькі тэкстам. Кліент, які паказвае толькі тэкст, можа схаваць дзеянне, што чакае чалавека.
Пацвярджэнне пабочнага дзеяння чалавекам
У AI SDK 6 задайце для інструмента needsApproval як true або як асінхронны прэдыкат, заснаваны на правераным уваходзе. Першы выклік generateText або streamText вяртае частку tool-approval-request. Сервер не спыняецца ў чаканні браўзера. Захавайце паведамленні адказу, пакажыце дакладную назву інструмента і аргументы, дадайце tool-approval-response у новае паведамленне tool і зноў выклічце мадэль.
Поўны прыклад Node ніжэй выкарыстоўвае пытанне ў тэрмінале як чалавечую мяжу. Вэб-праграма павінна захоўваць messages, approvalId, toolCallId, аўтэнтыфікаванага правяральніка і тэрмін дзеяння ў серверным сховішчы.
import { createInterface } from 'node:readline/promises';
import { openai } from '@ai-sdk/openai';
import {
generateText,
tool,
type ModelMessage,
type ToolApprovalResponse,
} from 'ai';
import { z } from 'zod';
const records = new Map([
['draft-17', { ownerId: 'user-7', text: 'Quarterly notes' }],
]);
const deleteDraft = tool({
description: 'Delete one draft owned by the authenticated user',
inputSchema: z.object({ draftId: z.string().regex(/^draft-[0-9]+$/) }),
needsApproval: true,
execute: async ({ draftId }) => {
if (!records.delete(draftId)) {
throw new Error('Draft was not found');
}
return { draftId, deleted: true };
},
});
async function requestHumanApproval(toolName: string, input: unknown) {
const terminal = createInterface({ input: process.stdin, output: process.stdout });
const answer = await terminal.question(`Approve ${toolName} ${JSON.stringify(input)}? [y/N] `);
terminal.close();
return answer.trim().toLowerCase() === 'y';
}
const messages: ModelMessage[] = [
{ role: 'user', content: 'Delete draft-17.' },
];
const first = await generateText({
model: openai('gpt-4o-mini'),
system: 'If an action is denied, do not retry it.',
tools: { deleteDraft },
messages,
});
messages.push(...first.response.messages);
const approvals: ToolApprovalResponse[] = [];
for (const part of first.content) {
if (part.type === 'tool-approval-request') {
approvals.push({
type: 'tool-approval-response',
approvalId: part.approvalId,
approved: await requestHumanApproval(part.toolCall.toolName, part.toolCall.input),
reason: 'Decision made by the authenticated reviewer',
});
}
}
if (approvals.length > 0) {
messages.push({ role: 'tool', content: approvals });
const final = await generateText({
model: openai('gpt-4o-mini'),
system: 'If an action is denied, do not retry it.',
tools: { deleteDraft },
messages,
});
console.log(final.text);
} else {
console.log(first.text);
}
Пацвярджэнне не замяняе аўтарызацыю. Непасрэдна перад пабочным дзеяннем зноў праверце карыстальніка, арандатара, мэту, палітыку і версію рэсурсу. Звяжыце захаванае пацвярджэнне з approvalId, toolCallId, імем інструмента і хэшем праверанага ўваходу. Дадайце тэрмін дзеяння, дазвольце адно выкарыстанне і адхіляйце зменены ўваход. Адмова павінна стаць зразумелым вынікам інструмента і не паўтарацца аўтаматычна. Для важнага дзеяння паказвайце мэту, асобу, аргументы, даныя, якія пакідаюць сістэму, і чаканы эфект, а не толькі пераказ мадэлі.
Небяспечныя інструменты і межы бяспекі
Не давайце мадэлі агульны shell, неабмежаваны HTTP-кліент, адвольны шлях да файла або прамое падключэнне да базы. Аддавайце перавагу вузкім аперацыям накшталт deleteDraft, createCalendarEvent або lookupOrder. Змяшчайце аўтарызацыю і спісы дазволаў у execute або сэрвісе палітык. Правярайце схему URL, хост, порт, вызначаны адрас і перанакіраванні. Развязвайце шляхі ў дазволеным корані з улікам сімвалічных спасылак. Правярайце ўладальніка арандатара ў запыце да базы, а не толькі ў prompt.
Лічыце апісанні інструментаў, атрыманыя дакументы, памяць і вынікі інструментаў недавераным змесцівам. Prompt injection можа папрасіць мадэль раскрыць сакрэт, выклікаць старонні інструмент або адправіць даныя на адрас зламысніка. Мадэль не з’яўляецца мяжой бяспекі. У артыкуле Prompt injection і бяспека MCP разглядаюцца мінімальныя прывілеі, паходжанне, выходная сетка, ізаляцыя, ліміты і пацвярджэнні. Для падзелу планавальніка, выканаўцы, палітыкі і сховішча звярніцеся да архітэктуры вытворчага AI-агента.
Ніколі не змяшчайце API-ключы ў prompt або вынікі інструментаў. Маскіруйце токены ў логах і тэлеметрыі. Не запісвайце поўны ўваход, калі ён можа ўтрымліваць персанальныя даныя. Перадавайце ID запыту праз выклікі мадэлі і інструменты. Абмяжоўвайце памер выніку перад даданнем у наступны prompt. Выкарыстоўвайце асобныя ўліковыя даныя для чытання, чарнавіка і фіксацыі. Браўзерны або файлавы агент запускайце ў ізаляваным worker без лішніх сакрэтаў і з сеткай, закрытай па змаўчанні.
MCP без страты тыпізацыі
Пакет @ai-sdk/mcp адаптуе інструменты MCP-сервера да інструментаў AI SDK. AI SDK 6 рэкамендуе HTTP у вытворчасці і пакідае stdio лакальным серверам. Відавочна задавайце схемы, калі сервер не пад вашым кантролем або інструмент адчувальны. Так набор застаецца вузкім, а TypeScript дае карысныя тыпы ўваходу. Усталюйце redirect: 'error', калі палітыка не дазваляе перанакіраванні, праверце крыніцы OAuth і закрывайце кліент у finally або onFinish.
import { openai } from '@ai-sdk/openai';
import { createMCPClient } from '@ai-sdk/mcp';
import { ToolLoopAgent, stepCountIs } from 'ai';
import { z } from 'zod';
const mcpUrl = process.env.MCP_URL;
if (!mcpUrl) throw new Error('MCP_URL is required');
const mcpClient = await createMCPClient({
transport: {
type: 'http',
url: mcpUrl,
headers: process.env.MCP_TOKEN
? { Authorization: `Bearer ${process.env.MCP_TOKEN}` }
: undefined,
redirect: 'error',
},
maxRetries: 2,
});
try {
const tools = await mcpClient.tools({
schemas: {
'get-customer-note': {
inputSchema: z.object({ customerId: z.string().uuid() }),
},
},
});
const agent = new ToolLoopAgent({
model: openai('gpt-4o-mini'),
instructions: 'Use only the customer note tool for this request.',
tools,
stopWhen: stepCountIs(3),
});
const result = await agent.generate({ prompt: 'Read the requested customer note.' });
console.log(result.text);
} finally {
await mcpClient.close();
}
Аўтаматычнае выяўленне праз mcpClient.tools() зручнае, але адкрывае кожны рэкламаваны інструмент і не дае тыпаў уваходу падчас кампіляцыі. Яўныя схемы загружаюць толькі названыя інструменты. Паўтарайце толькі часовыя сеткавыя памылкі. Памылкі праграмы MCP і паспяховыя адказы з isError: true трэба перадаваць без паўторнага пабочнага эфекту. OAuth па-ранейшаму патрабуе праверкі аўдыторыі, PKCE, кароткажывучых токенаў, дакладных redirect URI і спісу дазволеных сервераў аўтарызацыі. Серверны кантракт апісаны ў артыкуле MCP-сервер TypeScript з OAuth.
Памылкі, тэсты і эксплуатацыя
Абгарніце генерацыю мяжой, якая адрознівае няправільны ўваход, памылку правайдара, тайм-аўт, скасаванне, пашкоджаны вывад і памылку інструмента. AI SDK ператварае выключэнне з execute у частку tool-error, каб шматкрокавая мадэль убачыла збой. Вяртайце з інструмента ачышчанае паведамленне або пераўтварайце вынік перад перадачай мадэлі. Не раскрывайце stack trace, сакрэты, SQL, лакальныя шляхі або цела адказу upstream. Абмяжоўвайце кожны запыт праз abortSignal і timeout. Запісвайце finishReason, usage, totalUsage, колькасць крокаў і рашэнне палітыкі інструмента.
AI SDK 6 мае дэтэрмінаваныя mock-мадэлі ў ai/test. MockLanguageModelV3 можа вярнуць выклік інструмента пры першай генерацыі і тэкст пры другой. Наступны тэст даказвае, што цыкл выконвае інструмент адзін раз і атрымлівае фінальны адказ без звароту да правайдара.
import assert from 'node:assert/strict';
import { generateText, stepCountIs, tool } from 'ai';
import { MockLanguageModelV3 } from 'ai/test';
import { z } from 'zod';
const usage = {
inputTokens: { total: 1, noCache: 1, cacheRead: undefined, cacheWrite: undefined },
outputTokens: { total: 1, text: 1, reasoning: undefined },
};
let calls = 0;
let executions = 0;
const model = new MockLanguageModelV3({
doGenerate: async () => {
calls += 1;
if (calls === 1) {
return {
content: [{ type: 'tool-call', toolCallId: 'call-1', toolName: 'add', input: '{"a":2,"b":3}' }],
finishReason: { unified: 'tool-calls', raw: undefined },
usage,
warnings: [],
};
}
return {
content: [{ type: 'text', text: 'The result is 5.' }],
finishReason: { unified: 'stop', raw: undefined },
usage,
warnings: [],
};
},
});
const add = tool({
description: 'Add two numbers',
inputSchema: z.object({ a: z.number(), b: z.number() }),
execute: async ({ a, b }) => {
executions += 1;
return a + b;
},
});
const result = await generateText({
model,
tools: { add },
stopWhen: stepCountIs(2),
prompt: 'Add 2 and 3.',
});
assert.equal(result.text, 'The result is 5.');
assert.equal(calls, 2);
assert.equal(executions, 1);
Дадайце тэсты для адхіленых схем, неадпаведнасці арандатара, абыходу шляху, SSRF-адрасоў, дубляваных ключоў ідэмпатэнтнасці, пратэрмінаванага пацвярджэння, адмовы, спісаў MCP-інструментаў і праверкі вываду. Правярайце ў патоку часткі інструмента і пацвярджэння, а не толькі бачны тэкст. Запускайце adversarial cases з дакументамі, якія загадваюць мадэлі ігнараваць задачу або раскрываць кантэкст. У кіраўніцтве ацэнка AI-агентаў апісаны рэгрэсійныя наборы, праверкі выклікаў і вымярэнне кошту ды затрымкі.
Нотаткі пра міграцыю з AI SDK 5
Замяніце Experimental_Agent на ToolLoopAgent. Яго параметр system перайменаваны ў instructions. Стандартная ўмова спынення змяняецца з stepCountIs(1) на stepCountIs(20), таму пасля абнаўлення выклікаў мадэлі можа стаць больш. Задайце відавочную мяжу. Замяніце generateObject і streamObject на generateText і streamText з Output.object, Output.array або іншай стратэгіяй. Струменевы вынік выкарыстоўвае partialOutputStream.
CoreMessage становіцца ModelMessage, а convertToModelMessages у AI SDK 6 становіцца асінхроннай. ToolCallOptions перайменаваны ў ToolExecutionOptions. Mock-класы V2 замяніце на MockLanguageModelV3 і іншыя V3 з ai/test. Параметр правайдара structuredOutputs выдалены з chat-мадэляў на карысць strictJsonSchema. Пры рэалізацыі toModelOutput дэструктуруйце аргумент { output }, як патрабуе сігнатура v6. Запусціце codemod v6, а потым уручную праверце адаптары правайдараў, пераўтварэнне паведамленняў, паўтор пацвярджэнняў і струменевыя тэсты. Codemod пераймянуе сімвалы, але не вызначыць, ці бяспечнае новае значэнне па змаўчанні для вашага сцэнарыя.
Дакладная паверхня API залежыць ад зафіксаванага patch-рэлізу і правайдара. Перад абнаўленнем прачытайце даведнік ToolLoopAgent, кіраўніцтва па выкліку інструментаў, кіраўніцтва па структураваных даных, кіраўніцтва MCP, кіраўніцтва па тэставанні і кіраўніцтва міграцыі з AI SDK 5 на 6. Папярэджанне правайдара пра праігнараваны параметр лічыце памылкай тэсту.