For the complete documentation index, see llms.txt. Markdown versions of documentation pages are available by appending .md to the page URL.
기본 탐색 메뉴
Theme

패치 적용

모델이 구조화된 diff를 제안하면 애플리케이션에서 이를 적용할 수 있습니다.

Responses API에서 apply_patch를 사용하면 모델이 파일 생성, 수정, 삭제 작업을 구조화된 diff로 제안할 수 있습니다. 애플리케이션은 각 작업을 적용하고 그 결과를 모델에 반환합니다. 예제에서 사용하는 모델은 gpt-6-astra입니다.

사용 시점

apply_patch는 다음과 같은 작업에 사용할 수 있습니다.

  • 여러 파일 리팩터링 – 여러 파일에 걸쳐 심볼 이름을 바꾸거나, 헬퍼를 추출하거나, 모듈을 한 번에 재구성합니다.
  • 버그 수정 – 모델에 문제 진단과 정확한 수정 패치 생성을 함께 맡깁니다.
  • 테스트 및 문서 생성 – 코드를 변경하면서 새 테스트 파일, 픽스처, 문서를 함께 만듭니다.
  • 마이그레이션 및 일괄 수정 – API 마이그레이션, 타입 어노테이션 추가, 서식 수정처럼 일정한 규칙에 따라 반복되는 변경을 적용합니다.

레포지토리와 원하는 변경 사항을 텍스트로 설명할 수 있다면 대개 apply_patch로 그에 맞는 diff를 생성할 수 있습니다.

Responses API에서 패치 적용 도구 사용하기

Responses API에서 apply_patch를 사용하는 절차는 다음과 같습니다.

  1. apply_patch 도구를 활성화해 Responses API 호출하기
    • input에 사용 가능한 파일에 관한 컨텍스트나 요약을 포함하거나, 모델에 파일 시스템을 탐색할 도구를 제공하세요.
    • tools=[{"type": "apply_patch"}]로 도구를 활성화하세요.
  2. 모델로부터 하나 이상의 패치 작업 받기
    • 응답 출력에는 apply_patch_call 객체가 하나 이상 포함됩니다.
    • 각 호출은 파일 하나에 대한 생성, 수정, 삭제 작업 중 하나를 지정합니다.
  3. 작업 환경에 패치 적용하기
    • 다음 작업을 수행하는 패치 하네스나 스크립트를 실행하세요.
      • operation에 담긴 diff를 apply_patch_call별로 해석합니다.
      • 작업 디렉터리나 레포지토리에 패치를 적용합니다.
      • 각 패치의 성공 여부를 기록하고, 로그나 오류 메시지가 있으면 함께 기록합니다.
  4. 모델에 패치 결과 전달하기
    • previous_response_id를 사용하거나 대화 항목을 input에 다시 넣어 Responses API를 다시 호출하세요.
    • apply_patch_call_output 이벤트를 각 call_id에 하나씩 포함하세요. 이벤트에는 status가 필요하며 output 문자열은 선택 사항입니다.
    • 모델이 필요할 때 편집을 이어갈 수 있도록 tools=[{"type": "apply_patch"}] 설정을 유지하세요.
  5. 모델이 편집을 이어가거나 변경 사항을 설명하도록 하기
    • 모델은 apply_patch_call 작업을 추가로 요청할 수 있습니다.
    • 또는 무엇을 왜 변경했는지 사용자에게 설명할 수 있습니다.

예제: 패치 적용 도구로 함수 이름 바꾸기

1단계: 모델에 변경 계획을 세우고 패치를 생성하도록 요청하기

모델에 변경 계획을 세우고 패치를 생성하도록 요청하기
import OpenAI from "openai";
// Application-owned helper; see "Implementing the patch harness" in the guide.
// This demo only applies exact updates to the two in-memory files below.
import { applyOperation } from "../../support/apply-patch.mjs";

const client = new OpenAI();
const files = {
  "lib/fib.py": `def fib(n):
    if n <= 1:
        return n
    return fib(n - 1) + fib(n - 2)
`,
  "run.py": `from lib.fib import fib

print(fib(42))
`,
};
const fileContext = `The user has the following files:
<BEGIN_FILES>
${Object.entries(files)
  .map(([path, content]) => `===== ${path}\n${content}`)
  .join("\n")}
<END_FILES>

Help me rename the fib() function to fibonacci().`;

let response = await client.responses.create({
  model: "gpt-6-astra",
  input: fileContext,
  tools: [{ type: "apply_patch" }],
});

let patchCalls = response.output.filter(
  (item) => item.type === "apply_patch_call"
);

apply_patch_call 객체 예시

