Implementar un bucle de llamadas de herramientas LLM en Jitterbit Studio
Introducción
La llamada de función de una sola ronda (cubierta en Enrutar respuestas LLM a operaciones de Studio usando llamadas de función) maneja una selección de herramienta por solicitud: el modelo elige una función, se ejecuta una operación de Studio y la interacción termina. Muchos flujos de trabajo de agentes requieren más de una llamada de herramienta para responder a una única solicitud del usuario: el modelo puede necesitar consultar un registro de CRM, luego obtener detalles de la cuenta y luego redactar un resumen usando ambos resultados.
Un bucle de llamadas de herramientas maneja esto devolviendo cada resultado de herramienta al LLM como un mensaje de rol tool y llamando al modelo nuevamente con la conversación extendida. El bucle se repite hasta que el modelo produce una respuesta de texto sin formato sin llamadas de herramientas, o se alcanza un límite de iteración máximo.
Esta guía se basa en:
- Enrutar respuestas LLM a operaciones de Studio usando llamadas de función para la definición del esquema de herramientas y el patrón de envío de una sola ronda. Lee esa guía primero.
- Crear un chat LLM multiturno con historial de conversación si combinas el bucle de llamadas de herramientas con memoria de conversación persistente entre turnos de usuario.
Patrón de diseño
El bucle añade dos pasos al patrón de llamadas de función de una sola ronda: añadir el resultado de la herramienta al array de mensajes y llamar al LLM nuevamente. El bucle se repite mientras la respuesta del modelo incluya un array tool_calls.
Build initial request
(base messages + tools)"] --> B["HTTP v2
LLM call"] B --> C{"tool_calls
in response?"} C -->|Yes| D["Script
Extract function
name and ID"] D --> E["Dispatcher
Run tool
operation"] E --> F["Script
Append tool result
rebuild request"] F --> B C -->|No| G["Script
Extract final
response"]
Cinco variables globales mantienen el estado entre iteraciones:
| Variable | Propósito |
|---|---|
InAndOut |
El cuerpo de solicitud LLM actual (actualizado antes de cada llamada). |
non_tool_messages_json |
Los mensajes base (indicación del sistema y mensaje del usuario) como una cadena de array JSON. Permanece constante en todas las iteraciones. |
tools_messages |
Los mensajes de intercambio de herramientas acumulados de todas las rondas anteriores como una cadena de array JSON. Crece con cada iteración. |
tools_resp |
La respuesta LLM más reciente que contenía una selección tool_calls. Se utiliza para extraer el mensaje del asistente para su acumulación. |
call_llm_again |
Se establece en true cuando hay una llamada de herramienta en progreso; se establece en false cuando el modelo devuelve una respuesta final. |
Parte 1: Inicializar las variables del bucle
Antes de la primera llamada LLM, construye los mensajes base y el cuerpo de solicitud inicial, y reinicia todas las variables de estado del bucle. Añade un paso de script al inicio de la operación:
// Escape and build the base messages as a JSON array string
systemMsg = "{\"role\":\"system\",\"content\":\""
+ Replace($systemPrompt, "\"", "\\\"") + "\"}";
userMsg = "{\"role\":\"user\",\"content\":\""
+ Replace($userMessage, "\"", "\\\"") + "\"}";
$non_tool_messages_json = "[" + systemMsg + "," + userMsg + "]";
// Build the full initial request body
$InAndOut = "{\"model\":\"gpt-4o\","
+ "\"messages\":[" + systemMsg + "," + userMsg + "],"
+ "\"tools\":" + $toolsJson + ","
+ "\"tool_choice\":\"auto\"}";
// Initialize loop state
$tools_messages = "";
$tools_resp = "";
$call_llm_again = false;
$loop_count = 0;
toolsJson es una variable de proyecto que contiene el array de esquema de herramientas serializado. Para el formato de definición de herramientas y cómo construir los esquemas, consulta Enrutar respuestas LLM a operaciones de Studio usando llamadas de función.
Nota
Guarda los mensajes base en non_tool_messages_json antes de que comience el bucle. Cada iteración combina estos mensajes base con los intercambios de herramientas acumulados para reconstruir el array de mensajes completo. No actualices non_tool_messages_json dentro del bucle.
Parte 2: Analizar la respuesta de llamada de herramienta
Encadena una operación LLM Call (HTTP v2 POST al endpoint de OpenAI Chat Completions, con InAndOut como cuerpo de solicitud). Para la configuración de conexión y autenticación, consulta Llamar a una API REST usando el conector HTTP v2.
En caso de éxito, añade un paso de script para leer la respuesta y establecer las variables de control del bucle:
// Check whether the model selected a tool
toolCallsVal = GetJSONString($jitterbit.response, "/choices/0/message/tool_calls");
hasToolCalls = (toolCallsVal != "null" && Length(toolCallsVal) > 2);
If(hasToolCalls,
$tool_call_id = TrimChars(GetJSONString($jitterbit.response,
"/choices/0/message/tool_calls/0/id"), "\"");
$function_name = TrimChars(GetJSONString($jitterbit.response,
"/choices/0/message/tool_calls/0/function/name"), "\"");
$function_arguments = GetJSONString($jitterbit.response,
"/choices/0/message/tool_calls/0/function/arguments");
$tools_resp = $jitterbit.response;
$call_llm_again = true;
,
$call_llm_again = false;
$final_response = TrimChars(GetJSONString($jitterbit.response,
"/choices/0/message/content"), "\"");
);
tool_call_id es un identificador único que la API asigna a cada llamada de herramienta. Debe devolverse en el mensaje de resultado de herramienta en la Parte 4, exactamente como se recibió, o la API rechazará la solicitud.
Nota
tool_calls está ausente de la respuesta cuando el modelo devuelve texto sin formato. La llamada GetJSONString devuelve "null" en ese caso, y la verificación de longitud > 2 distingue correctamente un array poblado (que es como mínimo [...], longitud 3 o más) de un valor ausente o vacío.
Manejo de múltiples llamadas a herramientas
El script anterior lee tool_calls/0, la primera llamada a herramienta en la respuesta. Un LLM puede devolver varias llamadas a herramientas en una sola respuesta (llamadas a herramientas en paralelo), y se ignoran las llamadas posteriores a la primera. Para manejar cada llamada, realiza una de las siguientes acciones:
- Desactiva las llamadas a herramientas en paralelo en la solicitud del LLM para que el modelo devuelva como máximo una llamada por respuesta. Para las APIs de Chat Completions de OpenAI y Azure OpenAI, establece
parallel_tool_callsenfalseen el cuerpo de la solicitud creado en Parte 1. - Itera sobre el array
tool_calls, despachando cada herramienta y añadiendo un mensaje de resultadotoolpor entrada antes de la siguiente llamada al LLM. UsaGetJSONStringcon un índice incremental (por ejemplo,/choices/0/message/tool_calls/1/id) para leer cada llamada adicional. La API del LLM requiere un resultadotoolcoincidente para cadatool_call_iden el mensaje del asistente, que se envía una sola vez.
Parte 3: Despachar a la operación de herramienta
Usa una declaración Case para despachar a la operación de herramienta correcta según function_name, siguiendo el mismo patrón que en Enrutar respuestas del LLM a operaciones de Studio usando llamadas de función. Cada operación de destino ejecuta la función solicitada y establece function_resp en la cadena de resultado.
function_arguments contiene las selecciones de parámetros del modelo como una cadena JSON. Analízala usando JSONParser dentro de cada operación de destino para extraer valores de argumentos individuales.
Parte 4: Añadir el resultado de la herramienta y reconstruir la solicitud
Después de que la operación de herramienta establezca function_resp, construye el array de mensajes actualizado para la siguiente llamada al LLM. Este paso requiere manipulación JSON compleja: analizar los mensajes acumulados, añadir nuevas entradas y re-serializar el resultado. Implementalo como un paso de script de JavaScript:
var tool_resp = JSON.parse($tools_resp);
var prev_tool_msgs = $tools_messages ? JSON.parse($tools_messages) : [];
var base_msgs = JSON.parse($non_tool_messages_json);
// The assistant message that selected the tool (must precede the tool result)
var assistant_msg = tool_resp.choices[0].message;
// The tool result returned by the Studio operation
var tool_result_msg = {
"role": "tool",
"tool_call_id": $tool_call_id,
"content": String($function_resp)
};
// Accumulate all tool exchanges: assistant selection followed by tool result
prev_tool_msgs.push(assistant_msg);
prev_tool_msgs.push(tool_result_msg);
// Rebuild the full request with base messages + all accumulated tool exchanges
var req = JSON.parse($InAndOut);
req["messages"] = base_msgs.concat(prev_tool_msgs);
$tools_messages = JSON.stringify(prev_tool_msgs);
$InAndOut = JSON.stringify(req);
Para usar JavaScript en un paso de script, establece el lenguaje de script en JavaScript en el editor de scripts antes de añadir contenido.
Nota
El mensaje del asistente (tool_resp.choices[0].message) debe aparecer inmediatamente antes de su mensaje de resultado tool coincidente en el array. La API de OpenAI requiere que la selección tool_calls y el resultado tool correspondiente sean adyacentes y en el mismo orden en que se realizaron las llamadas. Un tool_call_id no coincidente o faltante causa un error 400.
Consejo
Si la operación de herramienta devuelve datos estructurados (por ejemplo, un objeto JSON), serialízalos a una cadena antes de asignarlos a function_resp. El campo content del mensaje con rol tool debe ser una cadena.
Parte 5: Controlar el bucle
Envuelve la llamada al LLM, el despachador y los pasos de reconstrucción de solicitud en un controlador de Script de Jitterbit usando un bucle While. Coloca este controlador en la parte superior de la cadena de operaciones:
maxIterations = 5;
// First LLM call (always runs before the loop)
RunOperation("<TAG>operation:LLM Call</TAG>");
// Loop until the model returns a final response or the limit is reached
while($call_llm_again == true && $loop_count < maxIterations,
$loop_count = $loop_count + 1;
RunOperation("<TAG>operation:Tool Dispatcher</TAG>");
RunScript("<TAG>script:Append Tool Result</TAG>");
RunOperation("<TAG>operation:LLM Call</TAG>");
);
If($loop_count >= maxIterations && $call_llm_again == true,
WriteToOperationLog("Tool-calling loop reached " + maxIterations
+ " iterations without a final response. Last function: " + $function_name)
);
La operación LLM Call usa InAndOut como cuerpo de la solicitud y ejecuta el analizador de respuesta de Parte 2 en caso de éxito. La operación Tool Dispatcher ejecuta la declaración Case de Parte 3. Append Tool Result es el script de JavaScript de Parte 4, referenciado aquí como un componente de script nombrado usando RunScript.
Establecer un límite máximo de iteraciones previene bucles descontrolados cuando una herramienta devuelve consistentemente un error y el modelo responde solicitando la misma herramienta nuevamente.
Consejo
Comienza con un límite de 5 iteraciones. La mayoría de los flujos de trabajo se resuelven en una o dos rondas; alcanzar consistentemente el límite señala un problema de diseño de indicaciones o resultado de herramienta en lugar de la necesidad de un límite más alto.
Alternativa: Construcción de mensajes con Script de Jitterbit
Si prefieres evitar JavaScript, la acumulación de mensajes en Parte 4 se puede implementar completamente en Script de Jitterbit usando concatenación de cadenas. El HR Agent implementa este enfoque, usando gpt.registeredTools como una variable de proyecto para mantener los esquemas de herramientas serializados (equivalente a toolsJson en esta guía), con call_llm_again controlando el bucle.
El enfoque de Jitterbit Script construye los mensajes del asistente y los resultados de herramientas concatenando cadenas JSON directamente, en lugar de usar JSON.parse y JSON.stringify. El array acumulado se mantiene extrayendo el mensaje del asistente de tools_resp usando GetJSONString y añadiendo el resultado de la herramienta como una cadena formateada:
// Extract the full assistant message object from the previous response
assistantMsgJson = GetJSONString($tools_resp, "/choices/0/message");
// Build the tool result message
escapedResp = Replace($function_resp, "\"", "\\\"");
toolResultJson = "{\"role\":\"tool\",\"tool_call_id\":\""
+ $tool_call_id + "\",\"content\":\"" + escapedResp + "\"}";
// Accumulate: start a new array or append to the existing one
If(Length($tools_messages) == 0,
$tools_messages = "[" + assistantMsgJson + "," + toolResultJson + "]"
,
$tools_messages = Left($tools_messages, Length($tools_messages) - 1)
+ "," + assistantMsgJson + "," + toolResultJson + "]"
);
// Rebuild the request by replacing the messages field
// (omitted: requires parsing and replacing the messages array in $InAndOut)
El reemplazo completo del campo messages en InAndOut requiere reemplazar el valor del array existente dentro de la cadena JSON, lo que es frágil si el contenido del mensaje contiene caracteres especiales. Usa el enfoque de JavaScript en Parte 4 cuando sea posible; reserva el enfoque de Jitterbit Script para proyectos donde JavaScript no está disponible.
Verifica la integración
-
Implementa y ejecuta la operación controladora con un mensaje de usuario que requiera exactamente una llamada de herramienta. Confirma en los registros de operación que
loop_countse incrementa a 1 y que la operación de herramienta se ejecutó y devolvió un resultado. -
En los registros, confirma que la segunda llamada al LLM recibió el array de mensajes extendido (registra
InAndOutusandoWriteToOperationLogantes de llamar al LLM) y que devolvió una respuesta final en texto plano sintool_calls. -
Envía un mensaje de usuario que requiera dos llamadas de herramienta secuenciales (por ejemplo, buscar un contacto y luego crear un ticket para ese contacto). Confirma que
loop_countalcanza 2, luego inspecciona los mensajes acumulados.tools_messageses una cadena de array JSON serializada, así que regístrala después de que se complete el bucle para ver su contenido:WriteToOperationLog("tools_messages: " + $tools_messages);En la cadena registrada, confirma que hay cuatro objetos, alternando
"role":"assistant"y"role":"tool"(dos de cada): cada ronda añade el mensaje del asistente que seleccionó la herramienta seguido de su resultadotool. Eltool_call_idde cada mensajetooldebe coincidir con eliden la entradatool_callsdel mensaje del asistente anterior. -
Envía un mensaje que no requiera ninguna llamada de herramienta (por ejemplo, una pregunta general que el modelo pueda responder con su propio conocimiento). Confirma que el bucle no se ejecuta (la llamada inicial al LLM no devuelve
tool_calls,call_llm_againpermanecefalseyloop_countpermanece en 0). -
Si la API devuelve un error 400 citando un
tool_call_idinválido, registra el valor detool_call_idy compáralo con el campoidenchoices[0].message.tool_calls[0]de la respuesta anterior del LLM. Una discrepancia típicamente significa quetools_respse actualizó antes de que se extrajera el ID. -
Si el bucle alcanza el límite de iteraciones, registra
InAndOutal inicio de cada ronda para inspeccionar los mensajes acumulados. Una llamada de herramienta repetida para la misma función con los mismos argumentos indica que la operación de herramienta está devolviendo un resultado de error que el modelo está reintentando, o que la descripción de la herramienta no coincide con la solicitud del usuario.