실행과 스텝

실행과 스텝 (Runs and Steps)

실행(run)이 언제 시작되고 끝나는지, 그 사이에 무슨 일이 일어날 수 있는지, 스텝이 어떻게 단계를 표시하는지 설명해 드릴게요 — 1.0 기준이에요. 컨슈머가 RunAgentInput을 보내면, 프로듀서가 하나 이상의 실행을 담은 이벤트 스트림으로 답합니다.

출처: 문서

본문

컨슈머가 RunAgentInput을 보내요; 프로듀서가 하나 이상의 실행을 담은 이벤트 스트림 — 요청된 실행, 그리고 재생된 히스토리가 앞설 수 있음 — 으로 답합니다. 이 페이지는 실행이 언제 시작·끝나는지와 그 경계가 뜻하는 바를 명시합니다; 실행 안쪽의 순서 규칙은 event patterns에 속해요.

실행 (The run)

스트림은 RUN_STARTED 또는 RUN_ERROR로 시작해야 합니다(MUST). 프로듀서는 어떤 다른 이벤트도 먼저 내보내면 안 되고(MUST NOT), 컨슈머는 다른 것으로 여는 스트림을 프로토콜 위반으로 취급해야 해요(MUST). RUN_ERROR가 먼저 인정되는 것은 실행이 시작되기 전에 실패할 수 있기 때문이에요 — 에이전트에 닿지 못하는 전송은 보고할 실패가 있고, 그것을 대고 보고할 실행은 없어요.

stateDiagram-v2
    [*] --> Active: RUN_STARTED
    [*] --> Failed: RUN_ERROR
    Active --> Closed: RUN_FINISHED
    Active --> Failed: RUN_ERROR
    Closed --> Active: RUN_STARTED (a new run)
    Closed --> Failed: RUN_ERROR (late failure)
    Failed --> Active: RUN_STARTED (a new run)

RUN_STARTED

실행을 엽니다. 그것은 실행의 식별자; 버전 관리 규칙에 따른 프로듀서 자신의 protocolVersion 선언; 그리고 실행이 시작된 입력을 에코할 수 있어요(MAY), 요청을 만들지 않은 컨슈머도 에이전트가 무엇을 물었는지 볼 수 있도록요. parentRunId는 에이전트가 서브에이전트로서가 아니라 별도의 실행으로 다른 에이전트를 호출할 때, 이 실행을 만들어 낸 실행을 이름 붙여요.

RUN_FINISHED

실패하지 않은 실행을 닫고, 어떻게 끝났는지 보고합니다:

  • 그것의 outcome은 완료된 실행과, 외부 입력을 기다리며 인터럽트된 실행, 완료 전에 멈춘 실행을 구분합니다; 없는 outcome은 성공을 의미해요. 인터럽트 outcome과 그것에 이어지는 것은 Interrupts and Resume에, cancelled outcome은 아래에 명시됩니다. outcome은 실행이 왜 끝났는지에 대해 프로듀서가 아는 것을 보고합니다: 프런트엔드 도구 호출에서 멈춘 실행은 완료된 실행이고, 그 성공 outcome은 답하지 않은 채 둔 호출을 pendingToolCallIds에 이름 붙일 수 있어요(MAY), 그러한 호출 후 애플리케이션이 스레드를 계속할지는 애플리케이션의 결정이지 프로듀서의 결정이 아니기 때문이에요.
  • 프로듀서는 스키마가 서술하지 않는 outcome 값을 보내면 안 됩니다(MUST NOT).
  • result는 OPTIONAL이고, 있다면 실행의 반환 값을 담아요.
  • usage는 OPTIONAL이고 프로바이더와 모델별로 항목 하나씩, 아래 회계에 따라 토큰 사용량을 보고합니다. 총계만 원하는 컨슈머는 항목들을 더합니다.

컨슈머가 인식하지 못하는 outcome은 다른 어떤 것과 마찬가지로 인식되지 않은 자료이고, 나머지와 같은 조건으로 떼어냅니다. 선택적 필드를 떼어내면 그것을 없게 만들고, 없는 outcome은 성공을 의미합니다 — 그래서 컨슈머는 완료된 실행을 그 컨슈머가 출시된 뒤 이름 붙은 사유로 끝난 실행과 구분하도록 요구되지 않아요.