apply_patch_call 객체 예시
{
    "id": "apc_08f3d96c87a585390069118b594f7481a088b16cda7d9415fe",
    "type": "apply_patch_call",
    "status": "completed",
    "call_id": "call_Rjsqzz96C5xzPb0jUWJFRTNW",
    "operation": {
        "type": "update_file",
        "diff": "
@@
-def fib(n):
+def fibonacci(n):
    if n <= 1:
        return n
-    return fib(n-1) + fib(n-2)                                                  +    return fibonacci(n-1) + fibonacci(n-2),
",
        "path": "lib/fib.py"
    }
}

2단계: 패치를 적용하고 모델에 결과 전달하기

패치를 적용하고 결과 반환하기
for (let turn = 0; patchCalls.length > 0; turn++) {
  if (turn === 8) throw new Error("Patch workflow exceeded eight tool turns.");
  const results = patchCalls.map((call) => {
    const { success, output } = applyOperation(files, call.operation);

    return {
      type: "apply_patch_call_output",
      call_id: call.call_id,
      status: success ? "completed" : "failed",
      output,
    };
  });

  response = await client.responses.create({
    model: "gpt-6-astra",
    previous_response_id: response.id,
    input: results,
    tools: [{ type: "apply_patch" }],
  });
  patchCalls = response.output.filter(
    (item) => item.type === "apply_patch_call"
  );
}

console.log(response.output_text);
for (const [path, content] of Object.entries(files)) {
  process.stdout.write(`===== ${path}\n${content}`);
}

파일을 찾을 수 없는 등의 이유로 패치 적용에 실패하면 status: "failed"로 설정하세요. 모델이 오류를 해결할 수 있도록 원인을 설명하는 output 문자열을 포함하세요.

apply_patch 호출 실패 보고하기
{
  "type": "apply_patch_call_output",
  "call_id": "call_cNWm41dB3RyQcLNOVTIPBWZU",
  "status": "failed",
  "output": "Could not apply patch to lib/foo.py — file not found on disk"
}

패치 적용 작업

작업 유형 용도 페이로드
create_file path에 새 파일을 생성합니다. diff는 파일의 전체 내용을 담은 V4A diff입니다.
update_file path에 있는 기존 파일을 수정합니다. diff는 추가, 삭제 또는 교체할 내용을 담은 V4A diff입니다.
delete_file path에 있는 파일을 삭제합니다. diff 없이 파일 전체를 삭제합니다.

패치 하네스는 V4A diff 형식을 해석하고 변경 사항을 적용합니다. 구현 예시는 Python Agents SDK 또는 TypeScript Agents SDK 코드를 참고하세요.

패치 하네스 구현하기

apply_patch 도구에는 입력 스키마를 제공할 필요가 없습니다. 모델이 operation 객체를 구성하는 방법을 알고 있기 때문입니다. 하네스에서는 다음 작업을 구현하세요.

  1. 응답에서 작업 파싱하기
    • 응답에서 type: "apply_patch_call"인 항목을 찾으세요.
    • 각 호출의 operation.type과 operation.path를 확인하고, diff가 있으면 함께 확인하세요.
  2. 파일 작업 적용하기
    • create_file과 update_file의 경우 파일 시스템이나 메모리 내 워크스페이스에 V4A diff를 적용하세요.
    • delete_file의 경우 path에 있는 파일을 삭제하세요.
    • 각 작업의 성공 여부를 기록하고, 로그나 오류 메시지가 있으면 함께 기록하세요.
  3. apply_patch_call_output 이벤트 반환하기
    • 각 call_id에 대해 apply_patch_call_output 이벤트를 정확히 하나 반환하세요. 이벤트의 상태는 다음과 같이 설정하세요.
      • 작업을 성공적으로 적용했으면 status: "completed"로 설정하세요.
      • 오류가 발생했으면 status: "failed"로 설정하고 사람이 이해하기 쉬운 짧은 output 문자열을 포함하세요.

안전성과 견고성

  • 경로 검증: 디렉터리 트래버설을 방지하고 허용된 디렉터리 안에서만 파일을 편집하도록 제한하세요.
  • 백업: 패치를 적용하기 전에 파일을 백업하거나 임시 복사본에서 작업하는 방법을 고려하세요.
  • 오류 처리: 패치를 적용할 수 없으면 항상 failed 상태와 함께 원인을 설명하는 output 문자열을 반환하세요.
  • 원자성: 패치 하나라도 실패하면 전체 변경을 롤백할지, 파일별로 성공과 실패를 처리할지 결정하세요.

Agents SDK에서 패치 적용 도구 사용하기

