Ir para o conteúdo

Function Calling

O function calling permite que o assistant invoque tools customizadas que rodam na sua aplicação. Você define quais funções estão disponíveis, o assistant decide quando chamá-las, e você executa e retorna os resultados.


Visão Geral

O fluxo tem quatro etapas:

  1. Declarar suas funções no array tools
  2. Detectar quando o assistant quer chamar uma função (status requires_action ou evento function_call_arguments.done)
  3. Executar a função na sua aplicação
  4. Retornar o resultado em uma requisição de acompanhamento
Sua Aplicação                     Plataforma Nodexa
    │                                    │
    │─── POST /v1/responses ────────────>│
    │    (model, input, tools)           │
    │                                    │ (processa, decide chamar tool)
    │<── response (requires_action) ─────│
    │    function_call: {                │
    │      name: "get_weather",          │
    │      call_id: "call_abc",          │
    │      arguments: '{"city":"Paris"}' │
    │    }                               │
    │                                    │
    │─── [executa get_weather localmente]│
    │                                    │
    │─── POST /v1/responses ────────────>│
    │    (previous_response_id,          │
    │     function_call_output)          │
    │                                    │
    │<── response (completed) ───────────│
    │    "The weather in Paris is 18°C"  │

Passo 1 — Declarar suas Funções

Inclua as definições de função no array tools:

{
  "model": "YOUR_ASSISTANT_ID",
  "input": "What's the weather in Paris right now?",
  "tools": [
    {
      "type": "function",
      "name": "get_weather",
      "description": "Get the current weather conditions for a city.",
      "parameters": {
        "type": "object",
        "properties": {
          "city": {
            "type": "string",
            "description": "The name of the city, e.g. 'Paris' or 'New York'"
          },
          "unit": {
            "type": "string",
            "enum": ["celsius", "fahrenheit"],
            "description": "Temperature unit. Defaults to celsius."
          }
        },
        "required": ["city"]
      }
    }
  ]
}

Restrições do nome da função:

  • Apenas caracteres alfanuméricos, underscores (_) e hífens (-)
  • Máximo de 64 caracteres
  • Deve ser único dentro da request (máximo de 128 tools no total)

Passo 2 — Detectar a Chamada de Tool

Sem streaming

Quando o assistant quer chamar uma função, a resposta tem status: "requires_action" e o array output contém um item function_call:

{
  "id": "resp_01234567-89ab-cdef-0123-456789abcdef",
  "status": "requires_action",
  "output": [
    {
      "type": "function_call",
      "name": "get_weather",
      "call_id": "call_abc123",
      "arguments": "{\"city\": \"Paris\", \"unit\": \"celsius\"}"
    }
  ]
}

Com streaming

Com streaming, escute response.function_call_arguments.done:

for await (const event of stream) {
  if (event.type === 'response.function_call_arguments.done') {
    console.log('Function to call:', event.name);
    console.log('Call ID:', event.call_id);
    console.log('Arguments:', event.arguments); // string JSON
  }
}

Você também pode ver eventos response.function_call_arguments.delta enquanto os argumentos estão sendo montados — você pode usá-los para mostrar um indicador de "processando".


Passo 3 — Executar a Função

Faça o parse da string arguments e chame sua função:

const toolCall = response.output.find(item => item.type === 'function_call');
const args = JSON.parse(toolCall.arguments);

let result;
switch (toolCall.name) {
  case 'get_weather':
    result = await getWeather(args.city, args.unit ?? 'celsius');
    break;
  default:
    result = { error: `Unknown function: ${toolCall.name}` };
}

Sempre trate funções desconhecidas

O assistant deve chamar apenas as funções que você declarou, mas tratar nomes desconhecidos de forma defensiva previne crashes e permite retornar um erro significativo ao assistant.


Passo 4 — Retornar o Resultado

Envie uma request de acompanhamento com:

  • previous_response_id definido como o ID da resposta requires_action
  • input contendo um item function_call_output
{
  "model": "YOUR_ASSISTANT_ID",
  "previous_response_id": "resp_01234567-89ab-cdef-0123-456789abcdef",
  "input": [
    {
      "type": "function_call_output",
      "call_id": "call_abc123",
      "output": "{\"temperature\": 18, \"unit\": \"celsius\", \"conditions\": \"partly cloudy\"}"
    }
  ]
}
Campo Tipo Descrição
type "function_call_output" Identifica isso como um resultado de tool
call_id string Deve corresponder ao call_id do item de function call
output string O valor de retorno da função — deve ser uma string JSON

O output deve ser uma string JSON

O campo output é uma string, não um objeto. Serialize seu resultado com JSON.stringify() antes de enviar. O assistant vai fazer parse do JSON para entender o resultado.


