Галоўная / Артыкулы / Апраўкавыя «вораты» у LangGraph.js: пазіранне агентаў за дапамойкай interrupt() і Command

Апраўкавыя «вораты» у LangGraph.js: пазіранне агентаў за дапамойкай interrupt() і Command

Створыце мінімальныя вораты затверджэння для LangGraph.js, якія на час зупіняюцься перед выпанаванням побачных эфектаў, збіраюць рашэнне чалавека ў тэрмінале і безпечна продовжуюць работу з пункта контролю.

3146 слоў

Дзеянні дэйственнікаў ёсць настолькі значныя, што іх не можна выканаць без нагляду: адправка электранаўпіса ад імені каго-небудзь, выдаленне запису, затверджэння платежа. У такіх случаях трэба, каб дэйственнік запропанаваў дзеянне, зупініўся і чакаў, пакуль чалавек скажа «так» або «нет». У гэтым кераванні ствараецца такая модель у LangGraph.js з максымальна простым графам, ўпэўніваючыся, што вы можаце точна пабачыць, як interrupt(), чэкпойнтар, thread_id і Command({ resume }) саўместна дапамагаюць павярнуць выкананне і продакраціраваць яго пазней.

Жадной оркестрацыі калькоў дэйственнікаў і жадных вызоў LLM тут няма, спецыяльна. Весь прыем падпісваецца ў адной фразе: павярнуць, дазволіць чалавеку вырашыць, продакраціраваць.

Калі дэйственнік не павінен маты канечнае слова

Аўтанамія ёсць ценная, калі вы спакойна дазваляеце агенту дзейваць на адпаведнасці з сабой. Большасць дзеянняў не падпадаюць під гэтыя критэрыя, і крок чалавечага перагляду ўзрабатвае гэтыя труднасці. Тыповыя прыклады включаюць:

  • надсылка электранаўскага або паведамлення ў чат для пользователя
  • змена або выдаленне запису ў базе дадзенаў
  • схваленне платежа
  • розгортанне коду
  • знікненне хмарных рэсурсаў
  • перадача заявкі на падтрымку на вышэйшы ранг
  • публікацыя контэнту, створанага AI

У кожным случае мета адна і тая ж. Агент все равно думае і готавіць дзеянне, але ён представляе сваі намеры і перадае заканчальнае рашэнне чалавеку, прытаму як не станецца незворачным. Гэтае распадзелленне і ўсё, што на практыцы значыць Human-in-the-Loop (HITL).

Як interact() і Command супрацоўнаюць

У сваім сутнасці, HITL у LangGraph працюе так: граф зупыняецца пад час выканання, чакае даныя зза меж графа, а пасля продовжвае роботу, выкарыстоўваючы гэтыя даныя.

Зупінка адбываецца за дапамогою interrupt(). Калі вузел вызывае яго, LangGraph зупыняе чынны запуск і зберагае стан графа через настаўлены checkpointer, ў результате чаго той самы запуск можа быць продовжаны пазней. Ваша прыкладна програма отрымае значэнне, яке вы паслалі ў interrupt(), паказвае яго чалавеку, збірае адпаведзь, а пасля продовжвае роботу графа, вызываючы яго з об’ектам Command, які несе адпаведзь.

Загальная последовасць выглядае так:

Graph starts
    ↓
Agent decides to send email
    ↓
⏸ interrupt()
    ↓
Human reviews the action
    ↓
Approve / Reject
    ↓
Command({ resume: ... })
    ↓
Graph continues

Запамятаце гэтую структуру; кожны фрагмент коду нижчэй адпавядае адной стрэлкі ў яй.

Сценарыю: электрана, якая патрабуе затверджэння

У прыкладзе викорыстоўваецца электрана. Агент вырашае, што хочаць аправіць гэтыя паведамленні:

Meeting at 5 PM with Aman

Перш чым це паведамленне пацягнёца куды-небудзь, чалавек павінен яго пераглядзець і або затвердзіць, або адхіліць. Структура розгалужэння выглядае так:

User
  ↓
Agent decides to send email
  ↓
⏸ Human approval
  ↓
┌───────────────┐
│ Approve       │ → Send email
│ Reject        │ → Stop
└───────────────┘

Ёжы ўвагу заставіць на механізме паўзы і продажвачэння, не выкарыстоўваецца жадны рэальны прадаўцу электранайпісаў. Узел, які „падае“ электранайпіс, проста выводзіць тэкст у тэрмінал. Пазнейшая замена яго на рэальны API нічога не змянюе ў логіцы керавання.

