Ir para o conteúdo

Implemente um loop de chamada de ferramenta LLM no Jitterbit Studio

Introdução

A chamada de função de uma única rodada (cobrindo em Roteie respostas LLM para operações do Studio usando chamada de função) lida com uma seleção de ferramenta por solicitação: o modelo escolhe uma função, uma operação do Studio a executa e a interação termina. Muitos fluxos de trabalho agentes requerem mais de uma chamada de ferramenta para responder a uma única solicitação do usuário: o modelo pode precisar consultar um registro de CRM, depois buscar detalhes da conta e, em seguida, elaborar um resumo usando ambos os resultados.

Um loop de chamada de ferramenta lida com isso retornando cada resultado da ferramenta para o LLM como uma mensagem de papel tool e chamando o modelo novamente com a conversa estendida. O loop se repete até que o modelo produza uma resposta em texto simples sem chamadas de ferramenta ou um limite máximo de iterações seja alcançado.

Este guia se baseia em:

Padrão de design

O loop adiciona duas etapas ao padrão de chamada de função de uma única rodada: anexar o resultado da ferramenta ao array de mensagens e chamar o LLM novamente. O loop se repete enquanto a resposta do modelo incluir um 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 variáveis globais mantêm o estado entre as iterações:

Variável Propósito
InAndOut O corpo da solicitação atual do LLM (atualizado antes de cada chamada).
non_tool_messages_json As mensagens base (prompt do sistema e mensagem do usuário) como uma string de array JSON. Permanece constante em todas as iterações.
tools_messages As mensagens acumuladas de troca de ferramentas de todas as rodadas anteriores como uma string de array JSON. Cresce a cada iteração.
tools_resp A resposta mais recente do LLM que continha uma seleção de tool_calls. Usada para extrair a mensagem do assistente para acumulação.
call_llm_again Definido como true quando uma chamada de ferramenta está em andamento; definido como false quando o modelo retorna uma resposta final.

Parte 1: Inicializar as variáveis de loop

Antes da primeira chamada LLM, construa as mensagens base e o corpo da solicitação inicial, e redefina todas as variáveis de estado do loop. Adicione um passo de script no início da operação:

// 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 é uma variável de projeto que contém o array de esquema de ferramenta serializado. Para o formato de definição da ferramenta e como construir os esquemas, veja Roteie as respostas LLM para operações do Studio usando chamadas de função.

Nota

Salve as mensagens base em non_tool_messages_json antes do início do loop. Cada iteração combina essas mensagens base com as trocas de ferramenta acumuladas para reconstruir o array completo de mensagens. Não atualize non_tool_messages_json dentro do loop.

Parte 2: Analisar a resposta da chamada da ferramenta

Encadeie uma operação de Chamada LLM (HTTP v2 POST para o endpoint de Conclusões de Chat da OpenAI, com InAndOut como corpo da solicitação). Para configuração de conexão e autenticação, veja Chame uma API REST usando o conector HTTP v2.

Em caso de sucesso, adicione um passo de script para ler a resposta e definir as variáveis de controle do loop:

// 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 é um identificador único que a API atribui a cada chamada de ferramenta. Ele deve ser retornado na mensagem de resultado da ferramenta em Parte 4, exatamente como recebido, ou a API rejeitará a solicitação.

Nota

tool_calls está ausente da resposta quando o modelo retorna texto simples. A chamada GetJSONString retorna "null" nesse caso, e a verificação de comprimento > 2 distingue corretamente um array populado (que é, no mínimo, [...], comprimento 3 ou mais) de um valor ausente ou vazio.

Tratando múltiplas chamadas de ferramenta

O script acima lê tool_calls/0, a primeira chamada de ferramenta na resposta. Um LLM pode retornar várias chamadas de ferramenta em uma única resposta (chamadas de ferramenta paralelas), e quaisquer chamadas após a primeira são ignoradas. Para lidar com cada chamada, faça uma das seguintes opções:

  • Desative chamadas de ferramentas paralelas na solicitação LLM para que o modelo retorne no máximo uma chamada por resposta. Para as APIs de Chat Completions da OpenAI e Azure OpenAI, defina parallel_tool_calls como false no corpo da solicitação construído em Parte 1.
  • Itere sobre o array tool_calls, despachando a ferramenta e anexando uma mensagem de assistente/tool para cada entrada antes da próxima chamada LLM. A API LLM requer um resultado tool correspondente para cada tool_call_id na mensagem do assistente.

Parte 3: Despachar para a operação da ferramenta

Use uma declaração Case para despachar para a operação da ferramenta correta com base em function_name, seguindo o mesmo padrão de Roteie respostas LLM para operações do Studio usando chamadas de função. Cada operação de destino executa a função solicitada e define function_resp como a string de resultado.

function_arguments contém as seleções de parâmetros do modelo como uma string JSON. Analise-a usando JSONParser dentro de cada operação de destino para extrair valores individuais de argumento.

Parte 4: Anexar o resultado da ferramenta e reconstruir a solicitação

