Implementar um loop de chamada de ferramenta LLM no Jitterbit Studio
Introdução
A chamada de função de rodada única (abordada em Rotear respostas de LLM para operações do Studio usando chamada de função) trata uma seleção de ferramenta por requisição: o modelo escolhe uma função, uma operação do Studio a executa e a interação termina. Muitos fluxos de trabalho com 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 trata isso retornando cada resultado da ferramenta ao LLM como uma mensagem de função 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 atingido.
Este guia se baseia em:
- Rotear respostas de LLM para operações do Studio usando chamada de função para definição de esquema de ferramenta e padrão de expedição de rodada única. Leia esse guia primeiro.
- Criar um chat LLM de múltiplas rodadas com histórico de conversa se você estiver combinando o loop de chamada de ferramenta com memória de conversa persistente entre os turnos do usuário.
Padrão de design
O loop adiciona duas etapas ao padrão de chamada de função de rodada única: anexar o resultado da ferramenta ao array de mensagens e chamar o LLM novamente. O loop se repete enquanto a resposta do modelo inclui um 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 variáveis globais carregam o estado entre iterações:
| Variável | Propósito |
|---|---|
InAndOut |
O corpo de requisição de LLM atual (atualizado antes de cada chamada). |
non_tool_messages_json |
As mensagens base (prompt do sistema e mensagem do usuário) como string de array JSON. Permanece constante em todas as iterações. |
tools_messages |
As mensagens de troca de ferramenta acumuladas de todas as rodadas anteriores como string de array JSON. Aumenta a cada iteração. |
tools_resp |
A resposta de LLM mais recente que continha uma seleção de tool_calls. Usada para extrair a mensagem do assistente para acumulação. |
call_llm_again |
Definida como true quando uma chamada de ferramenta está em andamento; definida como false quando o modelo retorna uma resposta final. |
Parte 1: Inicializar as variáveis do loop
Antes da primeira chamada de LLM, crie as mensagens base e o corpo de requisição inicial, e reinicie todas as variáveis de estado do loop. Adicione uma etapa 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 de ferramenta e como construir os esquemas, consulte Rotear respostas de LLM para operações do Studio usando chamada 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 de mensagens completo. Não atualize non_tool_messages_json dentro do loop.
Parte 2: Analisar a resposta da chamada de ferramenta
Encadeie uma operação de Chamada de LLM (HTTP v2 POST para o endpoint OpenAI Chat Completions, com InAndOut como corpo de requisição). Para configuração de conexão e autenticação, consulte Chamar uma API REST usando o conector HTTP v2.
Em caso de sucesso, adicione uma etapa 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 na Parte 4, exatamente como recebido, ou a API rejeitará a requisiçã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 preenchido (que é no mínimo [...], comprimento 3 ou mais) de um valor ausente ou vazio.
Tratando múltiplas chamadas de ferramentas
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 qualquer chamada após a primeira é ignorada. Para lidar com cada chamada, faça um dos seguintes:
- Desabilite as chamadas de ferramenta paralelas na solicitação do LLM para que o modelo retorne no máximo uma chamada por resposta. Para as APIs OpenAI e Azure OpenAI Chat Completions, defina
parallel_tool_callscomofalseno corpo da solicitação construído na Parte 1. - Itere sobre o array
tool_calls, despachando cada ferramenta e anexando uma mensagem de resultadotoolpor entrada antes da próxima chamada do LLM. UseGetJSONStringcom um índice incremental (por exemplo,/choices/0/message/tool_calls/1/id) para ler cada chamada adicional. A API do LLM requer um resultadotoolcorrespondente para cadatool_call_idna mensagem do assistente, que é enviada uma vez.
Parte 3: Despachar para a operação de ferramenta
Use uma instrução Case para despachar para a operação de ferramenta correta com base em function_name, seguindo o mesmo padrão de Rotear respostas do LLM para operações do Studio usando function calling. Cada operação de destino executa a função solicitada e define function_resp para a string de resultado.
function_arguments contém as seleções de parâmetros do modelo como uma string JSON. Analise usando JSONParser em cada operação de destino para extrair valores de argumentos individuais.
Parte 4: Anexar o resultado da ferramenta e reconstruir a solicitação
Após a operação de ferramenta definir function_resp, construa o array de mensagens atualizado para a próxima chamada do LLM. Esta etapa requer manipulação JSON complexa: análise das mensagens acumuladas, anexação de novas entradas e resserialização do resultado. Implemente como uma etapa 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 uma etapa de script, defina a linguagem de 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 do OpenAI requer que a seleção tool_calls e o resultado tool correspondente sejam 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 de ferramenta retornar dados estruturados (por exemplo, um objeto JSON), serialize-os para uma string antes de atribuir a function_resp. O campo content da mensagem com função tool deve ser uma string.
Parte 5: Controlar o loop
Encapsule a chamada do LLM, despachador e etapas de reconstrução de solicitação em um controlador Jitterbit Script 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 analisador de resposta da Parte 2 em caso de sucesso. A operação Tool Dispatcher executa a instruçã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ção 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 resolvem em uma ou duas rodadas; atingir consistentemente o limite sinaliza um problema de design de prompt ou resultado de ferramenta, em vez de uma necessidade de limite mais alto.
Alternativa: Construção de mensagens Jitterbit Script
Se preferir evitar JavaScript, a acumulação de mensagens na Parte 4 pode ser implementada inteiramente em Jitterbit Script usando concatenação de strings. O HR Agent implementa essa abordagem, usando gpt.registeredTools como uma variável de projeto para manter os esquemas de ferramentas serializados (equivalente a toolsJson neste guia), com call_llm_again controlando o loop.
A abordagem Jitterbit Script constrói as mensagens do assistente e resultado de ferramentas 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 adicionando 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 exige substituir o 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 na Parte 4 quando possível; reserve a abordagem Jitterbit Script para projetos onde JavaScript não está disponível.
Verificar a integração
-
Implante e execute a operação do controller com uma mensagem do usuário que exija exatamente uma chamada de ferramenta. Confirme nos logs de operação que
loop_countincrementa para 1 e que a operação da ferramenta executou e retornou um resultado. -
Nos logs, confirme que a segunda chamada LLM recebeu o array de mensagens estendido (registre
InAndOutusandoWriteToOperationLogantes de chamar o LLM) e que retornou uma resposta final em texto simples semtool_calls. -
Envie uma mensagem do usuário que exija duas chamadas de ferramenta sequenciais (por exemplo, procure um contato e depois crie um ticket para esse contato). Confirme que
loop_countatinge 2 e inspecione as mensagens acumuladas.tools_messagesé uma string de array JSON serializada, então registre-a após o loop ser concluído 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 adiciona a mensagem do assistente que selecionou a ferramenta seguida pelo seu resultadotool. Otool_call_idde cada mensagemtooldeve corresponder aoidna entradatool_callsda mensagem do assistente anterior. -
Envie uma mensagem que não exija nenhuma chamada de ferramenta (por exemplo, uma pergunta geral que o modelo possa responder com seu próprio conhecimento). Confirme que o loop não é executado (a chamada LLM inicial retorna sem
tool_calls,call_llm_againpermanecefalseeloop_countpermanece 0). -
Se a API retornar um erro 400 citando um
tool_call_idinválido, registre o valor detool_call_ide compare-o ao campoidemchoices[0].message.tool_calls[0]da resposta LLM anterior. Uma incompatibilidade normalmente significa quetools_respfoi atualizado antes da ID ser extraída. -
Se o loop atingir o limite de iteração, registre
InAndOutno 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 à solicitação do usuário.