Saltar al contenido

Implementar un bucle de llamadas a herramientas LLM en Jitterbit Studio

Introducción

La llamada a funciones de una sola ronda (cubierta en Dirigir las respuestas LLM a las operaciones de Studio utilizando llamadas a funciones) 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 agentes requieren más de una llamada a la herramienta para responder a una sola solicitud del usuario: el modelo puede necesitar consultar un registro de CRM, luego obtener detalles de la cuenta y, a continuación, redactar un resumen utilizando ambos resultados.

Un bucle de llamadas a 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 en texto plano sin llamadas a herramientas, o se alcanza un límite máximo de iteraciones.

Esta guía se basa en:

Patrón de diseño

El bucle añade dos pasos al patrón de llamada a funciones de una sola ronda: agregar 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.

flowchart LR A["Script
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 llevan el estado a través de las iteraciones:

Variable Propósito
InAndOut El cuerpo de la solicitud actual del LLM (actualizado antes de cada llamada).
non_tool_messages_json Los mensajes base (mensaje del sistema y mensaje del usuario) como una cadena de array JSON. Permanece constante a través de todas las iteraciones.
tools_messages Los mensajes acumulados del intercambio de herramientas de todas las rondas anteriores como una cadena de array JSON. Crece con cada iteración.
tools_resp La respuesta más reciente del LLM que contenía una selección de tool_calls. Se utiliza para extraer el mensaje del asistente para la acumulación.
call_llm_again Se establece en true cuando una llamada a herramienta está 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 a LLM, construye los mensajes base y el cuerpo de la solicitud inicial, y restablece todas las variables de estado del bucle. Agrega 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 la herramienta y cómo construir los esquemas, consulta Dirigir las respuestas de LLM a las operaciones de Studio utilizando la llamada a 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 completo de mensajes. No actualices non_tool_messages_json dentro del bucle.

Parte 2: Analizar la respuesta de la llamada a la herramienta

Encadena una operación de LLM Call (HTTP v2 POST al endpoint de OpenAI Chat Completions, con InAndOut como el cuerpo de la solicitud). Para la configuración de conexión y autenticación, consulta Llamar a una API REST utilizando el conector HTTP v2.

En caso de éxito, agrega 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 a la herramienta. Debe ser devuelto en el mensaje de resultado de la herramienta en 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 plano. 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 la herramienta en la respuesta. Un LLM puede devolver varias llamadas a herramientas en una sola respuesta (llamadas a herramientas en paralelo), y cualquier llamada después de la primera se ignora. Para manejar cada llamada, haz una de las siguientes:

  • Desactive las llamadas a herramientas en paralelo en la solicitud LLM para que el modelo devuelva como máximo una llamada por respuesta. Para las APIs de OpenAI y Azure OpenAI Chat Completions, establezca parallel_tool_calls en false en el cuerpo de la solicitud construido en Parte 1.
  • Itere sobre el array tool_calls, despachando la herramienta y agregando un par de mensajes asistente/tool para cada entrada antes de la siguiente llamada LLM. La API LLM requiere un resultado tool correspondiente para cada tool_call_id en el mensaje del asistente.

Parte 3: Despachar a la operación de la herramienta

Utilice una declaración Case para despachar a la operación de herramienta correcta basada en function_name, siguiendo el mismo patrón que en Rotee las respuestas LLM a las operaciones de Studio usando llamadas a funciones. Cada operación objetivo 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ícelo utilizando JSONParser dentro de cada operación objetivo para extraer los valores individuales de los argumentos.

Parte 4: Agregar el resultado de la herramienta y reconstruir la solicitud

Después de que la operación de la herramienta establezca function_resp, construya el array de mensajes actualizado para la siguiente llamada LLM. Este paso requiere una manipulación compleja de JSON: analizar los mensajes acumulados, agregar nuevas entradas y re-serializar el resultado. Impleméntelo como un paso de script en 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, establezca el lenguaje del script en JavaScript en el editor de scripts antes de agregar contenido.

Nota

El mensaje del asistente (tool_resp.choices[0].message) debe aparecer inmediatamente antes de su mensaje de resultado tool correspondiente en el array. La API de OpenAI requiere que la selección de tool_calls y el resultado tool correspondiente sean adyacentes y en el mismo orden en que se realizaron las llamadas. Un tool_call_id desajustado o faltante causa un error 400.

Consejo

Si la operación de la herramienta devuelve datos estructurados (por ejemplo, un objeto JSON), sérialo a una cadena antes de asignarlo a function_resp. El campo content del mensaje de 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 la solicitud en un controlador de Jitterbit Script utilizando un bucle While. Coloca este controlador en la parte superior de la cadena de operación:

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 utiliza InAndOut como el 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 utilizando 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 indica un problema de diseño del aviso o del resultado de la herramienta en lugar de una necesidad de un límite más alto.

Alternativa: Construcción de mensajes en Jitterbit Script

Si prefieres evitar JavaScript, la acumulación de mensajes en Parte 4 se puede implementar completamente en Jitterbit Script utilizando concatenación de cadenas. El HR Agent implementa este enfoque, utilizando gpt.registeredTools como una variable de proyecto para mantener los esquemas de herramienta 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 del resultado de la herramienta 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 utilizando GetJSONString y agregando 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)

La sustitución completa del campo messages en InAndOut requiere reemplazar el valor del array existente dentro de la cadena JSON, lo cual es frágil si el contenido del mensaje contiene caracteres especiales. Utiliza el enfoque de JavaScript en Parte 4 cuando sea posible; reserva el enfoque de Jitterbit Script para proyectos donde JavaScript no esté disponible.

Verificar la integración

  1. Desplegar y ejecutar la operación del controlador con un mensaje de usuario que requiera exactamente una llamada a la herramienta. Confirma en los registros de operación que loop_count se incrementa a 1 y que la operación de la herramienta se ejecutó y devolvió un resultado.

  2. En los registros, confirma que la segunda llamada a LLM recibió el array de mensajes extendido (registra InAndOut usando WriteToOperationLog antes de llamar al LLM) y que devolvió una respuesta final en texto plano sin tool_calls.

  3. Envía un mensaje de usuario que requiera dos llamadas a herramientas secuenciales (por ejemplo, buscar un contacto y luego crear un ticket para ese contacto). Confirma que loop_count alcanza 2, luego inspecciona los mensajes acumulados. tools_messages es una cadena de array JSON serializada, así que regístrala después de que el bucle se complete 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 uno): cada ronda agrega el mensaje del asistente que seleccionó la herramienta seguido de su resultado tool. El tool_call_id de cada mensaje tool debe coincidir con el id en la entrada tool_calls del mensaje del asistente anterior.

  4. Envía un mensaje que no requiera ninguna llamada a la 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 a LLM no devuelve tool_calls, call_llm_again permanece en false, y loop_count se mantiene en 0).

  5. Si la API devuelve un error 400 citando un tool_call_id inválido, registra el valor de tool_call_id y compáralo con el campo id en choices[0].message.tool_calls[0] de la respuesta anterior del LLM. Un desajuste generalmente significa que tools_resp se actualizó antes de que se extrajera el ID.

  6. Si el bucle alcanza el límite de iteraciones, registra InAndOut al inicio de cada ronda para inspeccionar los mensajes acumulados. Una llamada a la herramienta repetida para la misma función con los mismos argumentos indica que la operación de la 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.