Agente de vários usuários Swarm para Ollama

Há muito tempo, a interface gráfica do usuário substituiu a entrada da linha de comando. Pode parecer que uma interface pseudo-gráfica possa resolver o problema de interação para usuários despreparados, mas há um fator que nem todos percebem.

Importante! O desenvolvimento de uma interface gráfica do usuário é mais barato que o pseudo-graphical. Historicamente, logo após a próxima liberação do cubo, o OBJC foi introduzido com um editor de formulário gráfico, onde as páginas podem ser organizadas com um mouse. No mundo moderno, o Frontend fornece depuração de formulários gráficos através de ferramentas de desenvolvimento, o que é essencialmente a mesma coisa: código nominal sem detalhes técnicos e quando surgem problemas, há uma GUI que faz encontrar bugs mais baratos.

Mas é até mais barato não fazer uma interface de usuário. Você não precisa de um IP estático, PCI DSS, um domínio promovido em Yandex e Google ou Highload se decidir não reinventar a roda e criar mais um produto da Web que custará três vezes mais para atrair visitantes do que se desenvolver.

O telefone vem da palavra "telefone", fonética - som. Em vez de aprender um grande número de combinações de botões para figma, liquidificador, photoshop, motor irreal, é mais simples apenas expressar o comando. Como você gira um desenho no Archicad?
O agente Swarm é como fragmentos no Android ou um roteador no React: eles permitem especificar o escopo das tarefas (botões na tela) com base na entrada anterior do usuário. Por exemplo, quando uma chamada aparece em um telefone SIP, primeiro você precisa entender se a pessoa deseja comprar ou devolver um item em princípio e depois oferecer uma lista de produtos disponíveis.
O escritório tributário sempre solicitará o débito/crédito em forma de tabular, para que os sistemas de CRM não vão a lugar nenhum. A tarefa do LLM é analisar o texto natural do reconhecimento de bate -papo ou voz e transformá -lo em uma assinatura de função com nome e argumentos para que possa ser chamado e os dados possam ser gravados no banco de dados.
Para resolver esse problema, é importante conhecer várias nuances:
Para cada sessão de bate -papo aberto, a orquestração de enxame precisa ser realizada com uma árvore de agentes com um histórico de bate -papo compartilhado entre eles e separado para diferentes usuários. Neste código, ele é implementado sob o capô do agente-swarm-kit.
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 ( ) ;
} ,
}
} ) ) ;Ao criar um agente, especificamos pelo menos uma mensagem do sistema descrevendo o que deve fazer. Especificamos o conector para o modelo de idioma, que permitirá que alguns agentes sejam processados localmente gratuitamente, enquanto delegar os complexos no serviço em nuvem Openai. Se algo não funcionar, adicionamos instruções à matriz do sistema, por exemplo, as correções de chamadas de função para 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 ] ,
} ) ;Neste exemplo, eu uso o Ollama para processar solicitações de usuário. Para aqueles que não estão familiarizados com a terminologia: o processo em que um modelo de idioma recebe histórico de bate -papo com um usuário como entrada e produz uma nova mensagem é chamada de conclusão. O agente-swarm-kit usa uma interface abstrata que funciona da mesma forma com qualquer provedor de nuvem ou modelo local. Use este artigo para conectar o 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" ] ,
} ;
} ,
} ) ; Alterar o agente ativo e obter dados do banco de dados são feitos através de chamadas de ferramentas: o modelo de idioma retorna XML especial que é processado pela estrutura para modelos locais ou pelo provedor de nuvem para o OpenAI chamar código externo em Python/JS, etc. O resultado da execução é registrado no histórico de bate -papo como {"role": "tool", "content": "Product Paracetamol found in database: fever reducer for fighting flu"} A partir da próxima mensagem do usuário, o modelo de idioma opera com dados da ferramenta.
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 : [ ] ,
} ,
} ,
} ) ;Para evitar mensagens iniciais de codificação, ao alternar os agentes, ocorre uma simulação de solicitação do usuário pedindo para dizer olá.
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" ] ,
} ,
} ,
} ) ;Os modelos de idiomas podem formar um dicionário de parâmetros nomeados para chamadas de ferramentas. No entanto, os modelos OpenSource lidam com isso mal se houver um requisito técnico para um circuito fechado; É mais simples analisar a conversa em si.
Várias sessões de bate -papo (agentes) fazem chamadas de ferramentas. Cada agente pode usar modelos diferentes, por exemplo, MISTRAL 7B para comunicação cotidiana, Nemoton para conversas de negócios.
O agente Swarm direciona as mensagens para a sessão ativa do ChatGPT (agente) para cada canal do WebSocket usando o parâmetro CLIENTID URL. Para cada novo bate -papo com uma pessoa, um novo canal é criado com seu próprio enxame de agentes.
A sessão ativa do ChatGPT (agente) no enxame pode ser alterada executando uma ferramenta.
Todas as sessões do cliente usam um histórico de mensagens de bate -papo compartilhado para todos os agentes. O histórico de bate -papo de cada cliente armazena as últimas 25 mensagens com rotação. Entre as sessões do ChatGPT (agentes), apenas mensagens de assistente e tipo de usuário são transmitidas, enquanto as mensagens do sistema e da ferramenta são limitadas ao escopo do agente; portanto, cada agente conhece apenas as ferramentas relacionadas a ele. Como resultado, cada sessão de chatgpt (agente) possui seu prompt de sistema exclusivo.
Se a saída de um agente falhar com a validação (chamada de ferramenta inexistente, chamada de ferramenta com argumentos incorretos, saída vazia, tags XML na saída ou JSON na saída), o algoritmo de resgate tentará corrigir o modelo. Primeiro, ocultará mensagens anteriores do modelo; Se isso não ajudar, retornará um espaço reservado como "Desculpe, eu não entendi. Você poderia repetir?"
addAgent - registre um novo agenteaddCompletion - Registre um novo Modelo de Idioma: Cloud, Local ou MockaddSwarm - Registre um grupo de agentes para processamento de bate -papos de usuárioaddTool - Registre uma ferramenta para integrar modelos de linguagem em sistemas externoschangeAgent - Alterar agente ativo no enxamecomplete - Solicite uma resposta a uma mensagem passada para o enxame do agentesession - Crie uma sessão de bate -papo, forneça retornos de chamada para conclusão da sessão e nova mensagemgetRawHistory - Obtenha o histórico de sistemas brutos para depuraçãogetAgentHistory - Torne o histórico visível para o agente ajustado para mecanismo de auto -recuperação e destinatários de mensagenscommitToolOutput - Enviar resultado da execução da função para o histórico. Se uma função foi chamada, o agente congela até receber uma respostacommitSystemMessage - sistema de suplementos Prompt com novas entradascommitFlush - conversa clara para o agente se as respostas incorretas foram recebidas ou o modelo chama erroneamente uma ferramenta recursivamenteexecute - Peça à rede neural para tomar iniciativa e escreva para o usuário primeiroemit - envie uma mensagem pré -preparada ao usuáriogetLastUserMessage - Receba a última mensagem do usuário (sem considerar a execução)commitUserMessage - salve a mensagem do usuário no histórico de bate -papo sem resposta. Se o usuário spam as mensagens sem esperar pelo processamento de solicitaçãogetAgentName - Obtenha o nome do agente ativogetUserHistory - Obtenha histórico de mensagens do usuáriogetAssistantHistory - Obtenha histórico de mensagens do modelo de idiomagetLastAssistantMessage - Receba a última mensagem do Modelo de IdiomagetLastSystemMessage - Obtenha o último suplemento de prompt do sistema