Стварэнне графа па шагам

Інсталюйце LangGraph і падготавіце неабходныя імпорты

Пачніце з порожняга проекту Node.js і дадзіце LangGraph:

npm install @langchain/langgraph

Залежна ад версіі, яую вы інсталюеце, LangGraph.js можа таксама ачакваты @langchain/core як партнерскую залежнасць; якщо npm паведамляе пра гэта або імпорты не выконваюцца, дадзіце і гэты пакет, а таксама перагляньце документацыю да текущай інсталляціі.

Увод адміністратара будзе прыходзіць з тэрміналу через вбудованы модуль Node readline/promises, таму для гэтага не трэба додатковых пакетаў. Через імпорты запрашваюцца з’еднанні для будавання графа, дапаможнік для анотавання стану, сэнтіналы START і END, функція interrupt, контролер у памяці і элемент Command, а таксама компаненты для роботы з уводам:

import {
  StateGraph,
  Annotation,
  START,
  END,
  interrupt,
  MemorySaver,
  Command,
} from "@langchain/langgraph";

import readline from "node:readline/promises";
import {
  stdin as input,
  stdout as output,
} from "node:process";

Файл выкарыстоўвае сынтаксі ES-модуля (import), таму яго трэба або назваць з расширэнням .mjs, або задаць "type": "module" у файле package.json, і выкарыстоўваць версію Node, якая падтрымлеўвае await на верхнім рэвэлі, таму што пасля чаго код выконваецца з викорыстаннем await на рэвэлі модуля.

Задаць стан, які несе граф

Стан у LangGraph — это спяшчаны объект, які циркулюець праз граф. Кожны вузел чытае з яго і вяртае частковыя апдэйты. Штосьці такога графа патрэбны толькі два поля:

  • message — тэкст пісьма, яке пропануе агент
  • decision — адказ чалавека
const StateAnnotation = Annotation.Root({
  message: Annotation,
  decision: Annotation,
});

Annotation.Root() адзначае форму стану. Калі не заданы ніякія редюсэры, кожна змена проста берае найняўейшую значэнне, якая была ў яе занесена. Вузел агента запоўніць message; вузел затварэння запоўніць decision, калі чалавек даў адказ.

Запісайце дзеянне, якое хочаце захаваць

Далей ідзе вузел, який представляе рызыкаваную операцыю. У працоўнай сіткі гэта могла бы вызваць API для пісьмаў. Ён тут толькі фіксуе:

function sendEmail(state) {
  console.log(`\n📧 Email sent: "${state.message}"`);

  return {};
}

Тое, што робіць гэта функцыя, практычна не мае значэння. Важліва толькі часовая момент ўвыкання: яна ніколи не выконваецца раней, чым людзіня даўа згоду. Рэшта структуры існуе для падтрымкі гэтага порядку. Заўважыце, што яна вяртае порожні об’ект, тое значыць, што не змянюе стан системы.

Адпраўка рашэння агента

У рэальной системе самэлькі LLM чытае запит корыстніка і вяршыць выварку, чы рэкламаванае пісьмо неабходна, верагодна за дапамогою вызову інструментаў. Дадаванне модэлю тут толькі відвлекае увагу ад механізмаў HITL, таму простая функцыя выполняе роль агента і вяртае паведамленне, якое ён „выбраў“:

function agent() {
  return {
    message: "Meeting at 5 PM with Aman",
  };
}

Цей вузол можна розумець як агента, який анунцюе сваё намеры: гэта пісьмо, якое ён хочаць адправіць. Якщо пазней заменіць яго на LLM і логіку вызову інструментаў, механізмы затверджэння, якія існуюць навакол, застаюцца практычна тымі ж.

Пауза для людзіні за дапамогою interrupt()

Эта частка ўтварае сэрцо шаблона. Узлак затверджэння вызывае interrupt() з пакетам дадзеных, які описвае, што трэба адначасова, і вяртае тое, што прыходзіць у якості новага decision:

function humanApproval(state) {
  const decision = interrupt({
    message: state.message,
    question: "Do you want to send this email?",
  });

  return {
    decision,
  };
}

Як толькі выконанне доходзіць да гэтага вызыву

interrupt(...)

граф зупіняецца. Об’ект, які быў пераданы, стае пакетам дадзеных для перерыву, які можа прачытаць вызваючая аплікацыя. У гэтым случае це:

