Към съдържанието
18/30Глава 18 от 30

Tool Calling и structured outputs: договорът, който издържа

24 извиквания, нула счупен JSON и две използваеми дати. Какво поправя доброто описание и какво схемата не може.

На тази страница

Дайте на модел инструмент за търсене на полети и го помолете да намери полет от Мадрид до Берлин. Ето какво се връща:

TEXT
<tool_call>
{"name": "search_flights",
 "arguments": {"from": "Madrid", "to": "Berlin", "date": "3rd October 2026"}}
</tool_call>

JSON е валиден. Името на инструмента е правилно. Всички задължителни полета присъстват. И извикването е безполезно: никой API за полети не приема "Madrid" там, където очаква код на летище, или "3rd October 2026" там, където очаква дата.

Тази празнина — синтактично перфектно, семантично неизползваемо — е темата на тази глава, а първото, което трябва да установим, е, че това не е проблем с JSON. В двадесет и четири заявки с този инструмент моделът произведе 24 валидни извиквания на инструмент и нула счупен JSON. Нито веднъж не се провали в частта, която всички debug-ват.

Преди механиката, изречението, което предотвратява най-много объркване: tool call е заявка, не действие.

Моделът излъчва структурирано съобщение, което казва искам search_flights да бъде извикан с тези аргументи. След това спира. Вашият код получава това съобщение, решава дали да го уважи, извиква каквото трябва да извика и връща резултата като друго съобщение. Моделът никога не е докосвал вашата база данни, никога не е правил HTTP заявка, никога не е имал credentials.

Всичко за сигурността на agent-и в Глава 30 следва от това разделение, както и всичко за дизайна на agent-и в Глава 23: моделът предлага, а вашият код разполага, и кодът е мястото, където живее всяка гаранция.

Така че един инструмент, лишен от терминология, е две неща:

Схема. JSON Schema, описваща функция: нейното име, какво прави и какви аргументи приема, с техните типове и ограничения. Това влиза в prompt-а и е единственото нещо, което моделът изобщо вижда.

Endpoint. Функция във вашия код, която приема тези аргументи и връща нещо. Моделът никога не я вижда, никога не знае на какъв език е написана и не може да различи заявка към база данни от hardcoded низ.

Дефинициите на инструментите влизат в prompt-а, сериализирани във формата, върху който моделът е обучаван. Те струват tokens при всяко отделно извикване — факт, който по-късно в тази глава се връща с число.

Моделът отговаря с извикване вместо с текст

Връзка към раздела: Моделът отговаря с извикване вместо с текст

Вместо проза, отговорът съдържа структурирана заявка, а API съобщава причина за завършване, която казва точно това. Причината има значение: тя е начинът, по който вашият код разбира, че трябва да изпълни инструмент, вместо да покаже отговор на потребителя.

Това е стъпката, в която няма модел. Валидирайте аргументите спрямо схемата, решете дали този извикващ има право да направи това и изпълнете.

Изпращате резултата обратно като съобщение

Връзка към раздела: Изпращате резултата обратно като съобщение

Резултатът става още един ход в разговора, в роля, запазена за него. Моделът го чете като всеки друг context.

Моделът отговаря или иска друг инструмент

Връзка към раздела: Моделът отговаря или иска друг инструмент

Това е цикълът от Глава 23 и причината една заявка да може да се превърне в дузина обиколки напред-назад.

Нищо от това не е emergent. Както установи Глава 11, tool calling е обучено поведение:1 по време на post-training моделът е видял хиляди разговори, оформени точно така. Затова форматът е специфичен за модела, затова надеждността варира толкова много между модели с подобен размер и затова модел може да извика инструмент, който никога не е виждал — формата е обучена, конкретният инструмент идва от вашия prompt.

Ето инструмента така, както повечето хора го пишат първоначално. Забележете, че нищо в него не е грешно; той просто е тънък:

tools/badFlights.tsTS
{
  name: "search_flights",
  description: "Search for flights.",
  parameters: {
    type: "object",
    properties: {
      from: { type: "string", description: "Airport." },   
      to:   { type: "string", description: "Airport." },   
      date: { type: "string", description: "The date." },  
    },
    required: ["from", "to", "date"],
  },
}

Двадесет и четири заявки, шест двойки градове, кръстосани с четири начина за изразяване на дата („3-ти следващия месец“, „следващия петък“, „15 декември“, „утре“), greedy decoding, така че резултатите да се възпроизвеждат:

извикан инструментсчупен JSONдата в ISOлетища като IATAвсичко правилно
схемата по-горе24/2402/244/241/24