이것은 outcome 집합이 유용하게 자랄 수 있는 정도를 경계 지어요. 이후 버전에서 추가된 종단 상태는 더 오래된 컨슈머에게 성공적인 실행으로 도달하므로, 더 오래된 컨슈머가 새 종료 방식을 알아차리게 해야 하는 버전은 outcome만으로는 그것을 말할 수 없어요 — 그 컨슈머들이 이미 중요하게 취급하는 운반체가 필요해요. 그것이 아래의 cancelled outcome을 나중에 미루지 않고 1.0에서 이름 붙인 이유이기도 해요: 나중에 추가되면, 취소된 실행이 모든 1.0 컨슈머에게 완료된 것으로 도달할 테니까요.

취소된 실행 (Cancelled runs)

실행은 완료 전에 의도적으로 멈출 수 있어요 — 그것을 위해 실행되는 사람, 애플리케이션 자신의 코드, 프로듀서가 시행하는 제한에 의해서요. 그것은 실패하지 않았고, 아무것도 기다리지 않지만, 완료되지도 않았어요. 그리고 그것을 완료된 실행과 구분할 수 없는 컨슈머는 부분 작업을 전체로 제시합니다. cancelled outcome이 그 종료를 이름 붙여요.

  • 완료 전에, 실패가 아닌 사유로 실행을 멈추는 프로듀서는 cancelled outcome을 담은 RUN_FINISHED로 그것을 닫아야 합니다(MUST). 멈춘 실행을 성공으로 보고해서는 안 됩니다(MUST NOT) — 성공 outcome으로든, outcome을 생략해서든.
  • 닫힌 실행에 성립하는 모든 것이 여기 성립해요. 프로듀서는 성공하는 실행 앞에서 정확히 그러하듯, 그것을 취소하는 RUN_FINISHED 전에 실행이 연 것 — 메시지, 도구 호출, 스텝, 서브에이전트 — 을 닫아야 해요(MUST). 도구 호출을 닫는 것은 그것의 인자 텍스트가 완전함을 확립하지, 유효함을 확립하지 않으므로, 인자 중간에 잘린 호출은 다른 것처럼 닫히고, 파싱되지 않는 인자로 무엇을 할지는 도구 호출 규칙에 따라 애플리케이션의 결정이에요. 순서대로 닫을 수 없는 프로듀서 — 멈춤이 그 아래의 스트림을 내렸다면 — 대신 RUN_ERROR를 보고합니다: 그 실행은 깨끗하게 끝나지 않았고, RUN_ERROR는 그렇게 되지 않은 실행을 위한 이벤트예요.
  • 취소된 실행은 반환 값이 없어요: result는 없어야 해요(SHOULD). usage는 멈춤 전에 쌓인 토큰을 보고할 수 있어요(MAY).
  • 취소된 실행은 아무것도 기다리지 않아요. 그것의 outcome은 인터럽트를 담지 않고, 스레드의 다음 실행은 재개가 아니라 평범한 새 실행이에요.
  • 컨슈머는 취소된 실행을 성공한 것으로 제시해서는 안 되고(MUST NOT), 실패한 것으로 제시해서도 안 됩니다(MUST NOT) — 사용자가 요청한 멈춤은 그들에게 보여줄 오류가 아니에요. 멈춤 전에 실행이 전달한 모든 것은 RUN_ERROR 뒤에서처럼 전달된 채 남습니다. 그 너머로 멈춤을 어떻게 표면화할지는 컨슈머의 몫이에요.

취소는 프로듀서가 그것을 실행하던 실행에 대한 보고예요. 스트림을 버리는 컨슈머 — 연결을 닫고, 읽기를 멈추고 — 는 취소된 실행이 아니라 truncated run을 가져요: 그것은 끝을 결코 받지 못한 실행을 위해 RUN_FINISHED를 합성해서는 안 됩니다(MUST NOT), 취소든 아니든.

RUN_ERROR

실패한 실행을 끝냅니다. message는 무엇이 잘못됐는지, 사람이 읽게 말해 줘요; code는 OPTIONAL이고 기계가 읽을 수 있으며, 프로토콜이 어휘를 정의하지 않는 열린 문자열이에요; usage는 실패 전에 쌓인 토큰을 RUN_FINISHED에서와 같은 회계로 보고할 수 있어요(MAY).