{
  message: "Meeting at 5 PM with Aman",
  question: "Do you want to send this email?"
}

Аплікацыя паказвае гэты пакет дадзеных чалавеку, чакае на адказ і продовжвае роботу графа. Ключоўты момент — тое, што любая значэнне, якое вы задаеце пад час продовжэння, станавіцца значэннем, якое вяртае interrupt(). Такім чынам гэты рядок

const decision = interrupt(...);

практычна ператвараецца на следавае пасля таго, як чалавек даў згоду:

const decision = "approve";

Гэтае значэнне запісваецца ў стан як decision. Пасля чаго функцыя маршрутацыі выбирае наступны крок на адной з можлівасцей, якія дае гэты decision:

function routeAfterApproval(state) {
  if (state.decision === "approve") {
    return "sendEmail";
  }

  return END;
}

Адказ "approve" ведае да з’явіцца sendEmail; будзь-што інше завершае выконанне. Працава з кожным несхваленным адказам як з прычыной завершэння ёсць разумным стандартам для механізма безпекі: якщо калі-небудзь прыйдзе нечаканая значэнне, граф завершаецца без выконання запланаваных дзеянь.

Дадзіце checkpointer і паўязайце граф

Перад компіляваннем патрэбны ўсё жа адны элементы: checkpointer. Паколькі выконанне будзе завершвацца, а пасля продовжвалася, LangGraph должен захаваць стан выконання ў момент перерыву. Без checkpointer няма чаго восстанавіць. Для дэманстрацыі достатнека реалізаціі ў памяці:

const checkpointer = new MemorySaver();

Тепер заўважыце трыя вузлы, з’яеднайце START з агентам, а агента — з крокам затверджэння; дадзіце умовную грань з кроку затверджэння, якая керуецца параметрам routeAfterApproval, і скомпілюйце код з атрыбутам checkpointer:

const graph = new StateGraph(StateAnnotation)
  .addNode("agent", agent)
  .addNode("humanApproval", humanApproval)
  .addNode("sendEmail", sendEmail)

  .addEdge(START, "agent")
  .addEdge("agent", "humanApproval")

  .addConditionalEdges(
    "humanApproval",
    routeAfterApproval,
    {
      sendEmail: "sendEmail",
      [END]: END,
    }
  )

  .compile({
    checkpointer,
  });

Трэцій аргумент функцыі addConditionalEdges супарабатоўвае кожную значэння, якую можа вернуць маршрутазначальнік, з вялікім вузлом, што таксама дазволяе LangGraph правільна накрасіць граф. Рэзультатныя структуры вузлаў:

START
  ↓
agent
  ↓
humanApproval
  ↓
 ┌──────────────┐
 │              │
approve       reject
 │              │
 ↓              ↓
sendEmail      END
 │
 ↓
END

MemorySaver зберагае пункты контролю ў памяці процесу, што ідеальна для експерыментаў, але беспарадная пасля завершэння процесу. Для рэальных развертанняў трэба викорыстоваць стойкі пункт контролю, падтрымваны базай дадзеных, каб запавешчаны запуск застаўся пасля перзапуску і мог быць продактываў з іншага процесу або сервера, што ёсьць звычайная ситуацыя, калі затверджэнне прыходзіць через веб-інтэрфейс за калькі гадзін. Чым дакладней пазнаць, як унутршняя частка зберагання пунктав контролю, можна адзірнуцца на тое, як у LangGraph-а механізм зберагання ў памяці арганізуе пункты контролю і ведаць змест.

Другаяя частка пытання стойкасці — это thread_id. Ён вызначае, пра які пункт контролю йдзе мова. Для запавешчання і продактываў трэба викорыстоваць той самы thread_id; інакш LangGraph не мае можлівасці знайсці захаваны запуск.

Выкананне процесу з тэрміналу

Пачніце выкананне і адзначыце перарыв

Інтерфейс readline прыменяе тэрмінал як апрацоўвальнік ад чалавека:

const rl = readline.createInterface({
  input,
  output,
});

Об’ект налаштавання мае thread_id пад ключам configurable. Усе, што стосуецца гэтага выканання – як пачатковы вызов, так і продажчэнне – должна перадаваць гэты ж об’ект (або прынеймна той самы ідентыфікатор):

const config = {
  configurable: {
    thread_id: "thread-1",
  },
};

Пачніце роботу графа з пачатковым станам. Вузол агента перазапісае порожній message:

const stream = await graph.stream(
  {
    message: "",
  },
  config
);

