La llamada asíncrona a herramientas permite que el modelo siga trabajando después de llamar a una herramienta, sin esperar su resultado. Úsala para iniciar con anticipación consultas lentas, responder a partes independientes de una solicitud y entregar los resultados cuando tu aplicación los tenga.
Una llamada a función normal pausa el turno del modelo para esperar la respuesta de una herramienta. Establece async: true en la definición de una herramienta de función o personalizada para que el modelo siga trabajando después de emitir esa llamada, antes de que tu aplicación devuelva el resultado.
Tu aplicación sigue siendo la que ejecuta la herramienta. Las herramientas asíncronas no trasladan la ejecución a
OpenAI ni administran tus trabajos en segundo plano.
Esto difiere del modo en segundo plano, que genera respuestas de forma asíncrona. La llamada asíncrona a herramientas permite que el modelo siga trabajando mientras tu aplicación ejecuta una herramienta.
Cuando un trabajo termine, incluye su resultado en una solicitud posterior a Responses. Usa el call_id original de la API para asociar el resultado con su llamada:
| Tipo de herramienta | Elemento de llamada | Elemento de salida |
|---|
| Función | function_call | function_call_output |
| Personalizada | custom_tool_call | custom_tool_call_output |
Agrega async: true a la definición de la herramienta. Los elementos de llamada correspondientes en response.output incluyen async: true.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79import OpenAI from "openai";
const client = new OpenAI();
const model = "gpt-6-astra";
const tools = [
{
type: "function",
name: "get_weather",
description: "Read a demo weather snapshot for a city.",
async: true,
strict: true,
parameters: {
type: "object",
properties: { city: { type: "string" } },
required: ["city"],
additionalProperties: false,
},
},
];
async function getWeather(city) {
const snapshots = {
Paris: {
city: "Paris",
temperature_c: 22,
condition: "Clear",
source: "demo weather snapshot",
},
};
if (typeof city !== "string" || !Object.hasOwn(snapshots, city)) {
throw new Error(`No demo weather snapshot for ${city}.`);
}
return snapshots[city];
}
const instructions =
"Start the weather lookup and answer the independent packing question " +
"without waiting. Use the demo weather result when it arrives; never invent it.";
let response = await client.responses.create({
model,
tools,
instructions,
input:
"Check the demo weather snapshot for Paris. Meanwhile, " +
"list three essentials for any city trip.",
});
const call = response.output.find((item) => item.type === "function_call");
if (!call || call.name !== "get_weather") {
throw new Error("The response did not include a weather call.");
}
const { city } = JSON.parse(call.arguments);
let latestResponseId = response.id;
// Calling an async function starts the application's job immediately.
const job = getWeather(city).catch((error) => ({ error: error.message }));
if (!call.async) {
// Ordinary synchronous calls must finish before the model resumes.
await job;
}
console.log(response.output);
// Independent work or conversation turns can happen here.
// Update latestResponseId after each continuation.
const result = await job;
response = await client.responses.create({
model,
tools,
instructions,
previous_response_id: latestResponseId,
input: [
{
type: "function_call_output",
call_id: call.call_id,
output: JSON.stringify(result),
},
],
});
latestResponseId = response.id;
console.log(response.output);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93import json
from concurrent.futures import ThreadPoolExecutor
from openai import OpenAI
from openai.types.responses import FunctionToolParam
def get_weather(city):
# Demo data. Replace this function with your weather service.
weather = {
"Paris": {
"city": "Paris",
"temperature_c": 22,
"condition": "Clear",
"source": "demo weather snapshot",
}
}
return weather[city]
worker = ThreadPoolExecutor()
def main():
client = OpenAI()
model = "gpt-6-astra"
tools: list[FunctionToolParam] = [
{
"type": "function",
"name": "get_weather",
"description": "Read the demo weather snapshot for a city.",
"async": True,
"strict": True,
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"],
"additionalProperties": False,
},
},
]
instructions = (
"Start the weather lookup and answer the independent packing "
"question without waiting. Use the actual tool result when it "
"arrives; never invent it. Identify the weather as demo data."
)
response = client.responses.create(
model=model,
tools=tools,
instructions=instructions,
input=(
"Check the demo weather in Paris. Meanwhile, "
"list three essentials for any city trip."
),
)
call = next(item for item in response.output if item.type == "function_call")
arguments = json.loads(call.arguments)
if call.name != "get_weather" or arguments != {"city": "Paris"}:
raise ValueError("Expected a weather lookup for Paris")
latest_response_id = response.id
if call.async_:
job = worker.submit(get_weather, **arguments)
print(response.output_text)
# Independent work or conversation turns can happen here.
# Update latest_response_id after each continuation.
result = job.result()
else:
result = get_weather(**arguments)
response = client.responses.create(
model=model,
tools=tools,
instructions=instructions,
previous_response_id=latest_response_id,
input=[
{
"type": "function_call_output",
"call_id": call.call_id,
"output": json.dumps(result),
},
],
)
print(response.output_text)
if __name__ == "__main__":
try:
main()
finally:
worker.shutdown(wait=True)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100package main
import (
"context"
"encoding/json"
"fmt"
"github.com/openai/openai-go/v3"
"github.com/openai/openai-go/v3/responses"
)
type weatherArguments struct {
City string `json:"city"`
}
type weatherSnapshot struct {
City string `json:"city"`
TemperatureC int `json:"temperature_c"`
Condition string `json:"condition"`
Source string `json:"source"`
}
func getWeather(city string) weatherSnapshot {
// Demo data. Replace this function with your weather service.
if city != "Paris" {
panic("No demo weather snapshot for " + city)
}
return weatherSnapshot{
City: city, TemperatureC: 22, Condition: "Clear", Source: "demo weather snapshot",
}
}
func main() {
client := openai.NewClient()
ctx := context.Background()
tool := responses.ToolParamOfFunction("get_weather", map[string]any{
"type": "object",
"properties": map[string]any{"city": map[string]string{"type": "string"}},
"required": []string{"city"},
"additionalProperties": false,
}, true)
tool.OfFunction.Description = openai.String("Read the demo weather snapshot for a city.")
tool.OfFunction.Async = openai.Bool(true)
tools := []responses.ToolUnionParam{tool}
instructions := "Start the weather lookup and answer the independent packing question " +
"without waiting. Use the actual tool result when it arrives; never invent it. " +
"Identify the weather as demo data."
response, err := client.Responses.New(ctx, responses.ResponseNewParams{
Model: "gpt-6-astra",
Tools: tools,
Instructions: openai.String(instructions),
Input: responses.ResponseNewParamsInputUnion{OfString: openai.String("Check the demo weather in Paris. Meanwhile, list three essentials for any city trip.")},
})
if err != nil {
panic(err)
}
var call responses.ResponseFunctionToolCall
for _, item := range response.Output {
if item.Type == "function_call" && item.AsFunctionCall().Name == "get_weather" {
call = item.AsFunctionCall()
break
}
}
if call.CallID == "" {
panic("The response did not include a weather call.")
}
var arguments weatherArguments
if err := json.Unmarshal([]byte(call.Arguments), &arguments); err != nil {
panic(err)
}
latestResponseID := response.ID
var result weatherSnapshot
if call.Async {
job := make(chan weatherSnapshot, 1)
go func() { job <- getWeather(arguments.City) }()
fmt.Println(response.OutputText())
// Independent work or conversation turns can happen here.
// Update latestResponseID after each continuation.
result = <-job
} else {
result = getWeather(arguments.City)
}
output, err := json.Marshal(result)
if err != nil {
panic(err)
}
functionOutput := responses.ResponseInputItemParamOfFunctionCallOutput(string(output))
functionOutput.OfFunctionCallOutput.CallID = openai.String(call.CallID)
response, err = client.Responses.New(ctx, responses.ResponseNewParams{
Model: "gpt-6-astra",
Tools: tools,
Instructions: openai.String(instructions),
PreviousResponseID: openai.String(latestResponseID),
Input: responses.ResponseNewParamsInputUnion{OfInputItemList: responses.ResponseInputParam{functionOutput}},
})
if err != nil {
panic(err)
}
fmt.Println(response.OutputText())
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.openai.core.JsonValue;
import com.openai.models.responses.FunctionTool;
import com.openai.models.responses.Response;
import com.openai.models.responses.ResponseCreateParams;
import com.openai.models.responses.ResponseFunctionToolCall;
import com.openai.models.responses.ResponseInputItem;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
record WeatherArguments(String city) {}
record WeatherSnapshot(
String city,
@JsonProperty("temperature_c") int temperatureC,
String condition,
String source) {}
static WeatherSnapshot getWeather(String city) {
// Demo data. Replace this function with your weather service.
if (!city.equals("Paris")) {
throw new IllegalArgumentException("No demo weather snapshot for " + city);
}
return new WeatherSnapshot(city, 22, "Clear", "demo weather snapshot");
}
FunctionTool tool =
FunctionTool.builder()
.name("get_weather")
.description("Read the demo weather snapshot for a city.")
.async(true)
.strict(true)
.parameters(
FunctionTool.Parameters.builder()
.putAdditionalProperty("type", JsonValue.from("object"))
.putAdditionalProperty(
"properties", JsonValue.from(Map.of("city", Map.of("type", "string"))))
.putAdditionalProperty("required", JsonValue.from(List.of("city")))
.putAdditionalProperty("additionalProperties", JsonValue.from(false))
.build())
.build();
String instructions =
"Start the weather lookup and answer the independent packing question without waiting. Use the actual tool result when it arrives; never invent it. Identify the weather as demo data.";
Response response =
client
.responses()
.create(
ResponseCreateParams.builder()
.model("gpt-6-astra")
.addTool(tool)
.instructions(instructions)
.input(
"Check the demo weather in Paris. Meanwhile, list three essentials for any city trip.")
.build());
ResponseFunctionToolCall call =
response.output().stream()
.flatMap(item -> item.functionCall().stream())
.filter(item -> item.name().equals("get_weather"))
.findFirst()
.orElseThrow(
() -> new IllegalStateException("The response did not include a weather call."));
WeatherArguments arguments = call.arguments(WeatherArguments.class);
String latestResponseId = response.id();
WeatherSnapshot result;
if (call.async().orElse(false)) {
CompletableFuture<WeatherSnapshot> job =
CompletableFuture.supplyAsync(() -> getWeather(arguments.city()));
System.out.println(response.output());
// Independent work or conversation turns can happen here.
// Update latestResponseId after each continuation.
result = job.join();
} else {
result = getWeather(arguments.city());
}
response =
client
.responses()
.create(
ResponseCreateParams.builder()
.model("gpt-6-astra")
.addTool(tool)
.instructions(instructions)
.previousResponseId(latestResponseId)
.inputOfResponse(
List.of(
ResponseInputItem.ofFunctionCallOutput(
ResponseInputItem.FunctionCallOutput.builder()
.callId(call.callId())
.output(new ObjectMapper().writeValueAsString(result))
.build())))
.build());
response.output().stream()
.flatMap(item -> item.message().stream())
.flatMap(message -> message.content().stream())
.flatMap(content -> content.outputText().stream())
.forEach(text -> System.out.println(text.text()));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70require "json"
require "openai"
def get_weather(city)
# Demo data. Replace this function with your weather service.
raise "No demo weather snapshot for #{city}" unless city == "Paris"
{
city: city,
temperature_c: 22,
condition: "Clear",
source: "demo weather snapshot"
}
end
client = OpenAI::Client.new
tools = [
OpenAI::Models::Responses::FunctionTool.new(
name: "get_weather",
description: "Read the demo weather snapshot for a city.",
async: true,
strict: true,
parameters: {
type: "object",
properties: { city: { type: "string" } },
required: ["city"],
additionalProperties: false
}
)
]
instructions = "Start the weather lookup and answer the independent packing question " \
"without waiting. Use the actual tool result when it arrives; never invent it. " \
"Identify the weather as demo data."
response = client.responses.create(
model: "gpt-6-astra",
tools: tools,
instructions: instructions,
input: "Check the demo weather in Paris. Meanwhile, list three essentials for any city trip."
)
call = response.output.find do |item|
item.is_a?(OpenAI::Models::Responses::ResponseFunctionToolCall) && item.name == "get_weather"
end
unless call.is_a?(OpenAI::Models::Responses::ResponseFunctionToolCall)
raise "The response did not include a weather call."
end
city = JSON.parse(call.arguments).fetch("city")
latest_response_id = response.id
result = if call.async
job = Thread.new { get_weather(city) }
puts(response.output_text)
# Independent work or conversation turns can happen here.
# Update latest_response_id after each continuation.
job.value
else
get_weather(city)
end
response = client.responses.create(
model: "gpt-6-astra",
tools: tools,
instructions: instructions,
previous_response_id: latest_response_id,
input: [
OpenAI::Models::Responses::ResponseInputItem::FunctionCallOutput.new(
call_id: call.call_id,
output: JSON.generate(result)
)
]
)
puts(response.output_text)
La respuesta puede contener tanto la llamada asíncrona como una contestación. Si hay otros turnos de conversación antes de que termine el trabajo, actualiza latest_response_id para continuar desde la respuesta más reciente y conserva el call_id original de la herramienta.
Para iniciar la ejecución antes con streaming, inicia el trabajo en cuanto llegue su elemento de llamada completo mientras sigues consumiendo la respuesta.
Una herramienta de espera permite que el modelo decida cuándo necesita un resultado pendiente. Por ejemplo, puede iniciar dos consultas de precios, trabajar en algo independiente y esperar solo cuando esté listo para comparar los precios.
Agrega un argumento task_handle a cada herramienta asíncrona. El modelo asigna un identificador a cada llamada y tu aplicación lo vincula con el call_id original de la API y el trabajo en ejecución. Mantén los identificadores únicos a lo largo de toda la conversación, incluidas las tareas completadas y las consultas repetidas.
Define la herramienta de espera como una función síncrona común: omite async o establécelo en false. Tu aplicación define su esquema y su comportamiento. wait_for_tasks no es una herramienta integrada de Responses.
Usa estas definiciones en el arreglo tools de la solicitud:
1234567891011121314151617181920212223242526272829303132333435[
{
"type": "function",
"name": "lookup_price",
"async": true,
"description": "Look up a product price in the background. Choose a fresh task_handle unique within this conversation, including completed tasks.",
"strict": true,
"parameters": {
"type": "object",
"properties": {
"sku": { "type": "string" },
"task_handle": { "type": "string" }
},
"required": ["sku", "task_handle"],
"additionalProperties": false
}
},
{
"type": "function",
"name": "wait_for_tasks",
"description": "Wait for selected tasks whose results you need. Pass a nonempty list of distinct task_handles from your earlier lookup_price calls. Results arrive on their original calls; this tool returns status only. Do not wait again for results that have already arrived.",
"strict": true,
"parameters": {
"type": "object",
"properties": {
"task_handles": {
"type": "array",
"items": { "type": "string" }
}
},
"required": ["task_handles"],
"additionalProperties": false
}
}
]
Registra e inicia cada trabajo antes de procesar una espera que dependa de él. Las llamadas pueden llegar juntas o en distintas respuestas. Los siguientes elementos de salida de ejemplo muestran dos inicios de trabajos y una espera que depende de ambos:
12345678910111213141516171819202122[
{
"type": "function_call",
"name": "lookup_price",
"async": true,
"call_id": "call_widget",
"arguments": "{\"sku\":\"WIDGET\",\"task_handle\":\"widget_price_1\"}"
},
{
"type": "function_call",
"name": "lookup_price",
"async": true,
"call_id": "call_gadget",
"arguments": "{\"sku\":\"GADGET\",\"task_handle\":\"gadget_price_1\"}"
},
{
"type": "function_call",
"name": "wait_for_tasks",
"call_id": "call_wait",
"arguments": "{\"task_handles\":[\"widget_price_1\",\"gadget_price_1\"]}"
}
]
El registro de tu aplicación vincula cada identificador con su llamada original y su trabajo en ejecución:
| Identificador de tarea | ID de la llamada original | Trabajo |
|---|
widget_price_1 | call_widget | Consulta del precio de WIDGET |
gadget_price_1 | call_gadget | Consulta del precio de GADGET |
Conserva el registro durante toda la conversación para evitar que se reutilice el identificador de una tarea completada.
Busca los identificadores solicitados en el registro y espera únicamente a que terminen esos trabajos. Devuelve cada resultado recién completado con su call_id original y luego devuelve el estado con el call_id de la propia llamada de espera. Este orden permite que el modelo tenga los resultados cuando reanude su trabajo.
Por ejemplo, envía estos elementos de salida en el arreglo input de la siguiente solicitud. Los precios son ilustrativos:
1234567891011121314151617[
{
"type": "function_call_output",
"call_id": "call_widget",
"output": "{\"task_handle\":\"widget_price_1\",\"price_cents\":1200,\"currency\":\"USD\"}"
},
{
"type": "function_call_output",
"call_id": "call_gadget",
"output": "{\"task_handle\":\"gadget_price_1\",\"price_cents\":1500,\"currency\":\"USD\"}"
},
{
"type": "function_call_output",
"call_id": "call_wait",
"output": "{\"status\":\"completed\",\"completed_task_handles\":[\"widget_price_1\",\"gadget_price_1\"]}"
}
]
Establece previous_response_id en el ID de la respuesta más reciente e incluye las herramientas y las instrucciones en la solicitud de continuación. Tu aplicación también puede entregar resultados a medida que estén disponibles, sin una llamada de espera. Usa la herramienta de espera solo cuando el siguiente paso del modelo dependa de resultados que aún no hayan llegado.
GPT-6 Astra y los modelos posteriores admiten la llamada asíncrona a herramientas.
La ejecución asíncrona se aplica a las herramientas de función y personalizadas que ejecuta tu aplicación. No se aplica a las herramientas integradas alojadas. Usa llamadas directas a herramientas; no configures herramientas asíncronas para la llamada programática a herramientas.
En el modo multiagente, no combines herramientas asíncronas con llamadas paralelas a herramientas.