Exemplo Completo

import OpenAI from 'openai';

const client = new OpenAI({
  baseURL: 'https://your-admin.example.com/v1',
  apiKey: process.env.NODEXA_API_KEY,
});

// Função de clima simulada
async function getWeather(city, unit = 'celsius') {
  // Em uma aplicação real, chame uma API de clima aqui
  return {
    city,
    temperature: 18,
    unit,
    conditions: 'partly cloudy',
    humidity: 65,
  };
}

async function chatWithTools(userMessage) {
  // Passo 1: Requisição inicial com declarações de tool
  let response = await client.responses.create({
    model: process.env.NODEXA_ASSISTANT_ID,
    input: userMessage,
    tools: [
      {
        type: 'function',
        name: 'get_weather',
        description: 'Get current weather for a city',
        parameters: {
          type: 'object',
          properties: {
            city: { type: 'string', description: 'City name' },
            unit: {
              type: 'string',
              enum: ['celsius', 'fahrenheit'],
              description: 'Temperature unit',
            },
          },
          required: ['city'],
        },
      },
    ],
  });

  // Passo 2: Trate chamadas de tool em um loop (o modelo pode chamar tools várias vezes)
  while (response.status === 'requires_action') {
    const toolResults = [];

    for (const item of response.output) {
      if (item.type !== 'function_call') continue;

      const args = JSON.parse(item.arguments);
      console.log(`Calling ${item.name}(${JSON.stringify(args)})`);

      let result;
      if (item.name === 'get_weather') {
        result = await getWeather(args.city, args.unit);
      } else {
        result = { error: `Unknown function: ${item.name}` };
      }

      toolResults.push({
        type: 'function_call_output',
        call_id: item.call_id,
        output: JSON.stringify(result),
      });
    }

    // Passo 3: Enviar resultados de volta
    response = await client.responses.create({
      model: process.env.NODEXA_ASSISTANT_ID,
      previous_response_id: response.id,
      input: toolResults,
    });
  }

  return response.output_text;
}

const answer = await chatWithTools('What is the weather in Paris right now?');
console.log(answer);
// => "The current weather in Paris is 18°C, partly cloudy with 65% humidity."
import OpenAI from 'openai';

const client = new OpenAI({
  baseURL: 'https://your-admin.example.com/v1',
  apiKey: process.env.NODEXA_API_KEY,
});

async function getWeather(city, unit = 'celsius') {
  return { city, temperature: 18, unit, conditions: 'partly cloudy' };
}

async function chatWithToolsStreaming(userMessage) {
  const toolDefs = [
    {
      type: 'function',
      name: 'get_weather',
      description: 'Get current weather for a city',
      parameters: {
        type: 'object',
        properties: {
          city: { type: 'string' },
          unit: { type: 'string', enum: ['celsius', 'fahrenheit'] },
        },
        required: ['city'],
      },
    },
  ];

  let stream = await client.responses.create({
    model: process.env.NODEXA_ASSISTANT_ID,
    input: userMessage,
    tools: toolDefs,
    stream: true,
  });

  let responseId = null;
  let responseStatus = null;
  const pendingToolCalls = [];

  for await (const event of stream) {
    switch (event.type) {
      case 'response.output_text.delta':
        process.stdout.write(event.delta);
        break;

      case 'response.function_call_arguments.done':
        pendingToolCalls.push({
          name: event.name,
          call_id: event.call_id,
          arguments: event.arguments,
        });
        break;

      case 'response.completed':
        responseId = event.response.id;
        responseStatus = event.response.status;
        break;
    }
  }

  // Tratar requires_action
  if (responseStatus === 'requires_action' && pendingToolCalls.length > 0) {
    const toolResults = await Promise.all(
      pendingToolCalls.map(async (call) => {
        const args = JSON.parse(call.arguments);
        let result;

        if (call.name === 'get_weather') {
          result = await getWeather(args.city, args.unit);
        } else {
          result = { error: `Unknown function: ${call.name}` };
        }

        return {
          type: 'function_call_output',
          call_id: call.call_id,
          output: JSON.stringify(result),
        };
      })
    );

    // Continuar no mesmo thread com os resultados
    const finalStream = await client.responses.create({
      model: process.env.NODEXA_ASSISTANT_ID,
      previous_response_id: responseId,
      input: toolResults,
      stream: true,
    });

    for await (const event of finalStream) {
      if (event.type === 'response.output_text.delta') {
        process.stdout.write(event.delta);
      }
    }
    console.log();
  }
}

await chatWithToolsStreaming('What is the weather in Paris?');
import os
import json
from openai import OpenAI