Прочетете първите две колони преди последните три. Моделът извиква правилния инструмент всеки път и произвежда добре оформен JSON всеки път. Провалът е изцяло в стойностите, а стойностите са неизползваеми: "Madrid" вместо MAD, "3rd October 2026" вместо 2026-10-03.

Струва си да настояваме върху това, защото то определя къде гледате, когато нещо се счупи. Инстинктът е да добавите JSON parser с retry или да помолите модела по-настойчиво за валиден JSON. Нито едното не адресира нищо от случилото се тук.

Същият endpoint. Същият код зад него. Същият модел, същите prompts, същият decoding. Единственото, което се променя, е текстът в схемата:

tools/goodFlights.tsTS
{
  name: "search_flights",
  description: "Search scheduled flights between two airports on a given day.",
  parameters: {
    type: "object",
    properties: {
      from: {
        type: "string",
        description: "Departure airport as a three-letter IATA code, e.g. MAD for Madrid. Never a city name.",   
        pattern: "^[A-Z]{3}$",
      },
      to: { /* same */ },
      date: {
        type: "string",
        description: "Departure date as an ISO 8601 calendar date, YYYY-MM-DD. Resolve relative dates against today before calling.",   
        format: "date",
        pattern: "^\\d{4}-\\d{2}-\\d{2}$",
      },
    },
    required: ["from", "to", "date"],
  },
}
ФОРМАТ на дататаСТОЙНОСТ на дататаФОРМАТ на летищетоСТОЙНОСТ на летището
тънка схема2/241/244/244/24
описана схема24/2412/2416/248/24

Форматът на датата минава от 2 от 24 до 24 от 24. Перфектно, само от промяна в текста, без докоснат код и без retry логика. Ако вземете един оперативен навик от тази глава, нека е този: когато инструмент се извиква погрешно, поправката почти винаги е в описанието и това е най-евтината поправка в системата.

Сега прочетете втората колона, която е по-важната половина.

Схемата ограничава формата. Тя не може да достави знание.

Връзка към раздела: Схемата ограничава формата. Тя не може да достави знание.

Датата е в ISO формат 24 пъти от 24. Тя е правилният ден 12 пъти от 24.

Така че половината извиквания вече носят перфектно форматирана дата, която е грешната дата. Описанието е казало на модела каква форма да произведе и моделът я е произвел безупречно — но превръщането на „следващия петък“ в 2026-09-11 изисква да знае днешната дата и да направи календарна аритметика, а никакво количество описание не доставя това. Същата история е и с летищата: форматът се покачи от 4 на 16, но стойността само от 4 на 8, защото писането на MAD изисква да знаете, че летището на Мадрид е MAD.

Това разграничение е носещата идея на главата:

Схемата е договор за форма. Тя може да направи изхода на модела parseable, типизиран и последователен. Не може да го направи истинен, а всеки failure mode, който оцелява след добра схема, е провал на знанието, не провал на формата.

Двете изискват различни поправки, а объркването им губи седмици. Провалите на формата се поправят в описанието или с constrained decoding по-долу. Провалите на знанието се поправят чрез поставяне на знанието в prompt-а — текущата дата в system message, справка за летище като втори инструмент, който моделът извиква първо, enum в схемата, когато множеството е достатъчно малко за изброяване. Забележете какво е общото между трите: те изместват проблема от паметта на модела към неговия вход, което е цялата Глава 24.

Structured outputs и какво всъщност е „constrained decoding“

Връзка към раздела: Structured outputs и какво всъщност е „constrained decoding“

Всичко по-горе все още разчита на това моделът да избере да произведе правилната форма. Налична е по-силна гаранция и тя е най-добрата отплата от Глава 17.

Припомнете си как работи генерирането: на всяка стъпка моделът произвежда logit за всеки token в речника, а sampler-ът избира един. Constrained decoding вмъква стъпка по средата. При дадена граматика — извлечена от вашата JSON Schema — то изчислява кои tokens могат законно да дойдат след това, задава logits на всички останали на отрицателна безкрайност и оставя sampler-а да избира от останалото.

