Данила (Dayfing)
Жарияланымдарға оралу
2 414 сөз14 мин

AI SDK 6 және TypeScript: құралдар циклі, құрылымдалған нәтиже және адам мақұлдауы

AI SDK 6 TypeScript қолданбасында модель шешімі мен қолданба өкілеттігінің арасын анық бөледі. Модель типтелген құралды таңдап, оның нәтижесін алып, әңгімені жалғастыра алады. Ал сіздің код кіріс деректерін тексереді, рұқсаттарды қолданады, жанама әсерге адам керек пе екенін анықтайды және болған оқиғаны тіркейді. Бұл бөліну өндірістегі агентті жобалаудың негізгі қағидасы. Бұл мақала AI SDK 6-ның тұрақты API интерфейстерін қолданады, AI SDK 7-де енгізілген кейінгі мақұлдау API интерфейсін емес. 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 көмекшісі inputSchema негізінде execute аргументінің түрін шығарады. Схема провайдерге жіберіледі және модель берген аргументтерді тексеруге қолданылады. Бірақ осының өзі модельді сенімді етпейді. Дұрыс пішіндегі мән басқа есептік жазбаны, тыйым салынған жолды, қауіпті 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 } алады және бизнес күйіне немесе өлшенген token бюджетіне қарай тоқтай алады. Шарттар соңғы қадамда құрал нәтижесі болған кезде тексеріледі. Құрал шақыруын құрылымдалған нәтижемен біріктірсеңіз, нәтиже генерациясына қосымша қадам қалдырыңыз.

maxRetries сәтсіз модель шақыруларын қайталайды. Ол execute функциясын идемпотентті етпейді. Электрондық хат жіберетін, ақша алатын немесе жазба жасайтын құрал идемпотенттік кілт қолданып, қайталануды өзі тексеруі тиіс. Қашық MCP клиентінде қайталау әдепкіде өшірулі және createMCPClient ішіндегі maxRetries арқылы қосылады. Тек желі мен rate limit қателерін қайталаңыз. Идемпотентті емес tools/call сұрауын соқыр түрде қайта жібермеңіз.

TypeScript қолдана алатын құрылымдалған нәтиже

AI SDK 6 generateObject және streamObject функцияларын ескірген деп белгілеп, output параметрі бар generateText пен streamText қолдануды ұсынады. 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-ды талдап, схема бойынша тексереді. Қолданба бизнес ережелерін бәрібір тексеруі керек: мысалы, идентификатордың ағымдағы tenant-ке тиесілігін немесе score қайтарымды іске қосуға құқылы ма екенін. Схемаларды жабық әрі тар ұстаңыз. Жолдарға, массивтерге және сандарға шек қойып, enum қолданыңыз. Белгісіз командаларды артық JSON-ды артықшылықты адаптерге бергеннен гөрі қабылдамаңыз.

Құралдармен модель алдымен іздеу жасап, содан кейін есеп жасай алады. stopWhen: stepCountIs(4) немесе басқа нақты бюджет қойыңыз, өйткені құрылымдалған нәтиже қадамы сол көпқадамды ағынға кіреді. Талдау сәтсіз болса, SDK қатесін ұстаңыз, корреляция ID-сін және провайдер метадеректерін сақтаңыз, қауіпсіз қайталауға болатын жауап қайтарыңыз. Пішімі бұзылған нәтижеге басқа модельден рұқсат сұрамаңыз.

Мәтін мен оқиғаларды ағынмен жіберу

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);
}

Құрылымдалған ағын үшін Output.object бар streamText қолданып, partialOutputStream оқыңыз. onStepFinish аяқталған қадамды сақтауға, қолдануды жазуға және аудит оқиғасын көрсетуге мүмкіндік береді. Ағын тек мәтінмен емес, tool-approval-request, tool-error немесе tool-output-denied бөлігімен де аяқталуы мүмкін. Тек мәтін көрсететін клиент адамды күтіп тұрған әрекетті жасыруы ықтимал.