RUN_ERROR는 잘 형성된 이벤트예요: 프로듀서가 자기 자신의 실패를 보고하는 것이지, 컨슈머가 거부해야 할 것을 보내는 게 아니에요. 실행을 실패로 취급한다는 것은 컨슈머가 실패를 애플리케이션 코드에 표면화해야 하고(MUST) 실행을 성공한 것으로 보고해서는 안 됨(MUST NOT)을 의미합니다. 어떻게 할지는 규정하지 않아요 — 실행을 시작한 호출이 예외를 던지든, 실패와 함께 해소하든, 콜백으로 보고하든 구현의 몫이고, 두 적합한 컨슈머는 다를 수 있어요.

토큰 사용량 (Token usage)

RUN_FINISHED와 RUN_ERROR의 usage는 실행의 모델 호출이 비용이었던 것을, 프로바이더와 모델별 TokenUsage 항목 하나로 보고합니다. 프로바이더는 다르게 세어요 — 하나는 캐시된 프롬프트 토큰을 입력 수에 접고, 다른 하나는 그것을 옆에 보고하며, 셋째는 추론을 출력의 나머지와 분리 보고합니다 — 그래서 프로토콜이 하나의 회계를 고정하고 프로듀서가 그것으로 번역합니다. 모든 수는 총계이거나 총계의 이름 붙은 부분이에요:

  • inputTokens는 호출이 청구된 모든 프롬프트 토큰이에요: 캐시됐든 아니든, 캐시에 쓰였든 아니든, 텍스트든 아니든. outputTokens는 생성된 모든 토큰, 추론 포함이에요.
  • cachedInputTokens(캐시 읽기), cacheWriteInputTokens(캐시 쓰기), reasoningTokens는 그 총계들의 부분이지, 그것들에 대한 추가가 아니에요. 두 캐시 수는 서로소(disjoint)예요. 프로바이더가 이것들 중 하나를 더 작은 총계 옆에 보고하는 프로듀서는 항목을 내보내기 전에 그것을 총계에 더해야 하고(MUST); 프로바이더가 이미 그것을 포함하는 프로듀서는 다시 더하면 안 됩니다(MUST NOT).
  • totalTokens는 inputTokens 더하기 outputTokens예요. 프로듀서는 프로바이더의 총계를 복사하기보다 그것을 계산할 수 있고(MAY), 다르게 세는 프로바이더의 총계를 복사해서는 안 됩니다(MUST NOT).
  • 없는 수는 프로바이더가 보고하지 않았음을 뜻하고, 0은 0을 보고했음을 뜻해요. 프로듀서는 데이터가 없는 수에 0을 내보내면 안 되고(MUST NOT), 컨슈머는 없는 수를 0으로 읽으면 안 됩니다(MUST NOT).

실행이 회계 경계예요:

  • 사용량은 실행 안에서 이루어진 모든 모델 호출을 덮어요, 그것의 서브에이전트가 한 호출을 포함해서요: 서브에이전트 이벤트는 자기만의 사용량을 담지 않고, 서브에이전트의 호출은 실행의 호출이에요.
  • 별도의 실행으로 호출된 에이전트 — parentRunId로 이름 붙여진 — 은 자기 자신의 종단 이벤트에서 자기 사용량을 보고하고, 그것을 만들어 낸 실행은 그 사용량을 자신의 것에 포함해서는 안 됩니다(MUST NOT).
  • 인터럽트된 실행을 재개하는 실행은 자기 자신이 만든 호출만 보고합니다. 인터럽트된 실행은 이미 자기 것을 보고했고, 스레드의 총계를 원하는 컨슈머는 스레드의 실행들에 걸쳐 더합니다.

이 회계 아래서 총계만 원하는 컨슈머는 항목들과 실행들에 걸쳐 totalTokens를 더하고, 비용을 계산하는 컨슈머는 캐시 읽기·캐시 쓰기·추론을 각자 비율로 가격 책정할 각 부분을, 어느 것도 이중 계산하지 않고 얻습니다.

실행이 닫힌 뒤

실행은 RUN_FINISHED나 RUN_ERROR로 끝나고, 그 후 닫힙니다. 실행이 닫히면:

  • 프로듀서는 아래에 이름 붙인 두 개 외에는 그 실행을 위해 어떤 추가 이벤트도 내보내면 안 됩니다(MUST NOT).
  • 프로듀서는 같은 스트림에서 새 실행을 시작하기 위해 RUN_STARTED를 내보낼 수 있어요(MAY).
  • 프로듀서는 RUN_FINISHED 후 RUN_ERROR를 내보낼 수 있어요(MAY), 실행이 성공을 보고한 뒤 표면화된 실패 — 플러시 중 전송 오류 같은 — 를 보고하면서요. 그 경우 컨슈머는 실행을 실패한 것으로 취급해야 해요(MUST).
  • 프로듀서는 RUN_ERROR 후에 RUN_STARTED 외에는 아무것도 내보내면 안 됩니다(MUST NOT).

