Многопользовательский агент Swarm для Ollama

Давным-давно графический интерфейс пользователя заменил ввод командной строки. Может показаться, что псевдо-графический интерфейс может решить проблему взаимодействия для неподготовленных пользователей, но есть фактор, который не все замечает.

Важный! Разработка графического интерфейса пользователя дешевле, чем псевдо-графический. Исторически, сразу после следующего выпуска куба, OBJC был введен с графическим редактором форм, где страницы можно было организовать с мышью. В современном мире Frondend обеспечивает отладь графической формы с помощью Dev Tools, что, по сути, одно и то же: номинальный код без технических деталей, и когда возникают проблемы, существует графический интерфейс, который делает ошибку дешевле.

Но даже дешевле вообще не сделать пользовательский интерфейс. Вам не нужен статический IP, PCI DSS, домен, продвигаемый в Yandex и Google, или Highload, если вы решите не изобретать колесо и создать еще один веб -продукт, который будет стоить в три раза дороже для привлечения посетителей, чем для развития.

Телефон происходит от слова «телефон», фонетика - звук. Вместо того, чтобы изучать огромное количество комбинаций кнопок для Figma, Blender, Photoshop, Unreal Engine, проще просто высказать команду. Как вращать рисунок в Архикаде?
Agent Swarm похож на фрагменты в Android или маршрутизатор в React: они позволяют вам указать объем задач (кнопки на экране) на основе предыдущего пользовательского ввода. Например, когда звонок поступает по телефону SIP, вам сначала нужно понять, хочет ли человек купить или вернуть товар в принципе, а затем предложить им список доступных продуктов.
Налоговая служба всегда будет запрашивать дебет/кредит в табличной форме, поэтому CRM -системы никуда не уходят. Задача LLM состоит в том, чтобы проанализировать естественный текст по распознаванию чата или голоса и преобразовать его в подпись функции с именем и аргументами, чтобы его можно было вызвать, а данные могли быть записаны в базу данных.
Чтобы решить эту проблему, важно знать несколько нюансов:
Для каждой сеанса открытого чата рой должен выполняться с деревом агентов, имеющих общую историю чата между ними и отдельно для разных пользователей. В этом коде он реализован под капотом агента-сварка.
import { addSwarm } from "agent-swarm-kit" ;
export const ROOT_SWARM = addSwarm ( {
swarmName : 'root_swarm' ,
agentList : [
TRIAGE_AGENT ,
SALES_AGENT ,
] ,
defaultAgent : TRIAGE_AGENT ,
} ) ;
...
app . get ( "/api/v1/session/:clientId" , upgradeWebSocket ( ( ctx ) => {
const clientId = ctx . req . param ( "clientId" ) ;
const { complete , dispose } = session ( clientId , ROOT_SWARM )
return {
onMessage : async ( event , ws ) => {
const message = event . data . toString ( ) ;
ws . send ( await complete ( message ) ) ;
} ,
onClose : async ( ) => {
await dispose ( ) ;
} ,
}
} ) ) ;При создании агента мы указываем хотя бы одно системное сообщение, описывающее, что он должен делать. Мы указываем соединитель с языковой моделью, которая позволит некоторым агентам быть обработанными локальными бесплатными, при этом делегируя сложные в облачный сервис Openai. Если что -то не работает, мы добавляем подсказки в системный массив, например, исправления вызова функций для Ollama.
const AGENT_PROMPT = `You are a sales agent that handles all actions related to placing the order to purchase an item.
Tell the users all details about products in the database by using necessary tool calls
Do not send any JSON to the user. Format it as plain text. Do not share any internal details like ids, format text human readable
If the previous user messages contains product request, tell him details immidiately
It is important not to call tools recursive. Execute the search once
` ;
/**
* @see https://github.com/ollama/ollama/blob/86a622cbdc69e9fd501764ff7565e977fc98f00a/server/model.go#L158
*/
const TOOL_PROTOCOL_PROMPT = `For each function call, return a json object with function name and arguments within <tool_call></tool_call> XML tags:
<tool_call>
{"name": <function-name>, "arguments": <args-json-object>}
</tool_call>
` ;
export const SALES_AGENT = addAgent ( {
agentName : "sales_agent" ,
completion : OLLAMA_COMPLETION ,
system : [ TOOL_PROTOCOL_PROMPT ] ,
prompt : AGENT_PROMPT ,
tools : [ SEARCH_PHARMA_PRODUCT , NAVIGATE_TO_TRIAGE ] ,
} ) ;В этом примере я использую Ollama для обработки запросов пользователей. Для тех, кто не знаком с терминологией: процесс, в котором языковая модель получает историю чата с пользователем в качестве ввода и выводит, новое сообщение называется завершением. Agent-Swarm-KIT использует абстрактный интерфейс, который работает аналогичным образом с любым облачным провайдером или локальной моделью. Используйте эту статью, чтобы подключить DeepSeek.
import { addCompletion , IModelMessage } from "agent-swarm-kit" ;
const getOllama = singleshot ( ( ) => new Ollama ( { host : CC_OLLAMA_HOST } ) ) ;
export const OLLAMA_COMPLETION = addCompletion ( {
completionName : "ollama_completion" ,
getCompletion : async ( {
agentName ,
messages ,
mode ,
tools ,
} ) => {
const response = await getOllama ( ) . chat ( {
model : "nemotron-mini:4b" , // "mistral-nemo:12b";
keep_alive : "1h" ,
messages : messages . map ( ( message ) => omit ( message , "agentName" , "mode" ) ) ,
tools ,
} ) ;
return {
... response . message ,
mode ,
agentName ,
role : response . message . role as IModelMessage [ "role" ] ,
} ;
} ,
} ) ; Изменение активного агента и получение данных из базы данных выполняется с помощью инструментов: языковая модель возвращает специальный XML, который обрабатывается структурой для местных моделей или облачным поставщиком для Openai для названия внешнего кода в Python/JS и т. Д. Результат выполнения записан в истории чата как {"role": "tool", "content": "Product Paracetamol found in database: fever reducer for fighting flu"} . Из следующего пользовательского сообщения, языковая модель работает с данными из инструмента.
import { addTool , changeAgent , execute } from "agent-swarm-kit" ;
const PARAMETER_SCHEMA = z . object ( { } ) . strict ( ) ;
export const NAVIGATE_TO_SALES = addTool ( {
toolName : "navigate_to_sales_tool" ,
validate : async ( clientId , agentName , params ) => {
const { success } = await PARAMETER_SCHEMA . spa ( params ) ;
return success ;
} ,
call : async ( clientId , agentName ) => {
await commitToolOutput (
"Navigation success`,
clientId ,
agentName
) ;
await changeAgent ( SALES_AGENT , clientId ) ;
await execute ( "Say hello to the user" , clientId , SALES_AGENT ) ;
} ,
type : "function" ,
function : {
name : "navigate_to_sales_tool" ,
description : "Navigate to sales agent" ,
parameters : {
type : "object" ,
properties : { } ,
required : [ ] ,
} ,
} ,
} ) ;Чтобы избежать твердого кодирования первоначальных сообщений агента при переключении агентов, происходит моделирование запроса пользователя с просьбой поздороваться.
import {
addTool ,
commitSystemMessage ,
commitToolOutput ,
execute ,
getLastUserMessage ,
} from "agent-swarm-kit" ;
const PARAMETER_SCHEMA = z
. object ( {
description : z
. string ( )
. min ( 1 , "Fulltext is required" )
} )
. strict ( ) ;
export const SEARCH_PHARMA_PRODUCT = addTool ( {
toolName : "search_pharma_product" ,
validate : async ( clientId , agentName , params ) => {
const { success } = await PARAMETER_SCHEMA . spa ( params ) ;
return success ;
} ,
call : async ( clientId , agentName , params ) => {
let search = "" ;
if ( params . description ) {
search = String ( params . description ) ;
} else {
search = await getLastUserMessage ( clientId ) ;
}
if ( ! search ) {
await commitToolOutput (
str . newline ( `The products does not found in the database` ) ,
clientId ,
agentName
) ;
await execute (
"Tell user to specify search criteria for the pharma product" ,
clientId ,
agentName
) ;
return ;
}
const products = await ioc . productDbPublicService . findByFulltext (
search ,
clientId
) ;
if ( products . length ) {
await commitToolOutput (
str . newline (
`The next pharma product found in database: ${ products . map (
serializeProduct
) } `
) ,
clientId ,
agentName
) ;
await commitSystemMessage (
"Do not call the search_pharma_product next time!" ,
clientId ,
agentName
) ;
await execute (
"Tell user the products found in the database." ,
clientId ,
agentName
) ;
return ;
}
await commitToolOutput (
`The products does not found in the database` ,
clientId ,
agentName
) ;
await execute (
"Tell user to specify search criteria for the pharma product" ,
clientId ,
agentName
) ;
} ,
type : "function" ,
function : {
name : "search_pharma_product" ,
description :
"Retrieve several pharma products from the database based on description" ,
parameters : {
type : "object" ,
properties : {
description : {
type : "string" ,
description :
"REQUIRED! Minimum one word. The product description. Must include several sentences with description and keywords to find a product" ,
} ,
} ,
required : [ "description" ] ,
} ,
} ,
} ) ;Языковые модели могут сформировать словарь названных параметров для инструментов. Тем не менее, модели OpenSource обрабатывают это плохо, если есть технические требования для закрытого цепи; Проще анализировать сам разговор.
Несколько сеансов CHATGPT (агенты) делают звонки с инструментами. Каждый агент может использовать разные модели, например, Mistral 7B для повседневного общения, Nemoton для деловых разговоров.
Agent Swarm направляет сообщения в активное сеанс CHATGPT (агент) для каждого канала WebSocket, используя параметр URL ClientID. Для каждого нового чата с человеком создается новый канал со своим собственным роем агентов.
Активный сеанс CHATGPT (агент) в рой может быть изменена путем выполнения инструмента.
Все сессии клиентов используют общую историю сообщений чата для всех агентов. История чата каждого клиента хранит последние 25 сообщений с вращением. Между сеансами CHATGPT (агенты) передаются только сообщения помощника и пользователя, в то время как системы системы и инструментов ограничены масштабами агента, поэтому каждый агент знает только инструменты, которые относятся к нему. В результате каждый сеанс CHATGPT (агент) имеет свою уникальную систему.
Если вывод агента не выполняет проверку (несуществующий вызов инструмента, вызов инструмента с неправильными аргументами, пустым выводом, тегами XML в выводе или JSON в выводе), алгоритм спасения попытается исправить модель. Во -первых, он скрывает предыдущие сообщения от модели; Если это не поможет, это вернет заполнителя, как «Извините, я не понял. Не могли бы вы повторить?»
addAgent - зарегистрируйте новый агентaddCompletion - Зарегистрируйте новую языковую модель: облачный, локальный или макетaddSwarm - зарегистрируйте группу агентов для обработки чатов пользователейaddTool - зарегистрируйте инструмент для интеграции языковых моделей во внешние системыchangeAgent - изменить активного агента в ройcomplete - запросить ответ на сообщение, переданное на рой агентаsession - Создайте сеанс чата, предоставьте обратные вызовы для завершения сеанса и отправки новых сообщенийgetRawHistory - Получите историю системы необработанной системы для отладкиgetAgentHistory - Получите историю видимой для агента, скорректированного для механизма самообслуживания и получателей сообщенийcommitToolOutput - отправить результат выполнения функции в историю. Если была вызвана функция, агент замерзает до получения ответаcommitSystemMessage - Сводная система добавок с новыми входамиcommitFlush - четкий разговор для агента, если были получены неправильные ответы или модель ошибочно вызывает инструмент рекурсивноexecute - Попросите нейронную сеть принять инициативу и сначала написать пользователюemit - отправьте предварительно подготовленное сообщение пользователюgetLastUserMessage - Получите последнее сообщение от пользователя (без учета выполнения)commitUserMessage - Сохранить сообщение пользователя в истории чата без ответа. Если пользователь спамит сообщения, не ожидая обработки запросовgetAgentName - получить активное имя агентаgetUserHistory - получить историю пользовательских сообщенийgetAssistantHistory - Получить историю языковых модельных сообщенийgetLastAssistantMessage - Получить последнее сообщение от языковой моделиgetLastSystemMessage - Получите последнее приложение системы