Agents SDK에서도 패치 적용 도구를 사용할 수 있습니다. 실제 파일 작업을 수행하는 하네스는 직접 구현해야 하지만, diff 처리는 applyDiff 함수에 맡길 수 있습니다.

Agents SDK에서 패치 적용 도구 사용하기
import {
  mkdirSync,
  realpathSync,
  lstatSync,
  readFileSync,
  writeFileSync,
  unlinkSync,
} from "node:fs";
import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
import { applyDiff, Agent, run, applyPatchTool } from "@openai/agents";

class WorkspaceEditor {
  constructor(root = "workspace") {
    mkdirSync(root, { recursive: true });
    this.root = realpathSync(root);
  }

  path(name) {
    const target = resolve(this.root, name);
    const relativePath = relative(this.root, target);
    if (
      !relativePath ||
      isAbsolute(relativePath) ||
      relativePath === ".." ||
      relativePath.startsWith(`..${sep}`)
    ) {
      throw new Error("Patch paths must stay inside the workspace");
    }
    let current = this.root;
    for (const part of relativePath.split(sep)) {
      current = join(current, part);
      if (lstatSync(current, { throwIfNoEntry: false })?.isSymbolicLink()) {
        throw new Error("Patch paths must not traverse symbolic links");
      }
    }
    return target;
  }

  async createFile(operation) {
    const path = this.path(operation.path);
    const content = applyDiff("", operation.diff, "create");
    mkdirSync(dirname(path), { recursive: true });
    writeFileSync(path, content, { encoding: "utf8", flag: "wx" });
    return { status: "completed", output: `Created ${operation.path}` };
  }

  async updateFile(operation) {
    const path = this.path(operation.path);
    const content = applyDiff(readFileSync(path, "utf8"), operation.diff);
    writeFileSync(path, content, "utf8");
    return { status: "completed", output: `Updated ${operation.path}` };
  }

  async deleteFile(operation) {
    unlinkSync(this.path(operation.path));
    return { status: "completed", output: `Deleted ${operation.path}` };
  }
}

const editor = new WorkspaceEditor();

const agent = new Agent({
  name: "Patch Assistant",
  model: "gpt-6-astra",
  instructions:
    "Use apply_patch to edit files in the workspace. Supply paths relative to that workspace.",
  tools: [
    applyPatchTool({
      editor,
      // could also be a function for you to determine if approval is needed
      needsApproval: true,
      onApproval: async (_ctx, _approvalItem) => {
        // create your own approval logic
        return { approve: true };
      },
    }),
  ],
});

const result = await run(
  agent,
  "Create tasks.md with a shopping checklist of 5 entries."
);

console.log(`\nFinal response:\n${result.finalOutput}`);

GitHub에서 실행 가능한 전체 예제를 확인할 수 있습니다.

패치 적용 도구 예제 - TypeScript

TypeScript에서 Agents SDK와 함께 패치 적용 도구를 사용하는 예제

패치 적용 도구 예제 - Python

Python에서 Agents SDK와 함께 패치 적용 도구를 사용하는 예제

자주 발생하는 오류 처리하기

모델이 오류를 해결할 수 있도록 status: "failed"와 함께 원인을 명확히 설명하는 output 메시지를 반환하세요.

파일을 찾을 수 없을 때의 오류
{
  "type": "apply_patch_call_output",
  "call_id": "call_abc",
  "status": "failed",
  "output": "Error: File not found at path 'lib/baz.py'"
}

모델은 이러한 오류 메시지를 바탕으로 프롬프트에 포함된 파일을 다시 읽거나 변경 내용을 단순화해 다음 diff를 조정할 수 있습니다.

권장 사항

  • 파일 컨텍스트를 명확히 제공하기
    • Responses API를 호출할 때는 예제처럼 요청에 파일 스냅샷을 직접 포함하거나, 모델에 파일 시스템을 탐색할 도구(예: shell 도구)를 제공하세요.
  • shell 도구와 함께 사용하는 방안 고려하기
    • shell 도구를 함께 사용하면 모델이 디렉터리를 탐색하고, 파일을 읽고, grep으로 키워드를 검색하면서 편집할 파일을 스스로 찾아 수정할 수 있습니다.
  • 변경 범위가 작은 diff 생성 유도하기
    • 시스템 지침에 코드를 대대적으로 다시 작성하기보다 필요한 부분만 최소한으로 수정하도록 명시하세요.
  • 변경 사항이 제대로 적용됐는지 확인하기
    • 일련의 패치를 적용한 뒤 테스트나 린터를 실행하세요. 실패한 항목은 다음 input에 포함해 모델이 문제를 수정할 수 있도록 하세요.

사용 참고 사항