컨슈머는 실행이 닫힌 뒤 도착하는 그 외의 어떤 이벤트도 거부해야 해요(MUST).

한 스트림에 여러 실행

단일 스트림은 여러 실행을 순서대로 담을 수 있어요(MAY) — 재생된 스레드가 흔한 경우예요. 프로듀서는 다음을 열기 전에 현재 실행을 닫아야 해요(MUST): 실행이 여전히 활성인 동안의 RUN_STARTED는 위반이에요.

스트림 안에서 실행들에 걸쳐 메시지는 누적되고 상태는 이벤트가 그것을 교체하지 않는 한 지속됩니다. 히스토리를 다시 말하는 프로듀서 — 컨슈머의 입력이 이미 담았던 자료를 가진 재생 스레드 — 는 그것을 스냅샷으로 다시 말해야 해요(MUST): MESSAGES_SNAPSHOT은 id로 조정하고 STATE_SNAPSHOT은 교체하므로, 컨슈머가 이미 들고 있는 재진술은 멱등이에요. 컨슈머가 이미 가진 메시지를 다시 스트리밍하면 그것을 다시 말하기보다 이어붙여요, 그래서 스트리밍 삼중(triad)은 새 자료만을 위한 것입니다.

실행 범위 추적 — 열린 메시지, 열린 도구 호출, 열린 스텝, 활성 서브에이전트 — 은 경계를 건너지 않아요: RUN_FINISHED는 실행이 연 모든 것이 이미 닫히기를 요구하고, RUN_ERROR는 여전히 열린 것을 실행과 함께 끝냅니다. 새 실행은 아무것도 열지 않은 채 시작해요.

스텝 (Steps)

스텝은 실행의 단계를, 진행을 보여주는 UI를 위해 표시해요. STEP_STARTED가 스텝을 열고 STEP_FINISHED가 그것을 닫으며, stepName으로 짝지어집니다.

  • 프로듀서는 이미 열린 이름의 스텝을 열면 안 되고(MUST NOT), 결코 열지 않은 스텝을 끝내면 안 됩니다(MUST NOT).
  • 프로듀서가 여는 모든 스텝은 실행이 끝나기 전에 닫혀야 해요(MUST).
  • 스텝은 서로 그리고 실행의 다른 무엇과 겹칠 수 있어요(MAY); 스텝은 스트림 구간 위의 라벨이지, 컨테이너가 아니에요.

데이터 타입

이벤트 형태는 schema reference로 정의됩니다: RunStartedEvent, RunFinishedEvent(RunFinishedOutcome 포함), RunErrorEvent, StepStartedEvent, StepFinishedEvent, TokenUsage.

오류 처리

서로 다른 두 가지가 실행을 나쁘게 끝내고, 컨슈머는 그것들을 분리합니다.

컨슈머가 거부한 스트림은 그것이 감지한 프로토콜 위반이에요: 이 페이지에서 프로듀서가 하면 안 되는 모든 것은 컨슈머가 볼 때 치명적이에요 — RUN_STARTED 전의 이벤트, 닫힌 뒤의 인정된 둘 외 이벤트, 중첩된 RUN_STARTED, 균형이 깨진 스텝, RUN_FINISHED에서 여전히 열린 무엇이든. 프로듀서가 잘못이고 스트림은 신뢰할 수 없어요.

자기 실패를 RUN_ERROR로 보고하는 실행은 그 반대예요: 자신의 일이 성공하지 못했다고 말하는 적합한 프로듀서. 스트림은 잘 형성되었고 컨슈머는 위 규칙에 따라 그것을 받아들여요.

컨슈머는 하나를 다른 것으로 제시해서는 안 됩니다(MUST NOT). 두 종류의 어떤 실패한 실행이든 실패 전에 전달한 모든 것을 유지해요 — RUN_ERROR는 실행이 완료되지 않았음을 말하지, 그 이벤트들이 일어나지 않았음을 말하는 게 아니에요.

더 알아보기 (Learn more)