import json
import uuid
from openai import OpenAI
from openai.types.responses import (
FunctionToolParam,
ResponseInputParam,
)
def run_computer_use(endpoint, prompt, model="gpt-6-astra"):
client = OpenAI()
session_id = str(uuid.uuid4())
tools: list[FunctionToolParam] = [
{
"type": "function",
"name": "exec_py",
"description": (
"Run Python in a persistent desktop. Variables persist across calls. "
"PyAutoGUI operations are synchronous. Available: pyautogui, time, "
"log(value), and display(PIL_image). Inspect the screen with "
"display(pyautogui.screenshot()) before acting. Use screenshot "
"coordinates and check the screen after a short group of actions. "
"Keep screenshots in memory and PyAutoGUI's fail-safe enabled."
),
"parameters": {
"type": "object",
"properties": {"code": {"type": "string"}},
"required": ["code"],
"additionalProperties": False,
},
"strict": True,
}
]
next_input: ResponseInputParam = [{"role": "user", "content": prompt}]
previous_response_id = None
for turn in range(20):
response = client.responses.create(
model=model,
tools=tools,
input=next_input,
previous_response_id=previous_response_id,
)
if response.status != "completed":
raise RuntimeError(f"Response stopped with status: {response.status}")
calls = [item for item in response.output if item.type == "function_call"]
if not calls and any(
item.type == "message" and item.phase != "commentary"
for item in response.output
):
print(response.output_text)
return
if turn == 19:
raise RuntimeError(
"The task reached the 20-response limit. Inspect the last result."
)
next_input = []
for call in calls:
if call.name != "exec_py":
raise ValueError(f"Unexpected tool: {call.name}")
code = json.loads(call.arguments)["code"]
output = execute_in_sandbox(code, session_id, endpoint)
next_input.append(
{
"type": "function_call_output",
"call_id": call.call_id,
"output": output,
}
)
previous_response_id = response.id컴퓨터 사용 기능을 이용하면 모델이 브라우저나 데스크톱 인터페이스를 조작할 수 있습니다. 양식을 작성하거나 사용자 플로우를 테스트하거나 애플리케이션에서 작업을 완료할 때 활용하세요.
모델이 확인해야 할 정보와 환경 운영 주체에 따라 시작 방법을 선택하세요.
| 작업 | 시작 방법 |
|---|---|
| 자체 브라우저나 데스크톱에서 스크린샷을 확인하고 마우스나 키보드로 조작하기 | Responses의 컴퓨터 도구와 함께 ComputerToolset을 사용하세요. |
| 자체 브라우저에서 페이지 텍스트를 읽거나 컨트롤과 탭 조작하기 | BrowserToolset을 사용하세요. 드라이버는 선택한 동작을 함수 도구로 제공합니다. |
| OpenAI가 호스팅하는 브라우저 실행하기 | 별도의 Agents API 컴퓨터 사용 가이드를 따르세요. |
상호작용 없이 공개 정보만 조회하려면 웹 검색을 사용하세요.
SDK 사용하기
SDK는 Python 또는 Node에서 Responses 도구 호출 루프를 실행합니다. 애플리케이션은 드라이버를 제공합니다. 드라이버는 환경 상태를 읽거나 허용된 동작을 수행하는 코드입니다. 세션과 권한 관리, 리소스 정리도 애플리케이션이 담당합니다.
호스팅 환경 연결하기
브라우저나 데스크톱을 직접 운영하는 대신 파트너가 제공하는 환경을 사용하세요. 드라이버를 제공하는 파트너도 있고, 애플리케이션에서 SDK에 연결할 컨트롤을 제공하는 파트너도 있습니다. 설정 방법과 지원하는 패키지 버전은 파트너 가이드를 참고하세요.
| 환경 | 통합 방법 |
|---|---|
| Browserbase | Browserbase에서 호스팅하는 세션에 Stagehand 브라우저 드라이버를 사용하세요. Stagehand 통합 가이드를 따르거나 Python 및 TypeScript 예제를 참고하세요. |
| Browser Use | Browser Use 라이브러리 드라이버를 사용하거나 Browser Use Cloud 통합으로 기존 Playwright 코드를 연결하세요. 두 가이드 모두 Python을 사용합니다. |
| Daytona | Daytona 데스크톱 샌드박스에서 ComputerToolset을 사용하세요. Daytona 컴퓨터 사용 헬퍼 가이드(Python)를 따르세요. Agents SDK 예제는 Daytona 컴퓨터 사용 레시피를 참고하세요. |
| E2B Desktop | 데스크톱 샌드박스에서 컴퓨터 동작을 수행하거나 코드를 실행하세요. E2B 컴퓨터 사용 가이드를 따르거나 Python 및 TypeScript 예제를 참고하세요. |
세션 제한 시간을 설정하고 실행이 실패하더라도 세션을 닫거나 해제하세요. 정리에 실패하면 제공업체 대시보드를 확인하세요. 제공업체의 네트워크 규칙을 사용해 세션에서 접근할 수 있는 사이트와 리소스를 제한하세요.
첫 작업 실행하기
Python 3.11 이상 또는 Node.js 22.6 이상을 사용하세요. API 키를 생성하고 환경 변수로 내보내세요. 두 예제 모두 임시 로컬 브라우저를 사용하며 Responses API를 호출합니다. 이때 요금이 발생할 수 있습니다.
새 폴더에 패키지와 Chromium을 설치하세요.
# Python
python3 -m venv .venv
.venv/bin/python -m pip install openai==3.31.0 playwright==1.61.0
.venv/bin/python -m playwright install chromium
# Node
npm install openai@7.33.0 playwright@1.61.1
npx playwright install chromium
Windows에서는 py -3을 python3 대신 사용하고 .venv\Scripts\python.exe를 .venv/bin/python 대신 사용하세요.
모델이 가상의 택배 수령 페이지 스크린샷을 보고 컨트롤을 클릭해 수령 정보를 표시합니다. 드라이버는 스크린샷 캡처와 해당 컨트롤을 마우스 왼쪽 버튼으로 클릭하는 동작만 허용합니다. 페이지의 네트워크 요청은 차단하며 실행이 끝나면 브라우저를 닫습니다. 스크립트는 Responses 요청을 최대 6회 보내고 페이지를 확인한 뒤 성공 여부를 보고합니다. 프로젝트에서 컴퓨터 도구를 지원하는 모델에 접근할 수 있어야 합니다.
사용할 언어에 맞는 예제를 pickup.py 또는 pickup.mjs로 저장하세요.
import asyncio
import base64
from openai import AsyncComputerToolset, AsyncOpenAI
from playwright.async_api import async_playwright
class PickupDriver:
def __init__(self, page):
self.page = page
async def screenshot(self):
image = await self.page.screenshot(type="png")
return {
"type": "computer_screenshot",
"image_url": "data:image/png;base64," + base64.b64encode(image).decode(),
}
async def allowed(self, action):
if action.type == "screenshot":
return True
if action.type != "click" or action.button != "left" or action.keys:
return False
box = await self.page.locator("summary").bounding_box()
return bool(
box
and box["x"] <= action.x < box["x"] + box["width"]
and box["y"] <= action.y < box["y"] + box["height"]
)
async def click(self, action):
# The page may have changed after approval, so bind the click again.
if not await self.allowed(action):
raise RuntimeError("Pickup details moved or the click is not allowed.")
await self.page.mouse.click(action.x, action.y)
async def refuse(self, action):
raise RuntimeError(f"This exercise does not allow {action.type}.")
double_click = drag = keypress = move = scroll = type = wait = refuse
async def main():
async with (
async_playwright() as playwright,
AsyncOpenAI(timeout=30, max_retries=0) as client,
):
browser = await playwright.chromium.launch(env={}, chromium_sandbox=True)
try:
page = await browser.new_page(viewport={"width": 1024, "height": 768})
await page.route("**/*", lambda route: route.abort())
await page.set_content(
"<title>Parcel pickup</title><main>"
"<h1>Parcel pickup</h1><p>Order #1042 is ready.</p>"
"<details><summary>Show pickup details</summary>"
"<p>Locker 18. Pickup code: 6249.</p></details></main>"
)
driver = PickupDriver(page)
tools = AsyncComputerToolset(driver)
try:
response = await tools.run(
client,
{
"model": "gpt-6.1-sol",
"store": False,
"input": "Take a screenshot. Open 'Show pickup details' and report the locker and pickup code. Use only screenshots and left clicks.",
},
before_action=driver.allowed,
max_steps=6,
)
if await page.locator("details").get_attribute("open") is None:
raise RuntimeError("Pickup details are still closed.")
print(
"Verified pickup details:",
await page.locator("details p").inner_text(),
)
print("Answer:", response.output_text)
finally:
tools.close()
finally:
await browser.close()
if __name__ == "__main__":
asyncio.run(main())저장한 예제에 맞게 .venv/bin/python pickup.py 또는 node pickup.mjs를 실행하세요. 성공하면 스크립트가 Verified pickup details: Locker 18. Pickup code: 6249.를 출력한 뒤 모델의 답변을 출력합니다. 처리 대기 중인 API 안전 검사가 있으면 이 예제는 중단됩니다. 애플리케이션에서 해당 검사를 처리할 수 있을 때만 사용자 승인을 받는 콜백을 추가하세요.
모델이 방문증 양식에 이름을 입력하고 Create badge 버튼을 클릭합니다. 드라이버는 페이지 읽기, 텍스트 입력, 클릭만 제공합니다. 확인 콜백은 이 양식의 컨트롤과 Mira라는 이름만 허용합니다. 스크립트는 페이지에 표시된 결과를 검증한 뒤 성공 여부를 보고합니다.
사용할 언어에 맞는 예제를 badge.py 또는 badge.mjs로 저장하세요.
import asyncio
from openai import AsyncOpenAI
from openai.lib.browser import AsyncBrowserToolset
from openai.lib.responses_tool_runner import AsyncResponsesToolRunner
from playwright.async_api import async_playwright
class BadgeForm:
def __init__(self, page):
self.page = page
async def state(self):
return {
"tabs": [
{
"id": "badge",
"title": await self.page.title(),
"url": self.page.url,
"active": True,
}
]
}
async def read_page(self, args):
return {
"version": 1,
"text": await self.page.locator("body").inner_text(),
"elements": [
{"ref": "name", "role": "textbox", "label": "Name"},
{"ref": "save", "role": "button", "label": "Create badge"},
],
"omitted": [],
}
async def type(self, args):
await self.page.locator("#name").fill(args["text"])
async def click(self, args):
await self.page.locator("#save").click()
async def confirm(self, context):
args = context["arguments"]
if self.page.url != "about:blank" or args["tab_id"] not in (None, "badge"):
return False
return (
context["member"] == "type"
and args["target"] == "name"
and args["text"] == "Mira"
) or (context["member"] == "click" and args["target"] == "save")
async def main():
async with (
async_playwright() as playwright,
AsyncOpenAI(timeout=30, max_retries=0) as client,
):
browser = await playwright.chromium.launch(env={}, chromium_sandbox=True)
try:
page = await browser.new_page()
await page.route("**/*", lambda route: route.abort())
await page.set_content("""
<title>Visitor badge</title>
<label>Name <input id="name"></label>
<button id="save" onclick="document.querySelector('output').innerText =
'Badge ready: ' + document.querySelector('#name').value">Create badge</button>
<output></output>
""")
driver = BadgeForm(page)
tools = AsyncBrowserToolset(
driver, members=["read_page", "type", "click"], confirm=driver.confirm
)
response = await AsyncResponsesToolRunner(browser=tools).run(
client,
{
"model": "gpt-6-astra",
"store": False,
"input": "Create a visitor badge for Mira.",
},
max_steps=6,
)
result = await page.locator("output").inner_text()
if result != "Badge ready: Mira":
raise RuntimeError(f"The badge was not created: {result!r}")
print(result)
print("Answer:", response.output_text)
finally:
await browser.close()
if __name__ == "__main__":
asyncio.run(main())저장한 예제에 맞게 .venv/bin/python badge.py 또는 node badge.mjs를 실행하세요. 성공하면 스크립트가 Badge ready: Mira를 출력한 뒤 모델의 답변을 출력합니다.
API가 도구를 거부하거나 Chromium이 시작되지 않으면 실행 문제 해결 방법을 참고하세요. 클릭 위치가 어긋나면 스크린샷과 좌표를 확인하세요.
모델 접근 권한, 비용, 데이터 확인하기
- 모델: 브라우저 함수 도구를 사용하려면 모델이 Responses와 함수 호출을 지원하는지 확인하세요. 드라이버가 이미지를 반환한다면 이미지 입력 지원 여부도 확인하세요. 스크린샷 기반 컴퓨터 동작을 사용하려면 모델의 Tools 섹션에서 컴퓨터 사용 지원 여부를 확인하세요. 모델 카탈로그를 참고하고 프로젝트의 접근 권한을 확인하세요.
- 비용: Responses 요청마다 입력 및 출력 토큰 요금이 발생할 수 있습니다. 스크린샷은 이미지 입력으로 계산됩니다. API 요금을 확인하거나 이미지 입력 비용을 추정하거나 요청별 사용량을 조회하세요. 호스팅 환경에는 별도 요금이 부과될 수 있습니다.
- 데이터: SDK는 드라이버가 반환한 페이지 텍스트나 스크린샷을 OpenAI에 전송합니다. 예제에서는
store를false로 설정하지만, 이 설정이 모든 형태의 데이터 보관을 막지는 않습니다. 데이터 제어와 이미지 입력 예외 사항을 확인하세요. 환경 제공업체에는 자체 데이터 정책이 있습니다.
변경 사항 확인 및 승인하기
다음으로 Python 또는 Node 예약 데모를 실행해 보세요. 데모는 가상의 예약 양식을 작성하고 제출 전에 승인을 요청한 뒤 저장된 필드와 예약 확인 내용을 점검합니다. 브라우저를 지켜보거나 직접 제어할 수 있고, 제어권을 돌려주거나 실행을 중단할 수도 있습니다. 두 버전 모두 API 호출 없이 실제 브라우저로 로컬에서 연습할 수 있습니다.
데스크톱에서 SDK 동작을 지켜보려면 데스크톱 동작을 화면으로 확인하는 데모를 실행해 보세요.
드라이버 조정하기
샘플 페이지를 애플리케이션이 제어하는 환경으로 교체하세요. 브라우저 드라이버에서는 members로 작업에 필요한 동작만 제공하세요. 컴퓨터 드라이버에서는 필수 메서드를 구현하고 지원하지 않는 동작은 거부하세요. 실행이 끝날 때까지 세션을 유지한 다음 애플리케이션이 생성한 리소스를 정리하세요.
페이지 콘텐츠는 모델에 영향을 줄 수 있습니다. 새 브라우저 프로필이나 격리된 데스크톱을 사용하세요. 네트워크 접근을 제한하고 페이지 텍스트와 스크린샷에 자격 증명이 노출되지 않도록 하세요. 변경하기 전에 승인을 요청하고 성공을 보고하기 전에 저장된 결과를 확인하세요. 메서드 명세, 승인, URL 정책, 복구 방법은 드라이버 레퍼런스를 참고하세요.
선택지가 정해져 있을 때 Decisions로 전환하기
모델이 동작을 선택하고 인수를 생성해야 한다면 Responses를 사용하세요. 허용되는 모든 동작과 각 동작에 필요한 전체 인수를 애플리케이션이 이미 알고 있을 때만 Decisions를 사용하세요. 예를 들어 앱에서 route_billing, route_technical, review를 선택지로 제공할 수 있습니다. Decisions가 선택지를 고르면 애플리케이션이 해당 동작을 승인하고 실행한 뒤 결과를 확인합니다. 페이지가 바뀌면 기존 선택지를 폐기하세요. Decisions 가이드를 참고하세요.
예약 데모에서 시험해 보려면 Decisions 접근 권한이 있는 키를 사용하고 OPENAI_DECISIONS_MODEL을 설정한 뒤 서버를 다시 시작하세요. 새 실행을 시작하고 Decisions를 선택하세요.
API 직접 사용하기
도구 호출 루프를 직접 관리해야 한다면 Responses API를 직접 사용하세요. 애플리케이션에서 환경을 제공하고 모델이 요청한 동작을 실행합니다. 애플리케이션에 맞는 연결 방식을 선택하세요.
- 코드 실행: 모델이 PyAutoGUI나 Playwright 같은 라이브러리로 인터페이스를 조작하는 코드를 작성합니다. 한 번의 호출에 여러 동작, 반복문 또는 조건문을 포함할 수 있습니다.
- 컴퓨터 도구: 모델이 구조화된 마우스 및 키보드 동작을 반환하면 애플리케이션이 이를 브라우저나 데스크톱 입력으로 변환합니다.
모델이 GPT-6 Astra라면 코드 실행 방식을 권장합니다. 대안으로 computer 도구도 계속 지원합니다.
이미 함수 호출이나 원격 MCP 도구로 UI 동작을 제공하고 있다면 기존 인터페이스를 유지할 수 있습니다. 통합 방식별로 도구를 실행하고 결과를 반환하는 방법이 어떻게 다른지는 자체 UI 도구 사용하기를 참고하세요.
코드 실행 사용하기
코드 실행 방식에서는 스크립트를 입력으로 받는 함수 도구를 모델에 제공합니다. 애플리케이션은 격리된 브라우저나 데스크톱 환경에서 스크립트를 실행하고 스크린샷을 비롯한 실행 결과를 반환합니다. 모델이 이전 작업에 이어서 진행할 수 있도록 호출 사이에 환경을 유지하세요.
샘플 앱 실행하기
CUA 샘플 앱에는 JavaScript/Playwright 구현과 Python/PyAutoGUI 구현이 있으며, 로컬 작업과 공용 콘솔을 제공합니다.
- 격리된 환경에서 선택한 구현의 설정 지침을 따르세요.
- 내장 시나리오를 선택하고 실행을 시작하세요.
- 수행한 동작, 스크린샷, 최종 상태를 살펴보고 작업 성공 여부를 판단하세요.
설치 방법, 데스크톱 권한, 지원 환경은 앱의 README를 참고하세요. 실제 사이트나 계정에 적용하기 전에 안전하게 실행하기를 확인하세요.
자체 런타임 연결하기
다음 예제는 자체 런타임을 사용하는 API 호출 루프를 보여 줍니다. Python, Ruby, Go 클라이언트는 PyAutoGUI를 사용하는 데스크톱 런타임에 Python 코드를 보냅니다. JavaScript 클라이언트는 Playwright로 브라우저를 조작합니다. 각 클라이언트는 일반 함수 도구를 제공하고 원래의 call_id와 함께 텍스트나 이미지를 반환합니다.
예제에서 execute_in_sandbox 또는 executeInSandbox 헬퍼는 실행 환경에 코드를 보내고 관찰 결과를 반환합니다. 이 헬퍼는 브라우저나 데스크톱 세션을 유지하고 실행 한도를 강제하며 애플리케이션의 권한 규칙을 적용해야 합니다. 이 예제들은 자체 런타임을 통합하는 방법을 보여 주며 샘플 앱 실행 절차와는 별개입니다.
import json
import uuid
from openai import OpenAI
from openai.types.responses import (
FunctionToolParam,
ResponseInputParam,
)
def run_computer_use(endpoint, prompt, model="gpt-6-astra"):
client = OpenAI()
session_id = str(uuid.uuid4())
tools: list[FunctionToolParam] = [
{
"type": "function",
"name": "exec_py",
"description": (
"Run Python in a persistent desktop. Variables persist across calls. "
"PyAutoGUI operations are synchronous. Available: pyautogui, time, "
"log(value), and display(PIL_image). Inspect the screen with "
"display(pyautogui.screenshot()) before acting. Use screenshot "
"coordinates and check the screen after a short group of actions. "
"Keep screenshots in memory and PyAutoGUI's fail-safe enabled."
),
"parameters": {
"type": "object",
"properties": {"code": {"type": "string"}},
"required": ["code"],
"additionalProperties": False,
},
"strict": True,
}
]
next_input: ResponseInputParam = [{"role": "user", "content": prompt}]
previous_response_id = None
for turn in range(20):
response = client.responses.create(
model=model,
tools=tools,
input=next_input,
previous_response_id=previous_response_id,
)
if response.status != "completed":
raise RuntimeError(f"Response stopped with status: {response.status}")
calls = [item for item in response.output if item.type == "function_call"]
if not calls and any(
item.type == "message" and item.phase != "commentary"
for item in response.output
):
print(response.output_text)
return
if turn == 19:
raise RuntimeError(
"The task reached the 20-response limit. Inspect the last result."
)
next_input = []
for call in calls:
if call.name != "exec_py":
raise ValueError(f"Unexpected tool: {call.name}")
code = json.loads(call.arguments)["code"]
output = execute_in_sandbox(code, session_id, endpoint)
next_input.append(
{
"type": "function_call_output",
"call_id": call.call_id,
"output": output,
}
)
previous_response_id = response.idimport { randomUUID } from "node:crypto";
import OpenAI from "openai";
async function runComputerUse(endpoint, prompt, model = "gpt-6-astra") {
const client = new OpenAI();
const sessionId = randomUUID();
const tools = [
{
type: "function",
name: "exec_js",
description: `Run JavaScript in a persistent browser. Available: Playwright's
browser, context, and page objects; console.log(value); and display(base64Image).
Save reusable variables on globalThis. Inspect a screenshot before acting and
check the screen after a short group of actions. Keep screenshots in memory.
Use top-level await for async operations. Return images with display() and concise
text with console.log(). The context viewport is 1440x900.`,
parameters: {
type: "object",
properties: { code: { type: "string" } },
required: ["code"],
additionalProperties: false,
},
strict: true,
},
];
let nextInput = [{ role: "user", content: prompt }];
let previousResponseId;
for (let turn = 0; turn < 20; turn++) {
const response = await client.responses.create({
model,
tools,
input: nextInput,
previous_response_id: previousResponseId,
reasoning: { effort: "low" },
});
if (response.status !== "completed") {
throw new Error(`Response stopped with status: ${response.status}`);
}
const calls = response.output.filter(
(item) => item.type === "function_call"
);
if (
calls.length === 0 &&
response.output.some(
(item) => item.type === "message" && item.phase !== "commentary"
)
) {
console.log(response.output_text);
return;
}
if (turn === 19) {
throw new Error(
"The task reached the 20-response limit. Inspect the last result."
);
}
nextInput = [];
for (const call of calls) {
if (call.name !== "exec_js")
throw new Error(`Unexpected tool: ${call.name}`);
const { code } = JSON.parse(call.arguments);
const output = await executeInSandbox(code, sessionId, endpoint);
nextInput.push({
type: "function_call_output",
call_id: call.call_id,
output,
});
}
previousResponseId = response.id;
}
}require "json"
require "openai"
require "securerandom"
def run_computer_use(endpoint, prompt)
client = OpenAI::Client.new
session_id = SecureRandom.uuid
tools = [
{
type: :function,
name: "exec_py",
description: "Run Python in a persistent desktop. Variables persist across calls. PyAutoGUI operations are synchronous. Available: pyautogui, time, log(value), and display(PIL_image). Inspect the screen with display(pyautogui.screenshot()) before acting. Use screenshot coordinates and check the screen after a short group of actions. Keep screenshots in memory and PyAutoGUI's fail-safe enabled.",
parameters: {
type: :object,
properties: { code: { type: :string } },
required: ["code"],
additionalProperties: false
},
strict: true
}
]
next_input = []
next_input << {
role: :user,
content: prompt
}
history = {}
20.times do |turn|
response = client.responses.create(
model: "gpt-6-astra", tools: tools, input: next_input, previous_response_id: history[:id]
)
raise "Response stopped with status: #{response.status}" unless response.status == OpenAI::Responses::ResponseStatus::COMPLETED
calls = response.output.grep(OpenAI::Responses::ResponseFunctionToolCall)
if calls.empty? && response.output.any? { |item| item.is_a?(OpenAI::Responses::ResponseOutputMessage) && item.phase != :commentary }
puts(response.output_text)
return response
end
raise "The task reached the 20-response limit" if turn == 19
next_input.clear
calls.each do |call|
raise "Unexpected tool: #{call.name}" unless call.name == "exec_py"
code = JSON.parse(call.arguments).fetch("code")
raise "Expected Python source text" unless code.is_a?(String)
output = execute_in_sandbox(code, session_id, endpoint)
next_input << {
type: :function_call_output,
call_id: call.call_id,
output: output
}
end
history[:id] = response.id
end
endfunc runComputerUse(ctx context.Context, client openai.Client, endpoint, prompt string, terminal *bufio.Scanner, output io.Writer) error {
session := make([]byte, 16)
if _, err := rand.Read(session); err != nil {
return err
}
sessionID := hex.EncodeToString(session)
tool := responses.ToolParamOfFunction("exec_py", map[string]any{
"type": "object", "properties": map[string]any{"code": map[string]any{"type": "string"}},
"required": []string{"code"}, "additionalProperties": false,
}, true)
tool.OfFunction.Description = openai.String("Run Python in a persistent desktop. Variables persist across calls. " +
"PyAutoGUI operations are synchronous. Available: pyautogui, time, " +
"log(value), and display(PIL_image). Inspect the screen with " +
"display(pyautogui.screenshot()) before acting. Use screenshot " +
"coordinates and check the screen after a short group of actions. " +
"Keep screenshots in memory and PyAutoGUI's fail-safe enabled.")
params := responses.ResponseNewParams{
Model: "gpt-6-astra", Tools: []responses.ToolUnionParam{tool},
Input: responses.ResponseNewParamsInputUnion{OfString: openai.String(prompt)},
}
for turn := 0; turn < 20; turn++ {
response, err := client.Responses.New(ctx, params)
if err != nil {
return err
}
if response.Status != "completed" {
return fmt.Errorf("response stopped with status: %s", response.Status)
}
var calls []responses.ResponseFunctionToolCall
finalMessage := false
for _, item := range response.Output {
if item.Type == "function_call" {
calls = append(calls, item.AsFunctionCall())
}
if item.Type == "message" && item.AsMessage().Phase != "commentary" {
finalMessage = true
}
}
if len(calls) == 0 && finalMessage {
fmt.Fprintln(output, response.OutputText())
return nil
}
if turn == 19 {
return fmt.Errorf("task reached the 20-response limit; inspect the last result")
}
nextInput := responses.ResponseInputParam{}
for _, call := range calls {
if call.Name != "exec_py" {
return fmt.Errorf("unexpected tool: %s", call.Name)
}
var arguments struct {
Code *string `json:"code"`
}
if err := json.Unmarshal([]byte(call.Arguments), &arguments); err != nil {
return err
}
if arguments.Code == nil {
return fmt.Errorf("exec_py requires code")
}
observations, err := executeInSandbox(ctx, endpoint, sessionID, *arguments.Code, terminal, output)
if err != nil {
return err
}
nextInput = append(nextInput, responses.ResponseInputItemUnionParam{
OfFunctionCallOutput: &responses.ResponseInputItemFunctionCallOutputParam{
CallID: openai.String(call.CallID),
Output: responses.ResponseInputItemFunctionCallOutputOutputUnionParam{OfResponseFunctionCallOutputItemArray: observations},
},
})
}
params.Input = responses.ResponseNewParamsInputUnion{OfInputItemList: nextInput}
params.PreviousResponseID = openai.String(response.ID)
}
return nil
}전체 클라이언트 어댑터와 텍스트 및 이미지 출력 형식은 실행 서비스에 연결하기를 참고하세요. 예제의 서비스 인터페이스는 애플리케이션이 제공하며 OpenAI가 호스팅하는 엔드포인트가 아닙니다.
상태 유지 및 관찰 결과 반환하기
호출 사이에 브라우저나 데스크톱 세션을 유지하세요. Python이나 JavaScript 네임스페이스를 유지하면 변수도 보존할 수 있습니다. 모델이 무엇을 사용할 수 있는지 알 수 있도록 도구 정의에 사용 가능한 객체와 헬퍼를 설명하세요.
UI 상태를 알 수 없을 때는 현재 스크린샷을 모델에 제공하세요. 몇 가지 동작을 수행한 뒤에는 모델이 결과를 확인할 수 있도록 스크린샷을 다시 반환하세요. 이미지를 메모리에 보관하고 detail: "original"을 사용해 해상도를 유지하세요. 스크린샷을 축소했다면 동작을 실행하기 전에 모델이 지정한 좌표를 원래 환경의 좌표계로 변환하세요. 스크린샷 캡처 및 해상도를 참고하세요.
API 대화 상태와 실행 환경 상태는 별도로 관리됩니다. 대화에 도구 호출과 출력을 보존하고 애플리케이션에서 해당 환경을 계속 사용할 수 있도록 유지하세요. 응답을 이어 가더라도 브라우저 세션, 로그인 상태, 런타임 변수가 복원되지는 않습니다.
컴퓨터 도구 사용하기
통합 코드가 생성된 코드 대신 구조화된 동작을 입력으로 받는다면 이 방식을 사용하세요.
이 방식을 시험해 보려면 동일한 샘플 앱 설정 절차를 따른 뒤 Native 모드를 선택하고 내장 시나리오를 실행하세요. 컴퓨터 도구를 지원하는 모델을 사용하세요.
API 통신은 세 단계로 진행됩니다. 작업을 보내고, 반환된 동작을 실행한 뒤, 스크린샷을 반환합니다. 아래 예제에서는 Show filters 컨트롤과 검색 필드가 있는 페이지를 사용합니다. 도구를 통합할 때는 자체 인터페이스에 맞게 작업을 조정하세요.
환경 설정과 동작 핸들러 구현 방법은 통합 레시피를 참고하세요.
작업 보내기
요청에서 computer를 tools 배열에 추가해 활성화하고 원하는 결과를 설명하세요.
from openai import OpenAI
client = OpenAI()
response = client.responses.create(
model="gpt-6.1-sol",
tools=[{"type": "computer"}],
input="Check whether the Filters panel is open. If it is not open, click Show filters. Then type penguin in the search box. Use the computer tool for UI interaction.",
)
print(response.output)요청된 동작 실행하기
모델이 반환하는 computer_call에는 실행 순서대로 동작이 나열된 actions 배열이 들어 있습니다. 예를 들어 다음 호출은 검색 필드를 선택하고 penguin을 입력합니다.
{
"output": [
{
"type": "computer_call",
"call_id": "call_002",
"actions": [
{ "type": "click", "button": "left", "x": 405, "y": 157 },
{ "type": "type", "text": "penguin" }
],
"status": "completed"
}
]
}동작 핸들러는 이러한 요청을 브라우저 또는 운영체제 입력으로 변환합니다. 허용된 동작을 순서대로 실행한 다음 업데이트된 화면을 캡처하세요. 모델이 요청할 수 있는 동작은 click, double_click, drag, move, scroll, keypress, type, wait, screenshot입니다.
첫 번째 호출에는 screenshot 동작만 포함될 수 있습니다. 이 경우 UI를 변경하지 않고 현재 화면을 캡처해 반환하세요. 호출에 status: "completed"가 표시되면 모델이 해당 호출 생성을 마쳤다는 뜻입니다. 실제 동작은 애플리케이션이 실행해야 합니다.
키 매핑, 드래그 경로, 보조 키 처리 방법은 동작 핸들러 예제를 참고하세요.
스크린샷 반환하기
호출 결과로 computer_call_output을 반환할 때는 call_id가 처리한 호출의 ID와 일치해야 합니다. previous_response_id를 사용해 모델과의 대화를 이어 가세요.
from openai import OpenAI
client = OpenAI()
def send_computer_screenshot(response, call_id, screenshot_base64):
return client.responses.create(
model="gpt-6.1-sol",
tools=[{"type": "computer"}],
previous_response_id=response.id,
input=[
{
"type": "computer_call_output",
"call_id": call_id,
"output": {
"type": "computer_screenshot",
"image_url": f"data:image/png;base64,{screenshot_base64}",
"detail": "original",
},
}
],
)이 루프에도 동일한 스크린샷 및 상태 관리 지침이 적용됩니다. previous_response_id로 모델과의 대화를 이어 가는 동안 환경을 계속 사용할 수 있도록 유지하세요.
모델이 더 이상 computer_call 항목을 반환하지 않을 때까지 반복하세요. 나머지 출력에 답변, 도움 요청 또는 다른 도구 호출이 있는지 살펴보고 애플리케이션에서 결과를 검증하세요. 이 예제에서는 Filters 패널이 열려 있고 검색 필드에 penguin이 입력되어 있어야 합니다.
필수 동작 헬퍼와 스크린샷 헬퍼를 포함한 루프의 기본 구조는 컴퓨터 사용 루프 반복하기를 참고하세요.
안전하게 실행하기
컴퓨터 사용은 실제 계정과 데이터에 영향을 줄 수 있습니다. 다음 제어 방식을 모델 지침에 포함하고 애플리케이션과 실행 환경에도 적용하세요.
- 환경을 제한하세요. 격리된 브라우저나 VM을 사용하고 허용할 사이트와 동작을 목록으로 지정하세요. 작업에 필요한 범위로만 접근을 제한하세요.
- 화면 콘텐츠를 신뢰하지 마세요. 페이지, 문서 또는 도구 결과에 포함된 텍스트는 권한을 부여하거나 사용자의 지시를 무효화할 수 없습니다.
- 중대한 영향을 미치는 동작은 사용자에게 확인하세요. 구매, 데이터 전송, 데이터를 훼손하는 변경 등 되돌리기 어려운 동작은 사용자가 통제할 수 있어야 합니다. 양식에 민감한 정보를 입력하는 것도 전송에 해당합니다.
- 실행 한도를 설정하고 결과를 검증하세요. 단계 수, 시간 또는 비용에 한도를 두고 취소 기능을 제공하세요. 모델의 최종 답변에만 의존하지 말고 실제 결과를 확인하세요.
구체적인 승인 요건, 사람에게 제어권을 넘기는 방법, 프롬프트 예제는 확인 및 동의 지침을 참고하세요.
다음 단계
- 환경 설정, 동작 핸들러, 스크린샷 캡처, 실행 서비스 어댑터는 통합 레시피를 참고하세요.
- 이전 버전의 통합 코드를 업데이트할 때는 computer-use-preview에서 마이그레이션하기를 따르세요.
- 브라우저 및 데스크톱 워크플로우 전체를 살펴보려면 CUA 샘플 앱을 확인하세요.