Жанама әсерге адам мақұлдауы

AI SDK 6-да құралға needsApproval параметрін true немесе тексерілген кіріске негізделген асинхронды предикат ретінде беріңіз. Бірінші generateText немесе streamText шақыруы tool-approval-request бөлігін қайтарады. Сервер браузерді күтіп тоқтап қалмайды. Жауап хабарларын сақтап, құралдың нақты атауы мен аргументтерін көрсетіңіз, жаңа tool хабарламасына tool-approval-response қосып, модельді қайта шақырыңыз.

Төмендегі толық 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);
}

Мақұлдау авторизацияны алмастырмайды. Жанама әсер алдында пайдаланушыны, tenant-ті, нысанды, саясатты және ресурс нұсқасын қайта тексеріңіз. Сақталған мақұлдауды approvalId, toolCallId, құрал атауы және тексерілген кіріс хэшімен байланыстырыңыз. Мерзім қойып, бір рет қолдануға рұқсат беріңіз және өзгерген кірісті қабылдамаңыз. Бас тарту түсінікті құрал нәтижесіне айналып, автоматты түрде қайталанбауы керек. Маңызды әрекет үшін модельдің қысқа мазмұнын ғана емес, нысанды, тұлғаны, аргументтерді, жүйеден шығатын деректерді және күтілетін әсерді көрсетіңіз.

Қауіпті құралдар және қауіпсіздік шекаралары

Модельге жалпы shell, шектеусіз HTTP клиентін, кез келген файл жолын немесе дерекқорға тікелей қосылым бермеңіз. Оның орнына deleteDraft, createCalendarEvent немесе lookupOrder сияқты тар операцияларды ұсыныңыз. Авторизация мен рұқсат тізімдерін execute ішіне немесе саясат сервисіне орналастырыңыз. URL схемасын, хостты, портты, шешілген мекенжайды және қайта бағыттауды тексеріңіз. Жолдарды рұқсат етілген түбірде шешіп, символдық сілтемелерді ескеріңіз. Tenant иелігін prompt-та ғана емес, дерекқор сұрауында тексеріңіз.

Құрал сипаттамаларын, алынған құжаттарды, жадты және құрал нәтижелерін сенімсіз мазмұн деп есептеңіз. Prompt injection модельден құпияны ашуды, қатысы жоқ құралды шақыруды немесе деректерді шабуылдаушы мекенжайына жіберуді сұрауы мүмкін. Модель қауіпсіздік шекарасы емес. Ең аз артықшылық, дерек шығу тегі, сыртқы желі, оқшаулау, лимиттер және мақұлдау шаралары Prompt injection және MCP қауіпсіздігі мақаласында түсіндірілген. Жоспарлаушыны, орындаушыны, саясатты және сақтауды ажырату үшін өндірістегі AI агент архитектурасы нұсқаулығын қараңыз.

API кілттерін prompt-қа немесе құрал нәтижесіне ешқашан салмаңыз. Лог пен телеметриядағы token-дерді бүркемелеңіз. Жеке дерек болуы мүмкін кезде толық кірісті журналға жазбаңыз. Сұрау 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, қысқа мерзімді token-дер, дәл redirect URI және табылған авторизация серверлерінің рұқсат тізімі әлі де қажет. Сервер жағы OAuth бар TypeScript MCP сервері мақаласында көрсетілген.

Қателер, тесттер және пайдалану

Генерацияны жарамсыз кірісті, провайдер қатесін, тайм-аутты, тоқтатуды, пішімі бұзылған нәтижені және құрал қатесін ажырататын шекарамен қоршаңыз. AI SDK execute ішіндегі ерекше жағдайды tool-error бөлігіне айналдырады, сондықтан көпқадамды модель қатені көре алады. Құралдан тазартылған хабар қайтарыңыз немесе нәтижені модельге бермей тұрып түрлендіріңіз. Stack trace, құпия, SQL, жергілікті жол немесе upstream жауап денесін ашпаңыз. Әр сұрауды abortSignal және timeout арқылы шектеңіз. finishReason, usage, totalUsage, қадам санын және құрал саясатының шешімін тіркеңіз.