Выкананне праходзіць через agent да humanApproval, дзе interrupt() яго зупіняе. Потым поток выдае фрагмент, які мае ключ __interrupt__. Яго першы элемент мае value, які ўсьмо тое, што было пераданае да interrupt(), і якое цикл выводзіць для апрацоўвальніка:

for await (const chunk of stream) {
  if (chunk.__interrupt__) {
    const interruptValue =
      chunk.__interrupt__[0].value;

    console.log(
      "\n⏸ Waiting for human approval...\n"
    );

    console.log(
      "The agent wants to send this email:"
    );

    console.log(`"${interruptValue.message}"`);

    console.log(
      `\n${interruptValue.question}`
    );
  }
}

Рэзультаты вываду з тэрміналу адпамянюць наступным. Першыя лініі прадставляюць первасны запит корыстувальніка; код, паказаны вышэй, яго не выводзіць:

User: Send an email to Aman about the 5 PM meeting

⏸ Waiting for human approval...

The agent wants to send this email:
"Meeting at 5 PM with Aman"

Do you want to send this email?

У гэты момент граф выконання зупінены, і ніяго пашлана не было. Выконанне чакае ў механізме перагляду стану.

Запытайце пра рашэнне і пераканайцеся ў ям

Тепер запытайце ад таго, хто пераглядае. Цикл будзе запытваць, пакуль не отрымае аднаго з двух прийнятных адказоў, спачатку нормалізуючы прабелы і роўню верхніх/нижніх букв:

let humanAnswer;

while (true) {
  humanAnswer = (
    await rl.question("\nApprove or reject: ")
  )
    .trim()
    .toLowerCase();

  if (
    humanAnswer === "approve" ||
    humanAnswer === "reject"
  ) {
    break;
  }

  console.log(
    'Please type "approve" or "reject".'
  );
}

У тэрмінале паказуецца Затвердзіце або адхіліце: і блокуецца. Уведзенне approve разбивае цикл, і граф выконання можа быць запусканы знову. Перакананне ў правамільнасці вводу пры пачатку выконання ўартуе калькольвых дадзеных ліній: значэнне, якое вы пасылайце назад, будзе абоўсюды тым самым, якое пакажае ваша логіка маршрутацыі.

Аднова выконання за дапамою каманды

Актывацыя значыць паўтарную вызовку графа, але замест новых дадзенняў вы пасылаеце Command, у якога поле resume містіць адпаведзь чалавека:

await graph.invoke(
  new Command({
    resume: humanAnswer,
  }),
  config
);

Вжываецца той самы config, а значыць і той самы thread_id; так LangGraph выкарыстоўвае для аднарабаткі перарванага запуску. Значэнне resume падае як рынговая значэнне функціі interrupt(). Калі уведзена значэнне approve, вызов унутры humanApproval

const decision = interrupt(...);

тады вяртае approve. Вузол вяртае яго як decision, і рутэры працюе:

function routeAfterApproval(state) {
  if (state.decision === "approve") {
    return "sendEmail";
  }

  return END;
}

Паколькі decision равнае "approve", кантроль пераходзіць да sendEmail, і тэрмінал выводзіць:

📧 Email sent: "Meeting at 5 PM with Aman"

Электрана была "падзянутая" толькі пасля явнага затверджэння. Калі заканчылі, вызывайце rl.close(), ўсёлякім чынам, каб інтэрфейс readline звалічыў stdin і процес мог устаць.

Як выглядае адхіленне

Зноў запускайце скрыпт. Паколькі MemorySaver знаходзіцца ў памяці, новы процес запускаецца з порожнім сховішчам пунктавых контролей; якщо вы запускаеце його знову ў тым жа процесе, выкорыстоўваюце новы thread_id, каб не прагнаць продажчэнне запуску, які вялікі ўжо завершыўся. Калі з’являецца запит,

Approve or reject:

атаварда:

reject

Графік прагнаецца з тым значэннем. Нижэйшы фрагмент паказвае літэрал для яснасці; у скрыпцы гэта проста humanAnswer:

await graph.invoke(
  new Command({
    resume: "reject",
  }),
  config
);

У гэты раз state.decision мае значэнне "reject", таму рутэр вяртае END і функцыя sendEmail ніколі не запускаецца:

Agent wants to send email
        ↓
   ⏸ Paused
        ↓
Human: reject
        ↓
      END