Ако схемата казва, че следващото нещо трябва да е {, тогава всеки token, който не е {, има вероятност нула. Не „малко вероятно“: нула. Моделът не може да излъчи невалиден JSON, защото невалидните tokens са премахнати от разпределението преди sampling.

Това стои под „structured outputs“, „JSON mode“ и „guided generation“ и обяснява двете им свойства. Гаранцията е пълна за всичко, което граматиката може да изрази — типове, задължителни полета, enums, nesting — защото се налага механично, а не се иска учтиво. И не казва нищо за съдържанието: граматика може да принуди "date" да бъде низ, съответстващ на шаблон за дата, и не може да го принуди да бъде правилният ден. Което е същата стена като в предишния раздел, достигната от другата страна.

Две практични бележки. Не е безплатно: маската трябва да се изчислява на всяка стъпка, а сложните граматики струват измерима latency. И променя това, което моделът прави — модел, отклонен от предпочитания си token, може да произведе по-лошо съдържание, докато произвежда перфектна структура, поради което „помоли любезно и валидирай“ все още е разумната настройка по подразбиране за прости форми, а constrained decoding си заслужава цената, когато формата е сложна или потребителят е строг.

Странични ефекти и единственото свойство, което има значение

Връзка към раздела: Странични ефекти и единственото свойство, което има значение

Глава 14 измери timeout, последван от retry, който таксува две генерации за един отговор. С инструменти същият провал става по-лош, защото инструмент може да направи нещо.

Ако вашият код извика charge_card, получи timeout и опита отново, имате две таксувания. Моделът няма представа, че нещо от това се е случило; той вижда един резултат от инструмент. Поправката е същата като във всяка distributed system и не е проблем на модела: направете операцията идемпотентна, като дадете на извикването ключ, така че второто изпълнение да разпознае първото и да върне неговия резултат, вместо да свърши работата отново.

Правилото за дизайн, което следва, си струва да се каже ясно. Разделете четенията от записите във вашия каталог с инструменти. Четене може да се retry-не свободно, да се изпълнява паралелно и да се кешира. Запис не може, и трябва да носи ключ, проверка на permission и — за всичко, за което потребителят би искал да знае, преди да се случи — стъпка за одобрение, която поставя човек между заявката и действието. Тази стъпка за одобрение не е любезност: тя е едно от малкото неща, стоящи между prompt injection и реално последствие — и, както Глава 30 измерва, най-слабото от тях.

Колко инструмента, преди да започне деградация?

Връзка към раздела: Колко инструмента, преди да започне деградация?

Фолклорът казва, че зареждането на много инструменти кара модела да избира лошо. Струва си да се измери, вместо да се повтаря, така че: същите двадесет и четири заявки, с инструмента за полети плюс растящ набор от други — включително три умишлено объркващи се (разписания на влакове, фериботни връзки, автобусни маршрути).

заредени инструментиprompt tokensизбра search_flightsдата в ISO
135324/2424/24
573024/2424/24
101,19321/2421/24
202,11924/2424/24

Изборът не се влоши. С двадесет инструмента, три от които правдоподобно объркващи се, модел с половин милиард параметъра избра правилния двадесет и четири пъти от двадесет и четири. Спадът при десет са три извиквания, които назоваха друг инструмент, и той не оцелява при преминаването към двадесет.

Това е отрицателен резултат и трябва да бъде докладван като такъв: в тази задача, с тези инструменти, „твърде много инструменти“ не беше проблемът. Това, което нарасна, монотонно и с фактор шест, е prompt-ът: от 353 tokens до 2,119, плащани при всяка заявка в разговора, завинаги, независимо дали се използва инструмент.

Така че честната версия на фолклора е за цена и context, не за точност. Двадесет инструмента са постоянен данък върху всяко съобщение, а Глава 16 вече показа какво прави постоянен префикс със сметка през четиридесет хода. Когато хората съобщават, че много инструменти вредят на качеството, механизмът обикновено е, че дефинициите са изтласкали важния context — което е проблем от Глава 24, облечен в костюм на Глава 18. Инструменти, които наистина са почти дубликати един на друг, също са реален проблем, а поправката за тях не е по-малко инструменти, а по-добри описания и namespaces: сложете им префикс по система (crm.search_customer, billing.search_customer), така че два каталога, слети от два екипа, да не се сблъскват и моделът да има по какво да различава.

Три вида инструменти и този, който отваря следващата част

Връзка към раздела: Три вида инструменти и този, който отваря следващата част

Помага да сортираме инструментите според това какво правят със света, защото инженерната работа е различна за всеки.

Инструменти за данни четат: търсят, извличат, правят query. Могат да се retry-ват, паралелизират и кешират. Провалят се, като не връщат нищо полезно, а основният им риск е, че внасят недоверен текст в context — което е цялата attack surface на Глава 30.

Инструменти за действие пишат: изпращат, създават, таксуват, изтриват. Не могат да се retry-ват без ключ, не могат безопасно да се паралелизират и са причината да съществуват потоци за одобрение.

Оркестрационни инструменти извикват други модели. Инструмент, чиято имплементация е друг agent, със собствен prompt, собствени инструменти и собствен цикъл — а за извикващия модел той изглежда точно като другите два, защото схема и endpoint са всичко, което той някога вижда.

Този трети вид не е любопитство. Той е механизмът зад половината „agent като инструмент“ от Глава 25 — другата топология, handoff, предава разговора и никога не го получава обратно — и работи именно защото интерфейсът в тази глава е достатъчно тесен, за да побере цял agent зад него.

Сега имате модел, който може да иска неща, и договор, който прави искането parseable. Това, което нямате, е каквото и да било, за което да пита, отвъд това, което се побира в неговия prompt.

Най-често срещаният инструмент в production, с голяма разлика, е търсене в корпус от текст, който моделът никога не е виждал по време на training: вашата документация, вашите tickets, вашите договори. Това звучи като решен проблем — направете му embedding, намерете най-близките съседи, поставете ги вътре — а нерешените части са тези, които решават дали отговорът е надежден: как текстът се нарязва, преди да му се направи embedding, какъв праг на similarity е достатъчно нисък, за да означава не знам, и как citation се прикрепя към твърдение, така че читателят да може да го провери.

Глава 19 е retrieval и това е главата, в която грешният отговор спира да бъде любопитство и започва да бъде отговорност.


Измерванията в тази глава идват от Qwen/Qwen2.5-0.5B-Instruct с greedy decoding, върху 24 генерирани заявки, кръстосващи шест двойки градове с четири формулировки на дата, използвайки собствения chat template на модела за дефиниции на инструменти. Те се възпроизвеждат точно и са малък модел: четете разделението format/value като демонстрация на механизма, а не като benchmark за това какво правят днешните модели. Frontier model решава „следващия петък“ правилно много по-често — и пак не може да бъде накаран да го направи чрез схема, което е частта, която се обобщава.

Речникът на JSON Schema, използван по-горе (type, properties, required, pattern, format, enum), е специфициран в draft-а на JSON Schema, който документацията на вашия provider посочва; полезното подмножество е малко и еднакво между providers, а разликите, които съществуват — кои keywords се налагат чрез constrained decoding, вместо просто да се подават на модела — си струва да се прочетат в guide-а на provider-а за structured-output, вместо да се приемат на доверие.

За constrained decoding като техника, guidance-style библиотеките и проектът outlines документират конструкцията grammar-to-logit-mask по начин, който се мапва директно върху sampler-а от Глава 17. А за самата обиколка напред-назад най-ясната спецификация не е tutorial, а протокол: Глава 26 я чете ред по ред.

  1. Ouyang, L. et al. Training language models to follow instructions with human feedback. arXiv:2203.02155 (2022). Статията, която направи рецептата за post-training стандарт; формата на tool call се научава там, от демонстрации, точно както формата на отговор.


Създадено от

David Vicente Campos

Основател на NeuraLIA Labs и съосновател на MyRealFood

Компютърен инженер съм, завършил Университета в Леон. Съосновах MyRealFood, където като CTO създадох приложението, което милиони хора са използвали, за да се хранят по-здравословно, и основах NeuraLIA Labs, където изграждам AI продукти. Тук пиша за това, което трябваше да разбера по пътя, така, както ми се иска някой да ми го беше обяснил.

Още за автора

Публикувано от NeuraLIA Labs.

Получавайте нови публикации във входящата си поща

Новини за AI, ръководства и продуктови обновления — кратък имейл, когато публикуваме нещо, което си заслужава.

Индекс на курса

Abstract software decision engine with branching paths, probability nodes, and glowing gates.
jev12 мин четене

AI моделът Jev е създаден за решения, не за проза

Jev на TypeSafe AI привлича внимание, защото разглежда софтуерната интелигентност като проблем на вероятностите: изберете правилния клон, добавете увереност и не плащайте на LLM да пише текст, когато кодът има нужда от решение.

Abstract legal research workspace with documents, search nodes and governance controls.
openai11 мин четене

Astra for Law на OpenAI е правна AI система, не нов модел

Правният старт на OpenAI е не толкова за нов базов модел, колкото за системата около него: домейн извличане, надеждни инструменти, права, бенчмаркове и пътища за преглед.

Abstract agent runtime sorting documents, memory blocks and pointer nodes inside a bounded context frame.
context-engineering12 мин четене

Инженеринг на контекста за AI агенти с дълъг хоризонт

Дълго работещите агенти не се провалят само защото прозорецът е малък. Те се провалят, когато файлове, изходи от инструменти и остаряла история изтласкат задачата, която агентът е трябвало да завърши.

Готови ли сте LIA да избира вместо вас?

Създавайте с всички AI модели на едно място — започнете безплатно още днес.