La herramienta de shell permite que los modelos trabajen en un entorno de terminal completo. A través de la API de Responses, puedes ejecutar comandos de shell tanto de forma local como en la nube.
Con la herramienta de shell, los modelos pueden ejecutar comandos en cualquiera de estos entornos:
La herramienta de shell está disponible a través de la API de Responses. No está disponible a través de la API para completar chats.
Ejecutar comandos de shell arbitrarios puede ser peligroso. Ejecuta siempre los comandos en un sandbox,
permite o bloquea comandos específicos cuando sea posible y registra la actividad de la herramienta
para poder auditarla.
La terminal alojada en la nube ofrece una opción nativa y fácil de usar para tareas que requieren un procesamiento más avanzado y determinista, desde hacer cálculos hasta trabajar con contenido multimedia.
Usa container_auto cuando quieras que OpenAI aprovisione y administre un contenedor para la solicitud.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19curl -L 'https://api.openai.com/v1/responses' \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-d '{
"model": "gpt-6-astra",
"tools": [
{ "type": "shell", "environment": { "type": "container_auto" } }
],
"input": [
{
"type": "message",
"role": "user",
"content": [
{ "type": "input_text", "text": "Execute: ls -lah /mnt/data && python --version && node --version" }
]
}
],
"tool_choice": "auto"
}'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23import OpenAI from "openai";
const client = new OpenAI();
const response = await client.responses.create({
model: "gpt-6-astra",
tools: [{ type: "shell", environment: { type: "container_auto" } }],
input: [
{
type: "message",
role: "user",
content: [
{
type: "input_text",
text: "Execute: ls -lah /mnt/data && python --version && node --version",
},
],
},
],
tool_choice: "auto",
});
console.log(response.output_text);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23from openai import OpenAI
client = OpenAI()
response = client.responses.create(
model="gpt-6-astra",
tools=[{"type": "shell", "environment": {"type": "container_auto"}}],
input=[
{
"type": "message",
"role": "user",
"content": [
{
"type": "input_text",
"text": "Execute: ls -lah /mnt/data && python --version && node --version",
}
],
}
],
tool_choice="auto",
)
print(response.output_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
25package main
import (
"context"
"fmt"
"github.com/openai/openai-go/v3"
"github.com/openai/openai-go/v3/responses"
)
func main() {
client := openai.NewClient()
tool := responses.ToolUnionParam{OfShell: &responses.FunctionShellToolParam{
Environment: responses.FunctionShellToolEnvironmentUnionParam{OfContainerAuto: &responses.ContainerAutoParam{}},
}}
response, err := client.Responses.New(context.Background(), responses.ResponseNewParams{
Model: "gpt-6-astra",
Tools: []responses.ToolUnionParam{tool},
Input: responses.ResponseNewParamsInputUnion{OfString: openai.String("Execute: ls -lah /mnt/data && python --version && node --version")},
})
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
19import com.openai.client.OpenAIClient;
import com.openai.client.okhttp.OpenAIOkHttpClient;
import com.openai.models.responses.ContainerAuto;
import com.openai.models.responses.FunctionShellTool;
import com.openai.models.responses.ResponseCreateParams;
ResponseCreateParams params =
ResponseCreateParams.builder()
.model("gpt-6-astra")
.input("Run ls -lah /mnt/data, then show the Python and Node.js versions.")
.addTool(
FunctionShellTool.builder().environment(ContainerAuto.builder().build()).build())
.build();
client.responses().create(params).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
15require "openai"
client = OpenAI::Client.new
response = client.responses.create(
model: "gpt-6-astra",
input: "Run ls -lah /mnt/data, then show the Python and Node.js versions.",
tools: [
{
type: :shell,
environment: { type: :container_auto }
}
]
)
puts(response.output_text)
- El entorno de ejecución se basa actualmente en
Debian 12 y puede cambiar con el tiempo.
- El directorio de trabajo predeterminado es
/mnt/data.
/mnt/data siempre está disponible y es la ruta admitida para los artefactos que el usuario puede descargar.
- La terminal alojada en la nube no admite sesiones TTY interactivas.
- Los comandos de la terminal alojada en la nube se ejecutan sin
sudo.
- Puedes ejecutar servicios dentro del contenedor cuando tu flujo de trabajo los necesite.
Estos son algunos de los lenguajes que vienen preinstalados actualmente:
- Python
3.11
- Node.js
22.16
- Java
17.0
- PHP
8.2
- Ruby
3.1
- Go
1.23
Si necesitas un entorno de larga duración para flujos de trabajo iterativos, crea un contenedor y haz referencia a él en las llamadas posteriores a la API de Responses.
1
2
3
4
5
6
7
8curl -L 'https://api.openai.com/v1/containers' \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-d '{
"name": "analysis-container",
"memory_limit": "1g",
"expires_after": { "anchor": "last_active_at", "minutes": 20 }
}'
1
2
3
4
5
6
7
8
9
10
11import OpenAI from "openai";
const client = new OpenAI();
const container = await client.containers.create({
name: "analysis-container",
memory_limit: "1g",
expires_after: { anchor: "last_active_at", minutes: 20 },
});
console.log(container.id);
1
2
3
4
5
6
7
8
9
10
11from openai import OpenAI
client = OpenAI()
container = client.containers.create(
name="analysis-container",
memory_limit="1g",
expires_after={"anchor": "last_active_at", "minutes": 20},
)
print(container.id)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24package main
import (
"context"
"fmt"
"github.com/openai/openai-go/v3"
)
func main() {
client := openai.NewClient()
container, err := client.Containers.New(context.Background(), openai.ContainerNewParams{
Name: "analysis-container",
MemoryLimit: openai.ContainerNewParamsMemoryLimit1g,
ExpiresAfter: openai.ContainerNewParamsExpiresAfter{
Anchor: "last_active_at",
Minutes: 20,
},
})
if err != nil {
panic(err)
}
fmt.Println(container.ID)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18import com.openai.client.OpenAIClient;
import com.openai.client.okhttp.OpenAIOkHttpClient;
import com.openai.models.containers.ContainerCreateParams;
var container =
client
.containers()
.create(
ContainerCreateParams.builder()
.name("analysis")
.expiresAfter(
ContainerCreateParams.ExpiresAfter.builder()
.anchor(ContainerCreateParams.ExpiresAfter.Anchor.LAST_ACTIVE_AT)
.minutes(20)
.build())
.build());
System.out.println(container.id());
1
2
3
4
5
6
7
8
9
10require "openai"
client = OpenAI::Client.new
container = client.containers.create(
name: "analysis", expires_after: {
anchor: :last_active_at,
minutes: 20
}
)
puts(container.id)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16curl -L 'https://api.openai.com/v1/responses' \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-d '{
"model": "gpt-6-astra",
"tools": [
{
"type": "shell",
"environment": {
"type": "container_reference",
"container_id": "cntr_08f3d96c87a585390069118b594f7481a088b16cda7d9415fe"
}
}
],
"input": "List files in the container and show disk usage."
}'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19import OpenAI from "openai";
const client = new OpenAI();
const response = await client.responses.create({
model: "gpt-6-astra",
tools: [
{
type: "shell",
environment: {
type: "container_reference",
container_id: "cntr_08f3d96c87a585390069118b594f7481a088b16cda7d9415fe",
},
},
],
input: "List files in the container and show disk usage.",
});
console.log(response.output_text);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15response = client.responses.create(
model="gpt-6-astra",
tools=[
{
"type": "shell",
"environment": {
"type": "container_reference",
"container_id": container.id,
},
}
],
input="List files in the container and show disk usage.",
)
print(response.output_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
25package main
import (
"context"
"fmt"
"github.com/openai/openai-go/v3"
"github.com/openai/openai-go/v3/responses"
)
func main() {
client := openai.NewClient()
tool := responses.ToolUnionParam{OfShell: &responses.FunctionShellToolParam{
Environment: responses.FunctionShellToolEnvironmentUnionParam{OfContainerReference: &responses.ContainerReferenceParam{ContainerID: "cntr_08f3d96c87a585390069118b594f7481a088b16cda7d9415fe"}},
}}
response, err := client.Responses.New(context.Background(), responses.ResponseNewParams{
Model: "gpt-6-astra",
Tools: []responses.ToolUnionParam{tool},
Input: responses.ResponseNewParamsInputUnion{OfString: openai.String("List files in the container and show disk usage.")},
})
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
19import com.openai.client.OpenAIClient;
import com.openai.client.okhttp.OpenAIOkHttpClient;
import com.openai.models.responses.FunctionShellTool;
import com.openai.models.responses.ResponseCreateParams;
String containerId = "cntr_08f3d96c87a585390069118b594f7481a088b16cda7d9415fe";
ResponseCreateParams params =
ResponseCreateParams.builder()
.model("gpt-6-astra")
.input("List files in the container and show disk usage.")
.addTool(FunctionShellTool.builder().containerReferenceEnvironment(containerId).build())
.build();
client.responses().create(params).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
18require "openai"
client = OpenAI::Client.new
response = client.responses.create(
model: "gpt-6-astra",
input: "List files in the container and show disk usage.",
tools: [
{
type: :shell,
environment: {
type: :container_reference,
container_id: "cntr_08f3d96c87a585390069118b594f7481a088b16cda7d9415fe"
}
}
]
)
puts(response.output_text)
Las habilidades son paquetes reutilizables con control de versiones que puedes montar en entornos de la terminal alojada en la nube. Al montarlas, defines qué habilidades están disponibles. Cuando se ejecuta el shell, el modelo decide si las invoca.
Consulta la guía de habilidades para saber cómo subirlas y gestionar sus versiones.
1
2
3
4
5
6
7
8
9
10curl -L 'https://api.openai.com/v1/containers' \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-d '{
"name": "skill-container",
"skills": [
{ "type": "skill_reference", "skill_id": "skill_4db6f1a2c9e73508b41f9da06e2c7b5f" },
{ "type": "skill_reference", "skill_id": "openai-spreadsheets", "version": "latest" }
]
}'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20import OpenAI from "openai";
const client = new OpenAI();
const container = await client.containers.create({
name: "skill-container",
skills: [
{
type: "skill_reference",
skill_id: "skill_4db6f1a2c9e73508b41f9da06e2c7b5f",
},
{
type: "skill_reference",
skill_id: "openai-spreadsheets",
version: "latest",
},
],
});
console.log(container.id);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22# Replace the illustrative IDs and URLs below with your own resource values.
from openai import OpenAI
client = OpenAI()
skill_id = "skill_123"
container = client.containers.create(
name="skill-container",
skills=[
{
"type": "skill_reference",
"skill_id": skill_id,
},
{
"type": "skill_reference",
"skill_id": "openai-spreadsheets",
"version": "latest",
},
],
)
print(container.id)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24package main
import (
"context"
"fmt"
"github.com/openai/openai-go/v3"
"github.com/openai/openai-go/v3/responses"
)
func main() {
client := openai.NewClient()
container, err := client.Containers.New(context.Background(), openai.ContainerNewParams{
Name: "skill-container",
Skills: []openai.ContainerNewParamsSkillUnion{
{OfSkillReference: &responses.SkillReferenceParam{SkillID: "skill_4db6f1a2c9e73508b41f9da06e2c7b5f"}},
{OfSkillReference: &responses.SkillReferenceParam{SkillID: "openai-spreadsheets", Version: openai.String("latest")}},
},
})
if err != nil {
panic(err)
}
fmt.Println(container.ID)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22import com.openai.client.OpenAIClient;
import com.openai.client.okhttp.OpenAIOkHttpClient;
import com.openai.models.containers.ContainerCreateParams;
import com.openai.models.responses.SkillReference;
String skillId = "skill_4db6f1a2c9e73508b41f9da06e2c7b5f";
var container =
client
.containers()
.create(
ContainerCreateParams.builder()
.name("skill-container")
.addSkill(SkillReference.builder().skillId(skillId).build())
.addSkill(
SkillReference.builder()
.skillId("openai-spreadsheets")
.version("latest")
.build())
.build());
System.out.println(container.id());
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19require "openai"
client = OpenAI::Client.new
container = client.containers.create(
name: "skill-container",
skills: [
{
type: :skill_reference,
skill_id: "skill_4db6f1a2c9e73508b41f9da06e2c7b5f"
},
{
type: :skill_reference,
skill_id: "openai-spreadsheets",
version: "latest"
}
]
)
puts(container.id)
De forma predeterminada, los contenedores alojados en la nube no tienen acceso saliente a la red.
Para habilitarlo:
- Un administrador debe configurar la lista de dominios permitidos de tu organización en el panel.
- En tu solicitud, debes establecer explícitamente
network_policy en el entorno del contenedor.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25curl -L 'https://api.openai.com/v1/responses' \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-6-astra",
"tool_choice": "required",
"tools": [
{
"type": "shell",
"environment": {
"type": "container_auto",
"network_policy": {
"type": "allowlist",
"allowed_domains": ["pypi.org", "files.pythonhosted.org", "github.com"]
}
}
}
],
"input": [
{
"role": "user",
"content": "In the container, pip install httpx beautifulsoup4, fetch release pages, and write /mnt/data/release_digest.md."
}
]
}'
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
29import OpenAI from "openai";
const client = new OpenAI();
const response = await client.responses.create({
model: "gpt-6-astra",
tool_choice: "required",
tools: [
{
type: "shell",
environment: {
type: "container_auto",
network_policy: {
type: "allowlist",
allowed_domains: ["pypi.org", "files.pythonhosted.org", "github.com"],
},
},
},
],
input: [
{
role: "user",
content:
"In the container, pip install httpx beautifulsoup4, fetch release pages, and write /mnt/data/release_digest.md.",
},
],
});
console.log(response.output_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
32from openai import OpenAI
client = OpenAI()
response = client.responses.create(
model="gpt-6-astra",
tool_choice="required",
tools=[
{
"type": "shell",
"environment": {
"type": "container_auto",
"network_policy": {
"type": "allowlist",
"allowed_domains": [
"pypi.org",
"files.pythonhosted.org",
"github.com",
],
},
},
}
],
input=[
{
"role": "user",
"content": "In the container, pip install httpx beautifulsoup4, fetch release pages, and write /mnt/data/release_digest.md.",
}
],
)
print(response.output_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
30package main
import (
"context"
"fmt"
"github.com/openai/openai-go/v3"
"github.com/openai/openai-go/v3/responses"
)
func main() {
client := openai.NewClient()
tool := responses.ToolUnionParam{OfShell: &responses.FunctionShellToolParam{
Environment: responses.FunctionShellToolEnvironmentUnionParam{OfContainerAuto: &responses.ContainerAutoParam{
NetworkPolicy: responses.ContainerAutoNetworkPolicyUnionParam{OfAllowlist: &responses.ContainerNetworkPolicyAllowlistParam{
AllowedDomains: []string{"pypi.org", "files.pythonhosted.org", "github.com"},
}},
}},
}}
response, err := client.Responses.New(context.Background(), responses.ResponseNewParams{
Model: "gpt-6-astra",
ToolChoice: responses.ResponseNewParamsToolChoiceUnion{OfToolChoiceMode: openai.Opt(responses.ToolChoiceOptionsRequired)},
Tools: []responses.ToolUnionParam{tool},
Input: responses.ResponseNewParamsInputUnion{OfString: openai.String("In the container, pip install httpx beautifulsoup4, fetch release pages, and write /mnt/data/release_digest.md.")},
})
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
32import com.openai.client.OpenAIClient;
import com.openai.client.okhttp.OpenAIOkHttpClient;
import com.openai.models.responses.ContainerAuto;
import com.openai.models.responses.ContainerNetworkPolicyAllowlist;
import com.openai.models.responses.FunctionShellTool;
import com.openai.models.responses.ResponseCreateParams;
import com.openai.models.responses.ToolChoiceOptions;
ResponseCreateParams params =
ResponseCreateParams.builder()
.model("gpt-6-astra")
.input("Fetch release pages and write /mnt/data/release_digest.md.")
.toolChoice(ToolChoiceOptions.REQUIRED)
.addTool(
FunctionShellTool.builder()
.environment(
ContainerAuto.builder()
.networkPolicy(
ContainerNetworkPolicyAllowlist.builder()
.addAllowedDomain("pypi.org")
.addAllowedDomain("files.pythonhosted.org")
.addAllowedDomain("github.com")
.build())
.build())
.build())
.build();
client.responses().create(params).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
22require "openai"
client = OpenAI::Client.new
response = client.responses.create(
model: "gpt-6-astra",
input: "Fetch release pages and write /mnt/data/release_digest.md.",
tool_choice: :required,
tools: [
{
type: :shell,
environment: {
type: :container_auto,
network_policy: {
type: :allowlist,
allowed_domains: ["pypi.org", "files.pythonhosted.org", "github.com"]
}
}
}
]
)
puts(response.output_text)
Permitir el acceso a determinados dominios conlleva riesgos de seguridad, como la exfiltración de datos mediante la inyección de prompts.
Permite únicamente dominios de confianza que los atacantes no puedan usar para recibir datos robados.
Antes de usar esta herramienta, revisa detenidamente la sección Riesgos
y seguridad que aparece más adelante.
Cuando se aplican varios controles:
- La lista de dominios permitidos de tu organización define el conjunto completo de
allowed_domains.
- La política
network_policy de cada solicitud restringe aún más el acceso.
- Las solicitudes fallan si
allowed_domains incluye dominios que no figuran en la lista de tu organización.
La terminal alojada en la nube y el intérprete de código usan contenedores alojados en la nube. Mientras están activos, estos contenedores pueden guardar el estado temporal de la aplicación en su sistema de archivos, que utiliza almacenamiento efímero en bloques. Los datos del contenedor se eliminan cuando el contenedor caduca o cuando lo eliminas explícitamente.
Para obtener más detalles sobre los controles de datos, consulta ZDR y residencia de datos.
La terminal alojada en la nube puede generar archivos descargables. Usa las mismas API de contenedores y archivos que usa el intérprete de código para recuperar los artefactos guardados en /mnt/data.
Si quieres que el contenido y los archivos sean efímeros durante el ciclo de vida del contenedor alojado en la nube, puedes incluir los archivos directamente en la solicitud y montar en el contenedor las habilidades incluidas de la misma forma.
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
55INLINE_ZIP=$(base64 -i ./csv_insights.zip)
REPORT_CSV=$(base64 -i ./report.csv)
CONTAINER_ID=$(
curl -sL 'https://api.openai.com/v1/containers' \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-d '{
"name": "inline-skill-container",
"skills": [
{
"type": "inline",
"name": "csv-insights",
"description": "Summarize CSV files and produce a markdown report.",
"source": {
"type": "base64",
"media_type": "application/zip",
"data": "'"$INLINE_ZIP"'"
}
}
]
}' | jq -r '.id'
)
curl -L 'https://api.openai.com/v1/responses' \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-d '{
"model": "gpt-6-astra",
"tools": [
{
"type": "shell",
"environment": {
"type": "container_reference",
"container_id": "'"$CONTAINER_ID"'"
}
}
],
"input": [
{
"role": "user",
"content": [
{
"type": "input_file",
"filename": "report.csv",
"file_data": "data:text/csv;base64,'"${REPORT_CSV}"'"
},
{
"type": "input_text",
"text": "Use the csv-insights skill to summarize report.csv."
}
]
}
]
}'
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
56import fs from "fs";
import OpenAI from "openai";
const client = new OpenAI();
const inlineZip = fs
.readFileSync("fixtures/csv_insights.zip")
.toString("base64");
const reportCsv = fs.readFileSync("fixtures/report.csv").toString("base64");
const container = await client.containers.create({
name: "inline-skill-container",
skills: [
{
type: "inline",
name: "csv-insights",
description: "Summarize CSV files and produce a markdown report.",
source: {
type: "base64",
media_type: "application/zip",
data: inlineZip,
},
},
],
});
const response = await client.responses.create({
model: "gpt-6-astra",
tools: [
{
type: "shell",
environment: {
type: "container_reference",
container_id: container.id,
},
},
],
input: [
{
role: "user",
content: [
{
type: "input_file",
filename: "report.csv",
file_data: `data:text/csv;base64,${reportCsv}`,
},
{
type: "input_text",
text: "Use the csv-insights skill to summarize report.csv.",
},
],
},
],
});
console.log(response.output_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
57import base64
from openai import OpenAI
client = OpenAI()
with open("csv_insights.zip", "rb") as f:
inline_zip = base64.b64encode(f.read()).decode("utf-8")
with open("report.csv", "rb") as f:
base64_string = base64.b64encode(f.read()).decode("utf-8")
container = client.containers.create(
name="inline-skill-container",
skills=[
{
"type": "inline",
"name": "csv-insights",
"description": "Summarize CSV files and produce a markdown report.",
"source": {
"type": "base64",
"media_type": "application/zip",
"data": inline_zip,
},
}
],
)
response = client.responses.create(
model="gpt-6-astra",
tools=[
{
"type": "shell",
"environment": {
"type": "container_reference",
"container_id": container.id,
},
}
],
input=[
{
"role": "user",
"content": [
{
"type": "input_file",
"filename": "report.csv",
"file_data": f"data:text/csv;base64,{base64_string}",
},
{
"type": "input_text",
"text": "Use the csv-insights skill to summarize report.csv.",
},
],
}
],
)
print(response.output_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
70
71
72
73
74
75
76
77var bundle bytes.Buffer
archive := zip.NewWriter(&bundle)
file, err := archive.Create("csv-insights/SKILL.md")
if err != nil {
log.Fatal(err)
}
if _, err := file.Write([]byte("---\nname: csv-insights\ndescription: Summarize CSV files.\n---\nRead the CSV and produce a Markdown report of totals.\n")); err != nil {
log.Fatal(err)
}
if err := archive.Close(); err != nil {
log.Fatal(err)
}
client := openai.NewClient()
inlineZip := base64.StdEncoding.EncodeToString(bundle.Bytes())
reportCSV := base64.StdEncoding.EncodeToString([]byte("product,revenue\nA,120\nB,80\n"))
container, err := client.Containers.New(context.Background(), openai.ContainerNewParams{
Name: "inline-skill-container",
Skills: []openai.ContainerNewParamsSkillUnion{
{
OfInline: &responses.InlineSkillParam{
Name: "csv-insights",
Description: "Summarize CSV files.",
Source: responses.InlineSkillSourceParam{
Data: inlineZip,
},
},
},
},
})
if err != nil {
log.Fatal(err)
}
response, err := client.Responses.New(context.Background(), responses.ResponseNewParams{
Model: "gpt-6-astra",
Tools: []responses.ToolUnionParam{
{
OfShell: &responses.FunctionShellToolParam{
Environment: responses.FunctionShellToolEnvironmentUnionParam{
OfContainerReference: &responses.ContainerReferenceParam{
ContainerID: container.ID,
},
},
},
},
},
Input: responses.ResponseNewParamsInputUnion{
OfInputItemList: []responses.ResponseInputItemUnionParam{
{
OfMessage: &responses.EasyInputMessageParam{
Role: responses.EasyInputMessageRoleUser,
Content: responses.EasyInputMessageContentUnionParam{
OfInputItemContentList: []responses.ResponseInputContentUnionParam{
{
OfInputFile: &responses.ResponseInputFileParam{
Filename: openai.String("report.csv"),
FileData: openai.String("data:text/csv;base64," + reportCSV),
},
},
{
OfInputText: &responses.ResponseInputTextParam{
Text: "Use the csv-insights skill to summarize report.csv.",
},
},
},
},
},
},
},
},
})
if err != nil {
log.Fatal(err)
}
if response.Status != responses.ResponseStatusCompleted {
log.Fatalf("Response ended with status %s", response.Status)
}
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
65import com.openai.client.okhttp.OpenAIOkHttpClient;
import com.openai.models.containers.ContainerCreateParams;
import com.openai.models.responses.*;
import java.util.*;
var bundle = new java.io.ByteArrayOutputStream();
try (var zip = new java.util.zip.ZipOutputStream(bundle)) {
zip.putNextEntry(new java.util.zip.ZipEntry("csv-insights/SKILL.md"));
zip.write(
"---\nname: csv-insights\ndescription: Summarize CSV files.\n---\nRead the CSV and produce a Markdown report of totals.\n"
.getBytes(java.nio.charset.StandardCharsets.UTF_8));
zip.closeEntry();
}
var client = OpenAIOkHttpClient.fromEnv();
var inlineZip = Base64.getEncoder().encodeToString(bundle.toByteArray());
var reportCsv =
Base64.getEncoder()
.encodeToString(
"product,revenue\nA,120\nB,80\n".getBytes(java.nio.charset.StandardCharsets.UTF_8));
var container =
client
.containers()
.create(
ContainerCreateParams.builder()
.name("inline-skill-container")
.addSkill(
InlineSkill.builder()
.name("csv-insights")
.description("Summarize CSV files.")
.source(InlineSkillSource.builder().data(inlineZip).build())
.build())
.build());
var response =
client
.responses()
.create(
ResponseCreateParams.builder()
.model("gpt-6-astra")
.addTool(
FunctionShellTool.builder()
.containerReferenceEnvironment(container.id())
.build())
.inputOfResponse(
List.of(
ResponseInputItem.ofMessage(
ResponseInputItem.Message.builder()
.role(ResponseInputItem.Message.Role.USER)
.addContent(
ResponseInputFile.builder()
.filename("report.csv")
.fileData("data:text/csv;base64," + reportCsv)
.build())
.addInputTextContent(
"Use the csv-insights skill to summarize report.csv.")
.build())))
.build());
if (response.status().filter(ResponseStatus.COMPLETED::equals).isEmpty())
throw new IllegalStateException(
"Response ended with status " + response.status().orElse(null));
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
50require "base64"
require "openai"
client = OpenAI::Client.new
inline_zip = Base64.strict_encode64(File.binread("csv_insights.zip"))
base64_string = Base64.strict_encode64(File.binread("report.csv"))
container = client.containers.create(
name: "inline-skill-container",
skills: [
{
type: :inline,
name: "csv-insights",
description: "Summarize CSV files and produce a markdown report.",
source: {
type: :base64,
media_type: "application/zip",
data: inline_zip
}
}
]
)
response = client.responses.create(
model: "gpt-6-astra",
tools: [
{
type: :shell,
environment: {
type: :container_reference,
container_id: container.id
}
}
],
input: [
{
role: :user,
content: [
{
type: :input_file,
filename: "report.csv",
file_data: "data:text/csv;base64,#{base64_string}"
},
{
type: :input_text,
text: "Use the csv-insights skill to summarize report.csv."
}
]
}
]
)
puts(response.output_text)
Para las solicitudes posteriores, pasa el mismo container_id con container_reference. Las habilidades montadas y los archivos que ya están en el contenedor siguen disponibles mientras el contenedor está activo.
Puedes eliminar el contenedor explícitamente cuando termines el trabajo, sin esperar a que caduque por inactividad.
curl -L -X DELETE 'https://api.openai.com/v1/containers/container_id' \
-H "Authorization: Bearer $OPENAI_API_KEY"
import OpenAI from "openai";
const client = new OpenAI();
await client.containers.delete("container_id");
console.log("Container deleted");
# Replace the illustrative IDs and URLs below with your own resource values.
from openai import OpenAI
client = OpenAI()
container_id = "cntr_123"
client.containers.delete(container_id)
print("Container deleted")
package main
import (
"context"
"fmt"
"github.com/openai/openai-go/v3"
)
func main() {
client := openai.NewClient()
if err := client.Containers.Delete(context.Background(), "container_id"); err != nil {
panic(err)
}
fmt.Println("Container deleted")
}
import com.openai.client.OpenAIClient;
import com.openai.client.okhttp.OpenAIOkHttpClient;
String containerId = "container_id";
client.containers().delete(containerId);
System.out.println("Container deleted.");
require "openai"
client = OpenAI::Client.new
client.containers.delete("container_id")
puts("Container deleted")
Usa domain_secrets cuando un dominio de tu lista allowed_domains requiera encabezados de autorización privados, como Authorization: Bearer <token>.
Cada entrada de secreto incluye:
- El dominio de destino
- Un nombre descriptivo para el secreto
- El valor del secreto
Durante la ejecución:
- El modelo y el entorno de ejecución ven marcadores de posición (por ejemplo,
$API_KEY) en lugar de las credenciales reales.
- El contenedor auxiliar que sustituye los marcadores de autenticación usa los valores reales de los secretos solo para los destinos aprobados.
- Los valores reales de los secretos no se conservan en los servidores de la API ni aparecen en el contexto que ve el modelo.
Así, el asistente puede llamar a servicios protegidos con un menor riesgo de filtración de credenciales.
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
32curl -L 'https://api.openai.com/v1/responses' \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-6-astra",
"input": [
{
"role": "user",
"content": "Use curl to call https://httpbin.org/headers with header Authorization: Bearer $API_KEY. Tell me what you see in the final text response."
}
],
"tool_choice": "required",
"tools": [
{
"type": "shell",
"environment": {
"type": "container_auto",
"network_policy": {
"type": "allowlist",
"allowed_domains": ["httpbin.org"],
"domain_secrets": [
{
"domain": "httpbin.org",
"name": "API_KEY",
"value": "debug-secret-123"
}
]
}
}
}
]
}'
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
36import OpenAI from "openai";
const client = new OpenAI();
const response = await client.responses.create({
model: "gpt-6-astra",
input: [
{
role: "user",
content:
"Use curl to call https://httpbin.org/headers with header Authorization: Bearer $API_KEY. Tell me what you see in the final text response.",
},
],
tool_choice: "required",
tools: [
{
type: "shell",
environment: {
type: "container_auto",
network_policy: {
type: "allowlist",
allowed_domains: ["httpbin.org"],
domain_secrets: [
{
domain: "httpbin.org",
name: "API_KEY",
value: "debug-secret-123",
},
],
},
},
},
],
});
console.log(response.output_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
35from openai import OpenAI
client = OpenAI()
response = client.responses.create(
model="gpt-6-astra",
input=[
{
"role": "user",
"content": "Use curl to call https://httpbin.org/headers with header Authorization: Bearer $API_KEY. Tell me what you see in the final text response.",
}
],
tool_choice="required",
tools=[
{
"type": "shell",
"environment": {
"type": "container_auto",
"network_policy": {
"type": "allowlist",
"allowed_domains": ["httpbin.org"],
"domain_secrets": [
{
"domain": "httpbin.org",
"name": "API_KEY",
"value": "debug-secret-123",
}
],
},
},
}
],
)
print(response.output_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
35package main
import (
"context"
"fmt"
"github.com/openai/openai-go/v3"
"github.com/openai/openai-go/v3/responses"
)
func main() {
client := openai.NewClient()
tool := responses.ToolUnionParam{OfShell: &responses.FunctionShellToolParam{
Environment: responses.FunctionShellToolEnvironmentUnionParam{OfContainerAuto: &responses.ContainerAutoParam{
NetworkPolicy: responses.ContainerAutoNetworkPolicyUnionParam{OfAllowlist: &responses.ContainerNetworkPolicyAllowlistParam{
AllowedDomains: []string{"httpbin.org"},
DomainSecrets: []responses.ContainerNetworkPolicyDomainSecretParam{{
Domain: "httpbin.org",
Name: "API_KEY",
Value: "debug-secret-123",
}},
}},
}},
}}
response, err := client.Responses.New(context.Background(), responses.ResponseNewParams{
Model: "gpt-6-astra",
ToolChoice: responses.ResponseNewParamsToolChoiceUnion{OfToolChoiceMode: openai.Opt(responses.ToolChoiceOptionsRequired)},
Tools: []responses.ToolUnionParam{tool},
Input: responses.ResponseNewParamsInputUnion{OfString: openai.String("Use curl to call https://httpbin.org/headers with header Authorization: Bearer $API_KEY. Tell me what you see in the final text response.")},
})
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
42import com.openai.client.OpenAIClient;
import com.openai.client.okhttp.OpenAIOkHttpClient;
import com.openai.models.responses.ContainerAuto;
import com.openai.models.responses.ContainerNetworkPolicyAllowlist;
import com.openai.models.responses.ContainerNetworkPolicyDomainSecret;
import com.openai.models.responses.FunctionShellTool;
import com.openai.models.responses.ResponseCreateParams;
import com.openai.models.responses.ToolChoiceOptions;
ResponseCreateParams params =
ResponseCreateParams.builder()
.model("gpt-6-astra")
.input(
"Run exactly this one shell command, without additional commands:\n"
+ "curl -q -sS -o /dev/null -w '%{http_code}' "
+ "-H 'Authorization: Bearer $API_KEY' https://httpbin.org/status/204\n"
+ "Keep the header single-quoted so $API_KEY remains literal. "
+ "Return only the HTTP status code. Never print request headers or secret values.")
.toolChoice(ToolChoiceOptions.REQUIRED)
.addTool(
FunctionShellTool.builder()
.environment(
ContainerAuto.builder()
.networkPolicy(
ContainerNetworkPolicyAllowlist.builder()
.addAllowedDomain("httpbin.org")
.addDomainSecret(
ContainerNetworkPolicyDomainSecret.builder()
.domain("httpbin.org")
.name("API_KEY")
.value(System.getenv("OPENAI_EXAMPLE_DOMAIN_SECRET"))
.build())
.build())
.build())
.build())
.build();
client.responses().create(params).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
30require "openai"
client = OpenAI::Client.new
response = client.responses.create(
model: "gpt-6-astra",
input: "Use curl to call https://httpbin.org/headers with an " \
'"Authorization: Bearer $API_KEY" header.',
tool_choice: :required,
tools: [
{
type: :shell,
environment: {
type: :container_auto,
network_policy: {
type: :allowlist,
allowed_domains: ["httpbin.org"],
domain_secrets: [
{
domain: "httpbin.org",
name: "API_KEY",
value: "debug-secret-123"
}
]
}
}
}
]
)
puts(response.output_text)
Para seguir trabajando en el mismo entorno alojado en la nube, reutiliza el contenedor y pasa previous_response_id.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17curl -L 'https://api.openai.com/v1/responses' \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-d '{
"model": "gpt-6-astra",
"previous_response_id": "resp_2a8e5c9174d63b0f18a4c572de9f64a1b3c76d508e12f9ab47",
"tools": [
{
"type": "shell",
"environment": {
"type": "container_reference",
"container_id": "cntr_f19c2b51e4a06793d82d54a7be0fc9154d3361ab28ce7f6041"
}
}
],
"input": "Read /mnt/data/top5.csv and report the top candidate."
}'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21import OpenAI from "openai";
const client = new OpenAI();
const response = await client.responses.create({
model: "gpt-6-astra",
previous_response_id:
"resp_2a8e5c9174d63b0f18a4c572de9f64a1b3c76d508e12f9ab47",
tools: [
{
type: "shell",
environment: {
type: "container_reference",
container_id: "cntr_f19c2b51e4a06793d82d54a7be0fc9154d3361ab28ce7f6041",
},
},
],
input: "Read /mnt/data/top5.csv and report the top candidate.",
});
console.log(response.output_text);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20from openai import OpenAI
client = OpenAI()
response = client.responses.create(
model="gpt-6-astra",
previous_response_id="resp_2a8e5c9174d63b0f18a4c572de9f64a1b3c76d508e12f9ab47",
tools=[
{
"type": "shell",
"environment": {
"type": "container_reference",
"container_id": "cntr_f19c2b51e4a06793d82d54a7be0fc9154d3361ab28ce7f6041",
},
}
],
input="Read /mnt/data/top5.csv and report the top candidate.",
)
print(response.output_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
26package main
import (
"context"
"fmt"
"github.com/openai/openai-go/v3"
"github.com/openai/openai-go/v3/responses"
)
func main() {
client := openai.NewClient()
tool := responses.ToolUnionParam{OfShell: &responses.FunctionShellToolParam{
Environment: responses.FunctionShellToolEnvironmentUnionParam{OfContainerReference: &responses.ContainerReferenceParam{ContainerID: "cntr_f19c2b51e4a06793d82d54a7be0fc9154d3361ab28ce7f6041"}},
}}
response, err := client.Responses.New(context.Background(), responses.ResponseNewParams{
Model: "gpt-6-astra",
PreviousResponseID: openai.String("resp_2a8e5c9174d63b0f18a4c572de9f64a1b3c76d508e12f9ab47"),
Tools: []responses.ToolUnionParam{tool},
Input: responses.ResponseNewParamsInputUnion{OfString: openai.String("Read /mnt/data/top5.csv and report the top candidate.")},
})
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
22import com.openai.client.OpenAIClient;
import com.openai.client.okhttp.OpenAIOkHttpClient;
import com.openai.models.responses.FunctionShellTool;
import com.openai.models.responses.ResponseCreateParams;
String responseId = "resp_2a8e5c9174d63b0f18a4c572de9f64a1b3c76d508e12f9ab47";
String containerId = "cntr_f19c2b51e4a06793d82d54a7be0fc9154d3361ab28ce7f6041";
ResponseCreateParams params =
ResponseCreateParams.builder()
.model("gpt-6-astra")
.input("Read /mnt/data/top5.csv and report the top candidate.")
.previousResponseId(responseId)
.addTool(FunctionShellTool.builder().containerReferenceEnvironment(containerId).build())
.build();
client.responses().create(params).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
19require "openai"
client = OpenAI::Client.new
response = client.responses.create(
model: "gpt-6-astra",
input: "Read /mnt/data/top5.csv and report the top candidate.",
previous_response_id: "resp_2a8e5c9174d63b0f18a4c572de9f64a1b3c76d508e12f9ab47",
tools: [
{
type: :shell,
environment: {
type: :container_reference,
container_id: "cntr_f19c2b51e4a06793d82d54a7be0fc9154d3361ab28ce7f6041"
}
}
]
)
puts(response.output_text)
La terminal alojada en la nube y el shell local usan los mismos tipos de elementos de salida. Cada ejecución de shell se representa con un par de elementos de salida:
shell_call: comandos que solicita el modelo.
shell_call_output: salida de los comandos y resultados de su ejecución.
1
2
3
4
5
6
7
8
9
10{
"type": "shell_call",
"call_id": "call_9d14ac6f2b73485e91c0f4da6e1b27c8",
"action": {
"commands": ["ls -l"],
"timeout_ms": 120000,
"max_output_length": 4096
},
"status": "in_progress"
}
También puedes ejecutar comandos de shell en tu propio entorno de ejecución local. Para ello, ejecuta las acciones shell_call y devuelve shell_call_output al modelo.
Usa este modo cuando necesites controlar el sistema de archivos o acceder a herramientas internas. Establece el tipo de herramienta en shell y el tipo de entorno en local. Si estás migrando desde la herramienta local_shell, que ya se retiró, actualiza tu solicitud y procesa shell_call y shell_call_output en tu bucle de ejecución.
1
2
3
4
5
6
7
8
9curl -L 'https://api.openai.com/v1/responses' \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-d '{
"model": "gpt-6-astra",
"instructions": "The local bash shell environment is on Mac.",
"input": "find me the largest pdf file in ~/Documents",
"tools": [{ "type": "shell", "environment": { "type": "local" } }]
}'
1
2
3
4
5
6
7
8
9
10
11
12import OpenAI from "openai";
const client = new OpenAI();
const response = await client.responses.create({
model: "gpt-6-astra",
instructions: "The local bash shell environment is on Mac.",
input: "find me the largest pdf file in ~/Documents",
tools: [{ type: "shell", environment: { type: "local" } }],
});
console.log(response);
1
2
3
4
5
6
7
8
9
10
11
12from openai import OpenAI
client = OpenAI()
response = client.responses.create(
model="gpt-6-astra",
instructions="The local bash shell environment is on Mac.",
input="find me the largest pdf file in ~/Documents",
tools=[{"type": "shell", "environment": {"type": "local"}}],
)
print(response)
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
26package main
import (
"context"
"fmt"
"github.com/openai/openai-go/v3"
"github.com/openai/openai-go/v3/responses"
)
func main() {
client := openai.NewClient()
tool := responses.ToolUnionParam{OfShell: &responses.FunctionShellToolParam{
Environment: responses.FunctionShellToolEnvironmentUnionParam{OfLocal: &responses.LocalEnvironmentParam{}},
}}
response, err := client.Responses.New(context.Background(), responses.ResponseNewParams{
Model: "gpt-6-astra",
Instructions: openai.String("The local bash shell environment is on Mac."),
Input: responses.ResponseNewParamsInputUnion{OfString: openai.String("find me the largest pdf file in ~/Documents")},
Tools: []responses.ToolUnionParam{tool},
})
if err != nil {
panic(err)
}
fmt.Println(response.Output)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22import com.openai.client.OpenAIClient;
import com.openai.client.okhttp.OpenAIOkHttpClient;
import com.openai.core.JsonValue;
import com.openai.models.responses.ResponseCreateParams;
import java.util.List;
import java.util.Map;
ResponseCreateParams params =
ResponseCreateParams.builder()
.model("gpt-6-astra")
.input("Find the largest PDF in ~/Documents.")
.instructions("The local shell environment is macOS.")
.putAdditionalBodyProperty(
"tools",
JsonValue.from(
List.of(Map.of("type", "shell", "environment", Map.of("type", "local")))))
.build();
client.responses().create(params).output().stream()
.flatMap(item -> item.shellCall().stream())
.flatMap(call -> call.action().commands().stream())
.forEach(System.out::println);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16require "openai"
client = OpenAI::Client.new
response = client.responses.create(
model: "gpt-6-astra",
instructions: "The local shell environment is macOS.",
input: "Find the largest PDF in ~/Documents.",
tools: [
{
type: :shell,
environment: { type: :local }
}
]
)
puts(response.output)
Cuando recibas elementos de salida shell_call:
- Revisa cada comando antes de ejecutarlo. Usa una lista de comandos permitidos y exige aprobación para los comandos destructivos o que accedan a datos sensibles.
- Ejecuta los comandos aprobados en un sandbox que limite explícitamente el acceso al sistema de archivos y a la red, el uso de memoria y el tiempo de ejecución. Aplica tu propio límite de tiempo aunque el modelo sugiera uno.
- Registra los comandos y sus resultados para poder auditarlos.
- Captura
stdout, stderr y el resultado de la ejecución.
- Devuelve los resultados como
shell_call_output en la siguiente solicitud.
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
64import { spawn } from "node:child_process";
class ShellExecutor {
constructor(defaultTimeoutMs = 60_000) {
this.defaultTimeoutMs = defaultTimeoutMs;
}
async run(cmd, timeoutMs) {
return new Promise((resolve, reject) => {
const child = spawn("sh", ["-c", cmd], {
detached: true,
stdio: ["ignore", "pipe", "pipe"],
});
let stdout = "";
let stderr = "";
let timedOut = false;
let outputLimited = false;
const stop = () => {
try {
process.kill(-child.pid, "SIGKILL");
} catch (error) {
if (error.code !== "ESRCH") reject(error);
}
};
const timer = setTimeout(() => {
timedOut = true;
stop();
}, timeoutMs ?? this.defaultTimeoutMs);
const collect = (stream, append) => {
let bytes = 0;
stream.setEncoding("utf8");
stream.on("data", (chunk) => {
bytes += Buffer.byteLength(chunk);
if (bytes > 1024 * 1024) {
outputLimited = true;
clearTimeout(timer);
stop();
} else {
append(chunk);
}
});
};
collect(child.stdout, (chunk) => {
stdout += chunk;
});
collect(child.stderr, (chunk) => {
stderr += chunk;
});
child.on("error", (error) => {
clearTimeout(timer);
reject(error);
});
child.on("close", (code) => {
clearTimeout(timer);
resolve({
stdout,
stderr,
exitCode: outputLimited ? 1 : code,
timedOut,
});
});
});
}
}
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
38from contextlib import suppress
from dataclasses import dataclass
import os
import signal
import subprocess
@dataclass
class CmdResult:
stdout: str
stderr: str
exit_code: int | None
timed_out: bool
class ShellExecutor:
def __init__(self, default_timeout: float = 60):
self.default_timeout = default_timeout
def run(self, cmd: str, timeout: float | None = None) -> CmdResult:
t = self.default_timeout if timeout is None else timeout
p = subprocess.Popen(
cmd,
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
start_new_session=True,
)
try:
out, err = p.communicate(timeout=t)
return CmdResult(out, err, p.returncode, False)
except subprocess.TimeoutExpired:
# Stop the shell and its children before draining their output pipes.
with suppress(ProcessLookupError):
os.killpg(p.pid, signal.SIGKILL)
out, err = p.communicate()
return CmdResult(out, err, p.returncode, 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
58import (
"bytes"
"os/exec"
"syscall"
"time"
)
type shellResult struct {
Stdout string `json:"stdout"`
Stderr string `json:"stderr"`
ExitCode int `json:"exit_code"`
TimedOut bool `json:"timed_out"`
}
type shellExecutor struct {
DefaultTimeout time.Duration
}
func (e shellExecutor) run(command string, timeout time.Duration) shellResult {
if timeout == 0 {
timeout = e.DefaultTimeout
}
cmd := exec.Command("sh", "-c", command)
// Own a process group so cancellation also stops the shell's children.
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
if err := cmd.Start(); err != nil {
return shellResult{Stderr: err.Error(), ExitCode: -1}
}
// Keep the deadline active until output pipes close, even if the shell
// exits before a background child does.
expired := make(chan struct{})
timer := time.AfterFunc(timeout, func() {
_ = syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)
close(expired)
})
err := cmd.Wait()
result := shellResult{Stdout: stdout.String(), Stderr: stderr.String()}
if !timer.Stop() {
<-expired
result.TimedOut = true
result.ExitCode = -1
return result
}
if err != nil {
if exitError, ok := err.(*exec.ExitError); ok {
result.ExitCode = exitError.ExitCode()
return result
}
if result.Stderr == "" {
result.Stderr = err.Error()
}
result.ExitCode = -1
}
return result
}
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
42require "open3"
class ShellExecutor
Result = Data.define(:stdout, :stderr, :exit_code, :timed_out)
def initialize(default_timeout: 60)
@default_timeout = default_timeout
end
def run(command, timeout: @default_timeout)
Open3.popen3("sh", "-c", command, pgroup: true) do |stdin, stdout, stderr, wait_thread|
stdin.close
stdout_reader = Thread.new { stdout.read }
stderr_reader = Thread.new { stderr.read }
deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + timeout
finished = [wait_thread, stdout_reader, stderr_reader].all? do |thread|
remaining = [deadline - Process.clock_gettime(Process::CLOCK_MONOTONIC), 0].max
thread.join(remaining)
end
terminate_process_group(wait_thread) unless finished
Result.new(
stdout: stdout_reader.value,
stderr: stderr_reader.value,
exit_code: wait_thread.value.exitstatus || -1,
timed_out: !finished
)
end
end
private
def terminate_process_group(wait_thread)
Process.kill("TERM", -wait_thread.pid)
wait_thread.join(1)
Process.kill("KILL", -wait_thread.pid)
rescue Errno::ESRCH
nil
ensure
wait_thread.join
end
end
Estos ejemplos requieren Linux o macOS y detienen el grupo de procesos del comando cuando se agota el tiempo asignado al comando.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22{
"type": "shell_call_output",
"call_id": "call_3ef1b8c79a4d6520f9e3ab7d41c68f25",
"max_output_length": 4096,
"output": [
{
"stdout": "...",
"stderr": "...",
"outcome": {
"type": "exit",
"exit_code": 0
}
},
{
"stdout": "...",
"stderr": "...",
"outcome": {
"type": "timeout"
}
}
]
}
Si un comando falla o se agota su tiempo de ejecución, devuelve el resultado para que el modelo pueda decidir si vuelve a intentarlo. Consulta el aviso de retiro de la herramienta anterior.
Si usas el SDK de agentes, puedes pasar tu propia implementación del ejecutor de shell a la función auxiliar de la herramienta de shell.
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
42import { Agent, run, withTrace, shellTool } from "@openai/agents";
class LocalShell {
async run(action) {
return {
output: [
{
stdout: "Shell is not available. Needs to be implemented first.",
stderr: "",
outcome: {
type: "exit",
exitCode: 1,
},
},
],
maxOutputLength: action.maxOutputLength,
};
}
}
const shell = new LocalShell();
const agent = new Agent({
name: "Shell Assistant",
model: "gpt-6-astra",
instructions:
"You can execute shell commands to inspect the repository. Keep responses concise and include command output when helpful.",
tools: [
shellTool({
shell,
needsApproval: true,
onApproval: async (_ctx, _approvalItem) => {
return { approve: true };
},
}),
],
});
await withTrace("shell-tool-example", async () => {
const result = await run(agent, "Show the Node.js version.");
console.log(`\nFinal response:\n${result.finalOutput}`);
});
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
50from agents import (
Agent,
Runner,
ShellCallOutcome,
ShellCommandOutput,
ShellCommandRequest,
ShellResult,
ShellTool,
)
class LocalShell:
async def __call__(self, request: ShellCommandRequest) -> ShellResult:
action = request.data.action
return ShellResult(
output=[
ShellCommandOutput(
command="(not executed)",
stdout="Shell is not available. Needs to be implemented first.",
stderr="",
outcome=ShellCallOutcome(type="exit", exit_code=1),
)
],
max_output_length=action.max_output_length,
)
shell_tool = ShellTool(
executor=LocalShell(),
needs_approval=True,
on_approval=lambda _ctx, _approval_item: {"approve": True},
)
agent = Agent(
name="Shell Assistant",
model="gpt-6-astra",
instructions="You can execute shell commands to inspect the repository. Keep responses concise and include command output when helpful.",
tools=[shell_tool],
)
async def main():
result = await Runner.run(agent, input="Show the Node.js version.")
print(f"\nFinal response:\n{result.final_output}")
if __name__ == "__main__":
import asyncio
asyncio.run(main())
En los repositorios del SDK encontrarás ejemplos que puedes ejecutar.
Ejemplo de la herramienta de shell en TypeScript
Ejemplo en TypeScript de cómo usar la herramienta de shell en el SDK de agentes.
Ejemplo de la herramienta de shell en Python
Ejemplo en Python de cómo usar la herramienta de shell en el SDK de agentes.
- Si un comando supera el tiempo límite de ejecución, devuelve un resultado que indique que se agotó el tiempo e incluye la salida parcial capturada.
- Si
max_output_length está presente en shell_call, inclúyelo en shell_call_output.
- Evita depender de comandos interactivos. La herramienta de shell debe ejecutarse sin interacción.
- Conserva la salida de los comandos que terminan con un código de salida distinto de cero para que el modelo pueda determinar cómo recuperarse.
Habilitar el acceso a la red en la API de contenedores amplía considerablemente sus capacidades y conlleva riesgos importantes para la seguridad y la gobernanza de datos. El acceso a la red está deshabilitado de forma predeterminada. Si lo habilitas, limita estrictamente el acceso saliente a los dominios de confianza que requiera la tarea.
Los contenedores con acceso a la red pueden interactuar con servicios de terceros y registros de paquetes. Esto conlleva riesgos como la filtración de datos, el uso indebido de herramientas mediante la inyección de prompts y el acceso accidental a recursos fuera del alcance previsto. Estos riesgos aumentan cuando las políticas son amplias, son estáticas o se aplican de manera inconsistente.
Cualquier contenido externo que se obtenga a través de la red puede incluir instrucciones ocultas destinadas a manipular el comportamiento del modelo. Trata el contenido de red que no sea de confianza como potencialmente malicioso y extrema las precauciones ante acciones que puedan modificar datos o sistemas.
Permite únicamente dominios de confianza que mantengas activamente. Ten cuidado con los intermediarios y agregadores que reenvían solicitudes a otros servicios. Antes de agregarlos a tu lista de dominios permitidos, revisa cómo manejan y retienen los datos.
Revisa el comando de la herramienta de shell y la salida de su ejecución. Encontrarás ambos en la respuesta de la API de Responses. Registra en cada sesión los hosts solicitados y los destinos reales de las conexiones salientes. Revisa los registros periódicamente para verificar que los patrones de acceso coincidan con lo previsto, detectar desviaciones e identificar comportamientos sospechosos.
Los controles de datos de OpenAI se aplican dentro de la infraestructura de OpenAI. Sin embargo, los datos que se transmiten a servicios de terceros a través de conexiones de red están sujetos a las políticas de retención de datos de esos servicios. Verifica que los puntos de acceso externos cumplan tus requisitos de residencia de datos, retención y cumplimiento normativo.