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:
- Declarar suas funções no array
tools - Detectar quando o assistant quer chamar uma função (status
requires_actionou eventofunction_call_arguments.done) - Executar a função na sua aplicação
- 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_iddefinido como o ID da respostarequires_actioninputcontendo um itemfunction_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¶
- Tools — todos os tipos de tools e compatibilidade por provedor
- Streaming — eventos de function call no stream SSE
- Continuidade de Conversas — conversas com múltiplos turnos