Эта разліка мае значэнне. Граф не проста генеруе іншая паведамленне при адзінакоўцы; вузел, які выкананая побачны эфект, зовсама не выпаловаецца. Самэў гэтага падтрымка затверджэння ёсць справжнім захаваннем, а не толькі візуальным.

Полная картына

Якшто з’еднаць усё, цэлы граф выглядае так:

                 ┌─────────────┐
                 │    START    │
                 └──────┬──────┘
                        ↓
                 ┌─────────────┐
                 │    Agent    │
                 └──────┬──────┘
                        ↓
              ┌───────────────────┐
              │  Human Approval   │
              │                   │
              │   ⏸ interrupt()   │
              └─────────┬─────────┘
                        ↓
                 Human decides
                   /       \
                  /         \
             approve       reject
                ↓             ↓
         ┌────────────┐      END
         │ sendEmail  │
         └──────┬─────┘
                ↓
               END

У адной лініі: агент прыманяе рашэнне, граф на час зупіняецца, чалавек пераглядае, граф продовжае роботу, і толькі пасля чаго выкананае дзеянне.

Проблема павторнага выканання: трэба заставіць побачныя эфекты пасля перарыву

Адзін з спосабаў дзеяння interrupt() падводзіць большасць людзей. Калі граф працяўнаець знову, LangGraph не продырваецца да лініі пасля interrupt(); уместо таго ён зноў запускае вузол, які мае інтарупцыю, з самай першай лініі. Разлік у другым запуску заключаецца ў тым, што вызов interrupt() негайна вяртае значэнне працяўнаець, а не зупіняеся.

Это мае прымусовы наследкі для пабочных эфектаў. Усё, што размешана перед interrupt() у тым жа вузле, запускаецца аднойчы, калі граф зупіняецца, і ўтрохі пазней, калі працяўнаець зноў пачынаецца. Гэтыя схемы трэба утрымлівацца:

function humanApproval(state) {
  saveSomethingToDatabase();

  const decision = interrupt("Approve?");

  return { decision };
}

Тут saveSomethingToDatabase() бы выканаліся два разы для адной затверджэння. Рашэнне ў структурным аспекте: трэба, каб вузел затверджэння не меў пабочных эфектаў, а кожная рэальная дзеянне размешвалася ў пазнейшым вузле, які запускаецца толькі пасля таго, калі чалавек даў адказ. Самэ так утворены прыклад:

humanApproval
      ↓
interrupt()
      ↓
human response
      ↓
sendEmail

Якщо вам сапраўды неабходна выконваць яку-небудзь роботу перад перерывам у тым самым вузле, зробіце ёё ідэмптантной (безпечнаю для павторнага выконання, напрыклад, уперт з ключам, стабільным за сваёй прыродай) або перакладзіце ёю ў окремы поперадні вузел, чыя рэзультаты вже зафіксаваны і не будуць выконваны знова.

Што відбуваецца пад капотам, па порядку