client = OpenAI(
    base_url="https://your-admin.example.com/v1",
    api_key=os.environ['NODEXA_API_KEY'],
)

def get_weather(city: str, unit: str = "celsius") -> dict:
    # Em produção, chame uma API de clima real
    return {
        "city": city,
        "temperature": 18,
        "unit": unit,
        "conditions": "partly cloudy",
    }

tool_defs = [
    {
        "type": "function",
        "name": "get_weather",
        "description": "Get current weather for a city",
        "parameters": {
            "type": "object",
            "properties": {
                "city": {"type": "string", "description": "City name"},
                "unit": {
                    "type": "string",
                    "enum": ["celsius", "fahrenheit"],
                },
            },
            "required": ["city"],
        },
    }
]

def dispatch_tool(name: str, arguments: str) -> str:
    args = json.loads(arguments)
    if name == "get_weather":
        result = get_weather(args["city"], args.get("unit", "celsius"))
    else:
        result = {"error": f"Unknown function: {name}"}
    return json.dumps(result)

response = client.responses.create(
    model=os.environ['NODEXA_ASSISTANT_ID'],
    input="What is the weather in Paris?",
    tools=tool_defs,
)

while response.status == "requires_action":
    tool_results = []
    for item in response.output:
        if item.type == "function_call":
            output = dispatch_tool(item.name, item.arguments)
            tool_results.append({
                "type": "function_call_output",
                "call_id": item.call_id,
                "output": output,
            })

    response = client.responses.create(
        model=os.environ['NODEXA_ASSISTANT_ID'],
        previous_response_id=response.id,
        input=tool_results,
    )

print(response.output_text)
# Passo 1: Requisição inicial
RESPONSE=$(curl -s "https://your-admin.example.com/v1/responses" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "YOUR_ASSISTANT_ID",
    "input": "What is the weather in Paris?",
    "tools": [
      {
        "type": "function",
        "name": "get_weather",
        "description": "Get current weather for a city",
        "parameters": {
          "type": "object",
          "properties": {
            "city": { "type": "string" },
            "unit": { "type": "string", "enum": ["celsius", "fahrenheit"] }
          },
          "required": ["city"]
        }
      }
    ]
  }')

echo "Status: $(echo $RESPONSE | jq -r '.status')"

# Se requires_action, extrair os detalhes da chamada
RESP_ID=$(echo $RESPONSE | jq -r '.id')
CALL_ID=$(echo $RESPONSE | jq -r '.output[0].call_id')
ARGUMENTS=$(echo $RESPONSE | jq -r '.output[0].arguments')
CITY=$(echo $ARGUMENTS | jq -r '.city')

echo "Calling get_weather for: $CITY"

# Passo 2: Executar função (simulada) e retornar resultado
WEATHER_RESULT='{"temperature":18,"unit":"celsius","conditions":"partly cloudy"}'

curl "https://your-admin.example.com/v1/responses" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{
    \"model\": \"YOUR_ASSISTANT_ID\",
    \"previous_response_id\": \"$RESP_ID\",
    \"input\": [
      {
        \"type\": \"function_call_output\",
        \"call_id\": \"$CALL_ID\",
        \"output\": \"$WEATHER_RESULT\"
      }
    ]
  }"

Múltiplas Chamadas de Tool em Uma Resposta

O assistant pode chamar múltiplas funções em uma única resposta. Processe todas antes de enviar a request de acompanhamento:

const toolResults = [];

for (const item of response.output) {
  if (item.type !== 'function_call') continue;

  const args = JSON.parse(item.arguments);
  const result = await dispatchTool(item.name, args);

  toolResults.push({
    type: 'function_call_output',
    call_id: item.call_id,
    output: JSON.stringify(result),
  });
}

// Envie TODOS os resultados em uma requisição
const nextResponse = await client.responses.create({
  model: assistantId,
  previous_response_id: response.id,
  input: toolResults,
});

Envie todos os resultados em uma request

Não envie resultados de funções individuais em requests separadas. Envie todos os itens function_call_output de uma determinada resposta em uma única request de acompanhamento.


Tratamento de Erros

Se sua função falhar, retorne um objeto de erro como output — não deixe a chamada sem resposta:

try {
  result = await getWeather(args.city);
} catch (err) {
  result = {
    error: true,
    message: err.message,
  };
}

toolResults.push({
  type: 'function_call_output',
  call_id: item.call_id,
  output: JSON.stringify(result), // sempre responda
});

O assistant verá o erro e poderá responder adequadamente (por exemplo, "Não consegui obter as informações do clima devido a um erro de serviço. Por favor, tente novamente mais tarde.").


Páginas Relacionadas