Após a operação da ferramenta definir function_resp, construa o array de mensagens atualizado para a próxima chamada LLM. Esta etapa requer manipulação complexa de JSON: analisando as mensagens acumuladas, anexando novas entradas e re-serializando o resultado. Implemente isso como um passo de script 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 em um passo de script, defina a linguagem do script como JavaScript no editor de Script antes de adicionar conteúdo.

Nota

A mensagem do assistente (tool_resp.choices[0].message) deve aparecer imediatamente antes de sua mensagem de resultado tool correspondente no array. A API OpenAI requer que a seleção tool_calls e o resultado tool correspondente estejam adjacentes e na mesma ordem em que as chamadas foram feitas. Um tool_call_id incompatível ou ausente causa um erro 400.

Dica

Se a operação da ferramenta retornar dados estruturados (por exemplo, um objeto JSON), serialize-o para uma string antes de atribuí-lo a function_resp. O campo content da mensagem de papel tool deve ser uma string.

Parte 5: Controle do loop

Envolva a chamada do LLM, o despachante e as etapas de reconstrução da solicitação em um controlador de Script Jitterbit usando um loop While. Coloque este controlador no topo da cadeia de operações:

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)
);

A operação LLM Call usa InAndOut como o corpo da solicitação e executa o parser de resposta da Parte 2 em caso de sucesso. A operação Tool Dispatcher executa a declaração Case da Parte 3. Append Tool Result é o script JavaScript da Parte 4, referenciado aqui como um componente de script nomeado usando RunScript.

Definir um limite máximo de iterações evita loops descontrolados quando uma ferramenta retorna consistentemente um erro e o modelo responde solicitando a mesma ferramenta novamente.

Dica

Comece com um limite de 5 iterações. A maioria dos fluxos de trabalho se resolve em uma ou duas rodadas; atingir consistentemente o limite sinaliza um problema de design do prompt ou resultado da ferramenta, em vez de uma necessidade de um limite maior.

Alternativa: Construção de mensagens em Script Jitterbit

Se preferir evitar JavaScript, a acumulação de mensagens na Parte 4 pode ser implementada inteiramente em Script Jitterbit usando concatenação de strings. O Agente de RH implementa essa abordagem, usando gpt.registeredTools como uma variável de projeto para armazenar os esquemas de ferramentas serializados (equivalente a toolsJson neste guia), com call_llm_again controlando o loop.

A abordagem de Script Jitterbit constrói as mensagens do assistente e do resultado da ferramenta concatenando strings JSON diretamente, em vez de usar JSON.parse e JSON.stringify. O array acumulado é mantido extraindo a mensagem do assistente de tools_resp usando GetJSONString e anexando o resultado da ferramenta como uma string formatada:

// 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)

A substituição completa do campo messages em InAndOut requer a substituição do valor do array existente dentro da string JSON, o que é frágil se o conteúdo da mensagem contiver caracteres especiais. Use a abordagem JavaScript em Parte 4 quando possível; reserve a abordagem Jitterbit Script para projetos onde o JavaScript não está disponível.

Verifique a integração

  1. Implante e execute a operação do controlador com uma mensagem de usuário que requer exatamente uma chamada de ferramenta. Confirme nos logs da operação que loop_count incrementa para 1 e que a operação da ferramenta foi executada e retornou um resultado.

  2. Nos logs, confirme que a segunda chamada LLM recebeu o array de mensagens estendido (registre InAndOut usando WriteToOperationLog antes de chamar o LLM) e que retornou uma resposta final em texto simples sem tool_calls.

  3. Envie uma mensagem de usuário que requer duas chamadas de ferramenta sequenciais (por exemplo, procurar um contato e depois criar um ticket para esse contato). Confirme que loop_count atinge 2, depois inspecione as mensagens acumuladas. tools_messages é uma string de array JSON serializado, então registre-a após a conclusão do loop para ver seu conteúdo:

    WriteToOperationLog("tools_messages: " + $tools_messages);
    

    Na string registrada, confirme que há quatro objetos, alternando "role":"assistant" e "role":"tool" (dois de cada): cada rodada anexa a mensagem do assistente que selecionou a ferramenta seguida pelo seu resultado tool. O tool_call_id de cada mensagem tool deve corresponder ao id na entrada tool_calls da mensagem do assistente anterior.

  4. Envie uma mensagem que não requer nenhuma chamada de ferramenta (por exemplo, uma pergunta geral que o modelo pode responder com seu próprio conhecimento). Confirme que o loop não é executado (a chamada LLM inicial não retorna tool_calls, call_llm_again permanece false e loop_count permanece 0).

  5. Se a API retornar um erro 400 citando um tool_call_id inválido, registre o valor de tool_call_id e compare-o com o campo id em choices[0].message.tool_calls[0] da resposta anterior do LLM. Um desvio geralmente significa que tools_resp foi atualizado antes que o ID fosse extraído.

  6. Se o loop atingir o limite de iterações, registre InAndOut no início de cada rodada para inspecionar as mensagens acumuladas. Uma chamada de ferramenta repetida para a mesma função com os mesmos argumentos indica que a operação da ferramenta está retornando um resultado de erro que o modelo está tentando novamente, ou que a descrição da ferramenta não corresponde ao pedido do usuário.