Agente de múltiples usuarios Swarm para Ollama

Hace mucho tiempo, la interfaz gráfica de usuario reemplazó la entrada de línea de comandos. Puede parecer que una interfaz pseudorgráfica podría resolver el problema de interacción para los usuarios no preparados, pero hay un factor que no todos se dan cuenta.

¡Importante! Desarrollar una interfaz de usuario gráfica es más barata que una pseudorgrafías. Históricamente, justo después del próximo lanzamiento de Cube, OBJC se introdujo con un editor de formulario gráfico donde las páginas se podían organizar con un mouse. En el mundo moderno, Frontend proporciona depuración de forma gráfica a través de herramientas de desarrollo, que es esencialmente lo mismo: código nominal sin detalles técnicos, y cuando surgen problemas, hay una GUI que hace que los errores sean más baratos.

Pero es aún más barato no hacer una interfaz de usuario en absoluto. No necesita una IP estática, PCI DSS, un dominio promovido en Yandex y Google, o HighLoad si decide no reinventar la rueda y crear otro producto web que costará tres veces más atraer a los visitantes que desarrollarse.

El teléfono proviene de la palabra "teléfono", fonética - sonido. En lugar de aprender una gran cantidad de combinaciones de botones para Figma, Blender, Photoshop, Unreal Engine, es más simple simplemente expresar el comando. ¿Cómo se gira un dibujo en Archicad?
Agent Swarm es como fragmentos en Android o un enrutador en React: le permiten especificar el alcance de las tareas (botones en la pantalla) en función de la entrada del usuario anterior. Por ejemplo, cuando se encuentra una llamada en un teléfono SIP, primero debe comprender si la persona quiere comprar o devolver un artículo en principio, y luego ofrecerles una lista de productos disponibles.
La oficina de impuestos siempre solicitará débito/crédito en forma tabular, por lo que los sistemas CRM no irán a ningún lado. La tarea de LLM es analizar el texto natural desde el reconocimiento de chat o voz y transformarlo en una firma de función con el nombre y los argumentos para que pueda llamarse y los datos se pueden escribir en la base de datos.
Para resolver este problema, es importante conocer varios matices:
Para cada sesión de chat abierta, la orquestación de enjambre debe realizarse con un árbol de agentes que tiene un historial de chat compartido entre ellos y separarse para diferentes usuarios. En este código, se implementa bajo el capó del agente-kit-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 ( ) ;
} ,
}
} ) ) ;Al crear un agente, especificamos al menos un mensaje del sistema que describe lo que debe hacer. Especificamos el conector al modelo de lenguaje, que permitirá que algunos agentes se procesen localmente de forma gratuita, mientras delegaron los complejos al servicio de nube de OpenAI. Si algo no funciona, agregamos indicaciones a la matriz del sistema, por ejemplo, correcciones de llamadas de función 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 ] ,
} ) ;En este ejemplo, uso Ollama para procesar las solicitudes de los usuarios. Para aquellos que no están familiarizados con la terminología: el proceso en el que un modelo de idioma recibe el historial de chat con un usuario como entrada y salida, se llama un nuevo mensaje. Agent-swarm-kit utiliza una interfaz abstracta que funciona de manera similar con cualquier proveedor de nube o modelo local. Use este artículo para conectar 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" ] ,
} ;
} ,
} ) ; Cambiar el agente activo y obtener datos de la base de datos se realiza a través de llamadas de herramientas: el modelo de idioma devuelve XML especial que es procesado por el marco para modelos locales o por el proveedor de la nube para OpenAI para llamar al código externo en Python/JS, etc. El resultado de la ejecución se registra en el historial de chat como {"role": "tool", "content": "Product Paracetamol found in database: fever reducer for fighting flu"} . Desde el siguiente mensaje de usuario, el modelo de idioma funciona con datos de la herramienta.
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 la codificación de mensajes de agente inicial, al cambiar de agentes, se produce una simulación de solicitud de usuario pidiendo saludar.
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" ] ,
} ,
} ,
} ) ;Los modelos de idiomas pueden formar un diccionario de parámetros con nombre para llamadas de herramientas. Sin embargo, los modelos OpenSource manejan esto mal si hay un requisito técnico para un circuito cerrado; Es más simple analizar la conversación en sí.
Múltiples sesiones de chatgpt (agentes) hacen llamadas de herramientas. Cada agente puede usar diferentes modelos, por ejemplo, Mistral 7B para la comunicación diaria, Nemoton para conversaciones comerciales.
El agente enjambre dirige los mensajes a la sesión de ChatGPT activa (agente) para cada canal de WebSocket utilizando el parámetro de URL ClientID. Para cada nuevo chat con una persona, se crea un nuevo canal con su propio enjambre de agentes.
La sesión de chatgpt activa (agente) en el enjambre se puede cambiar ejecutando una herramienta.
Todas las sesiones de clientes utilizan un historial de mensajes de chat compartidos para todos los agentes. El historial de chat de cada cliente almacena los últimos 25 mensajes con rotación. Entre las sesiones de ChatGPT (agentes), solo se transmiten mensajes de tipo de asistente y de usuario, mientras que los mensajes del sistema y la herramienta se limitan al alcance del agente, por lo que cada agente solo conoce las herramientas que se relacionan con él. Como resultado, cada sesión de ChatGPT (agente) tiene su mensaje único del sistema.
Si la salida de un agente falla la validación (llamada de herramienta inexistente, llamada de herramienta con argumentos incorrectos, salida vacía, etiquetas XML en salida o JSON en salida), el algoritmo de rescate intentará corregir el modelo. Primero, ocultará mensajes anteriores del modelo; Si eso no ayuda, devolverá a un marcador de posición como "Lo siento, no entendí. ¿Podría repetirlo?"
addAgent - Registre un nuevo agenteaddCompletion - Registre un nuevo modelo de idioma: nube, local o simuladoaddSwarm : registre un grupo de agentes para procesar chats de usuariosaddTool : registre una herramienta para integrar modelos de lenguaje en sistemas externoschangeAgent - Cambiar agente activo en el enjambrecomplete : solicite una respuesta a un mensaje pasado al enjambre del agentesession : cree una sesión de chat, proporcione devoluciones de llamada para la finalización de la sesión y el nuevo envío de mensajesgetRawHistory - Obtener historial de sistema sin procesar para la depuracióngetAgentHistory : obtenga un historial visible para el agente ajustado para el mecanismo de auto recuperación y los destinatarios de los mensajescommitToolOutput : envíe el resultado de la ejecución de la función al historial. Si se llamaba a una función, el agente se congela hasta recibir una respuestacommitSystemMessage - Sistema de suplementos con nuevas entradascommitFlush : claro conversación para el agente si se recibieron respuestas incorrectas o el modelo llama erróneamente una herramienta recursivamenteexecute : solicite a la red neuronal que tome la iniciativa y escriba primero al usuarioemit : enviar un mensaje prepreparado al usuariogetLastUserMessage : obtenga el último mensaje del usuario (sin considerar ejecutar)commitUserMessage : guarde el mensaje de usuario en el historial de chat sin respuesta. Si los mensajes de Spams de los usuarios sin esperar el procesamiento de solicitudesgetAgentName - Obtener nombre de agente activogetUserHistory - Obtener historial de mensajes de usuariogetAssistantHistory : obtenga la historia de los mensajes del modelo de idiomagetLastAssistantMessage - Obtenga el último mensaje del modelo de idiomagetLastSystemMessage : obtenga el último suplemento de indicación del sistema