Калі код выключаны, жыцёвы цикл є короткім:

  1. Граф пачынае выконванне.
  2. Вузел агента прымае рашэнне аб выкліканні е-паўтарку.
  3. Выконанне доходзіць да функцыі interrupt().
  4. LangGraph зупіняе роботу.
  5. Тэкучы стан зафіксаваны прыборам фіксаціі стану.
  6. Програма прымеяе пакет даных перерыву.
  7. Чалавек пераглядае запрошаныя дзеяння.
  8. Этот чалавек выдае рашэнне.
  9. Програма пракраяе выконанне графа за дапамою Command, выкарыстоўваючы той самы thread_id.
  • interrupt() вяртае адпаведны адказ чалавека.
  • Граф распачваецца далей па гілке, якую выбраў адказ.
  • Выклікі API — гэта простая частка; схема паузы і продажвачэння — гэта ідея, якую варта запам’ятаваць:

            Graph
              │
              ▼
        Agent decision
              │
              ▼
          interrupt()
              │
              │
          ┌───┴───┐
          │ Human │
          └───┬───┘
              │
         approve/reject
              │
              ▼
           resume
              │
              ▼
          Continue
    

    Калі гэты прыем паводзіцца зрозумелым, HITL больш не здаецца таінственным. Цэлькам простая пауза з адказам, увінчаным напісаным адказам.

    Перакананне у сопрацоўчыстве вашай власнай рэалізацыі

    Перш чым паспольгаваць на механізм затверджэння, атрымайце калькі швыдкіх тэстаў:

    • Затвердзіце раз і пераканайцеся, што дзеянне выконваецца роўна раз.
    • Адхіліце і пераканайцеся, што вузол дзеяння ніколі не выконваецца, а не толькі што рэзультат разны.
    • Увведзіце некоректны адказ і пераканайцеся, што запит павтарыцца, а не продажваецца з некоректным дадзеннем.
    • Продажвачыце роботу з іншым thread_id і старайцеся, каб пачатковы запуск не быў парадуктаваны.
  • За дапамою стойкага пунктара перазпачатайце процес між паузай і продажчым запускам і пераканайцеся, што він усё ж такі завершыцца.
  • Дзе застосоўваецца тая ж праблема

    Электранаведчык ўсьмо толькі зручны прыклад. Такая ж структура падходзіць для будзь-якай дзеяння, якае трэба кантролюваць людзём: адправкі паведамленняў, апдэйтаў чы выдаленняў записаў, затверджэння платежаў, развяртання, знішчэння ресурсаў у хмаре, публікацыі створанага кантэнту чы падняття запитоў на падтрымку. Сама дзеяння меняецца; праблема, якая стоіць перед ёю, — ні. Якщо вы хочаце пабачыць крокі затверджэння разам з іншымі шаблонамі оркестрацыі, такімі як маршрутызацыя і розповсюджэння, гэты аптаварыс пяці шаблонаў LangGraph паказвае іх разам.

    Ключовыя выводы

    • interrupt() зупавляе граф і передае пакет дадзеных вашам прыкладку; значэнне, якім вы продаўжываеце роботу, становіцца яго значэннем на выходзе.
    • Command({ resume: ... }) вяртае адпаведны адказ чалавека назад у зупавлены процес.
    • Для зупавлення неабходны пункт контролю; у прыметнай роботе выкорыстоўваюць стойкі пункт контролю, каб санкцыі моглі прыйсці пасля перзапуску.
    • Для зупавлення і продаўжэння адпаведнага процесу неабходна выкорыстоўваць той самы thread_id.
    • Узел, які мае interrupt(), пад час продаўжэння роботы запускаецца зноў з самага пачатку, таму паслядкі дзеяння трэба размесціў у пазнейшых узлах або зробіць іх ідэмпатнымі.
    • Усё, што не ўзьязначае явную санкцыю, трэба направіць на зупавлення, каб воратак застаўся закрытым.

    Ўжо не трэба большога рабочага прыему, каб прадаць чалавеку контроль над агентам. Адна правильна пауза перед ключовым крокам дазволяе агенту выконваць большую частку работы, тады як чалавек застаецца тым, хто выносіць заканчальныя рашэнні.

    Спадзяючыся матэрыялы

    • Approval-Gated Agents у LangGraph: interrupt(), Checkpoints і a Store — Стварэнне агента LangGraph па кроках: явны граф ReAct, аправарэнне чалавека за дапамою interrupt(), памяць між потокамі з адпаведным храненнем, што завершаецца асистэнтам у папцы входзячых спрабоў, які спачатку ставіць запытанні.
  • Спецыялісты-агенты, маршрутазначальнік ключоў і функцыя interrupt(): наставник LangGraph — Адзін разрабатываць асистента LangGraph з двума спецыялістамі, які мае дэтерміністычны маршрутазначальнік, спяльны стан, який застаецца пасля перадачы задач, а таксама людзкі пункт контролю, створаны на аснове функцый interrupt і Command.
  • Перакананне ў тым, што робяць агенты AI: права доступу, бар’еры затверджэння і рангі рызыку — Дазвольце дазнацца, чаму агенты, якія выконваюць задачі, патрабуюць менталітэту пераканання, і як прынцып мінімальных прав, людзкага затверджэння і автонаміі, базаванай на рызыку, дапамагае контролюваць ўсілкія іхнія памылкі.
  • Паузаванне та продажчэнне агентаў LangGraph за дапамойкай interrupt() і Command — Как ЛангГрафтавыя функціі interrupt() і Command выкарыстоўваюць пункты контролу та ниткі, каб на час зупініць агента для параджэння чалавека, ведзбыўства змян аб адпраўкі дадзенняў, а пасля безбедна яго запусціць знову.
  • Адсечкі строк у JavaScript, якія не пашкоджаюць эмодзі, за дапамойкай Intl.Segmenter — Чаму методы slice() та адсечкі на аднойчынных частках пашкоджаюць эмодзі та текст з акуратнымі знакамі, як з-за гэтага ломаліся рэальныя продукты, і як замест таго адсечаць строкі па межах графем.