AI SDK 6 ai/test ішінде детерминирленген mock модельдерін береді. 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);

Қабылданбаған схемаларға, tenant сәйкессіздігіне, жолды айналып өтуге, SSRF мекенжайларына, қайталанған идемпотенттік кілттерге, мерзімі өткен мақұлдауға, бас тартуға, MCP құралдарының рұқсат тізіміне және нәтиже валидациясына тест қосыңыз. Ағынның құрал мен мақұлдау бөліктерін тексеріңіз, көрінетін мәтінді ғана емес. Модельге тапсырманы елемеуді немесе контекстті шығаруды бұйыратын құжаттармен adversarial cases іске қосыңыз. AI агенттерін бағалау нұсқаулығында регрессия деректері, құрал шақыру assertions және шығын мен кідірісті өлшеу қарастырылған.

AI SDK 5-тен көшіру ескертпелері

Experimental_Agent орнына ToolLoopAgent қойыңыз. Оның system параметрі instructions деп аталады. Әдепкі тоқтау шарты stepCountIs(1) мәнінен stepCountIs(20) мәніне өзгереді, сондықтан жаңартудан кейін модель қоңыраулары көбейуі мүмкін. Нақты шек қойыңыз. generateObject пен streamObject орнына Output.object, Output.array немесе басқа стратегиясы бар generateText және streamText қолданыңыз. Ағын нәтижесі partialOutputStream пайдаланады.

CoreMessage орнына ModelMessage келеді, ал AI SDK 6-да convertToModelMessages асинхронды болады. ToolCallOptions орнына ToolExecutionOptions келеді. V2 mock кластарын MockLanguageModelV3 және ai/test ішіндегі басқа V3 mock кластарымен ауыстырыңыз. Chat модельдеріндегі провайдердің structuredOutputs параметрі алынып, орнына strictJsonSchema қолданылды. toModelOutput жүзеге асырғанда v6 сигнатурасына сай { output } аргументін деструктуралаңыз. v6 codemod іске қосылғаннан кейін провайдер адаптерлерін, хабар түрлендіруін, мақұлдауды қайта ойнатуды және ағын тесттерін қолмен тексеріңіз. Codemod таңбаларды қайта атай алады, бірақ жаңа әдепкі мән жұмыс ағынына қауіпсіз бе екенін шеше алмайды.

Нақты API бекітілген patch нұсқасы мен провайдерге тәуелді. Жаңартудан бұрын ToolLoopAgent анықтамасын, құрал шақыру нұсқаулығын, құрылымдалған дерек нұсқаулығын, MCP нұсқаулығын, тест нұсқаулығын және AI SDK 5-тен 6-ға көшіру нұсқаулығын оқыңыз. Провайдер параметрдің еленбегені туралы ескертсе, оны тест қатесі деп санаңыз.

Өндіріске дейінгі тексеру

Өндіріске шығар алдында әр құрал үшін иесін, кірісін, шығысын, рұқсатын, тайм-аутын және аудит оқиғасын анықтаңыз. Әр қадамның бюджеті мен барлық сұраудың жалпы бюджеті болсын. Тестте модельге келген зиянды құжаттың нұсқауды өзгерте алмайтынын тексеріңіз. Клиент үзілгенде MCP байланысын жабыңыз, ал approval хабарламасы қайталанса, оны бір рет қана қабылдаңыз. Жаңа модельді қоспас бұрын қадам саны, шығын, кідіріс, құрал қатесі және бас тарту көрсеткіштерін бұрынғы нұсқамен салыстырыңыз.

Дереккөздер

Басқа жарияланымдар