Results
Results (결과)
Runner.run 메서드를 호출하면 두 가지 결과 타입 중 하나를 받아요.
Runner.run(...)또는Runner.run_sync(...)의RunResultRunner.run_streamed(...)의RunResultStreaming
둘 다 RunResultBase에서 상속받는데, 여기에는 final_output, new_items, last_agent, raw_responses, to_state() 같은 공유 결과 표면이 노출돼 있어요. RunResultStreaming은 stream_events(), current_agent, is_complete, cancel(...) 같은 스트리밍 전용 컨트롤을 추가해요.
출처: 문서
본문
올바른 결과 표면 고르기
대부분의 애플리케이션은 결과 프로퍼티나 헬퍼 몇 개만 필요해요.
| 필요하다면... | 사용 |
|---|---|
| 사용자에게 보여줄 최종 답변 | final_output |
| 전체 로컬 대본과 함께 재생 가능한 다음 턴 입력 목록 | to_input_list() |
| 에이전트·도구·handoff·승인 메타데이터가 담긴 풍부한 run item | new_items |
| 보통 다음 사용자 턴을 처리해야 하는 에이전트 | last_agent |
previous_response_id로 OpenAI Responses API 체이닝 |
last_response_id |
| 대기 중인 승인과 재개 가능한 스냅샷 | interruptions, to_state() |
현재 중첩 Agent.as_tool() 호출에 관한 메타데이터 |
agent_tool_invocation |
| 원시 모델 호출이나 guardrail 진단 | raw_responses, guardrail 결과 배열 |
최종 출력 (Final output)
final_output 프로퍼티는 마지막으로 실행된 에이전트의 최종 출력을 담아요. 이건 이중 하나예요.
- 마지막 에이전트에
output_type이 정의되지 않았다면str - 마지막 에이전트에 출력 타입이 정의됐다면
last_agent.output_type타입의 객체 - 실행이 최종 출력을 만들기 전에 멈췄다면(예: 승인 interruption에서 일시 중지)
None
Note:
final_output은Any로 타이핑돼요. handoff가 실행을 끝내는 에이전트를 바꿀 수 있으니, SDK가 가능한 출력 타입 전체 집합을 정적으로 알 수 없어요.
스트리밍 모드에서 final_output은 스트림이 처리를 완료할 때까지 None으로 남아요. 이벤트별 흐름은 Streaming을 참고하세요.
입력, 다음 턴 기록, 새 항목
이 표면들은 서로 다른 질문에 답해요.
| 프로퍼티 또는 헬퍼 | 내용 | 가장 적합한 용도 |
|---|---|---|
input |
이 실행 구간의 기본 입력. handoff 입력 필터가 기록을 다시 썼다면 실행이 계속된 필터링된 입력을 반영해요. | 이 실행이 실제로 무엇을 입력으로 썼는지 감사하기 |
to_input_list() |
실행의 입력 항목 뷰. 기본 mode="preserve_all"은 new_items에서 변환된 기록을 유지하되, SDK 기본 중첩 handoff 기록으로 이미 이동된 정확한 세션 항목 발생은 두 번 추가하지 않아요. mode="normalized"는 handoff 필터링이 모델 기록을 다시 쓸 때 정규 연속 입력을 선호해요. |
수동 채팅 루프, 클라이언트 관리 대화 상태, 일반 항목 기록 검사 |
new_items |
에이전트·도구·handoff·승인 메타데이터가 담긴 풍부한 RunItem 래퍼 |
로그, UI, 감사, 디버깅 |
raw_responses |
실행의 각 모델 호출에서 나온 원시 ModelResponse 객체 |
프로바이더 수준 진단이나 원시 응답 검사 |
실제로는:
- 실행의 일반 입력 항목 뷰가 필요하면
to_input_list()를 쓰세요. - handoff 필터링이나 중첩 handoff 기록 재작성 뒤 다음
Runner.run(..., input=...)호출에 쓸 정규 로컬 입력이 필요하면to_input_list(mode="normalized")를 쓰세요. - SDK가 기록을 로드·저장해 주길 원하면
session=...을 쓰세요. - OpenAI 서버 관리 상태를
conversation_id나previous_response_id로 쓰고 있다면 보통 새 사용자 입력만 넘기고to_input_list()를 다시 보내는 대신 저장된 ID를 재사용하세요. - 로그·UI·감사용으로 완전한 변환 기록이 필요하면 기본
to_input_list()모드나new_items를 쓰세요.
SDK 기본 중첩 handoff 기록이 메시지 항목을 그대로 보존할 때, Sessions·RunState·to_input_list()는 내용으로 중복 제거하지 않고 정확히 소유된 발생을 추적해요. 별도로 발생한 동일한 메시지는 별개로 남고, 이미 소유된 발생만 두 번째로 추가되지 않아요.
모델 출력이 재생 가능한 입력으로 변환될 때, to_input_list(), ModelResponse.to_input_items(), 각 RunItemBase.to_input_item() 호출은 프로바이더 출력 전용 created_by 메타데이터를 제거해요. 중첩 shell_call_output 청크의 created_by도 포함돼요. 변환은 영향을 받는 매핑을 재구성하며 원본 원시 항목은 변형하지 않아요.
JavaScript SDK와 달리 Python은 실행 중 새로 생성된 모델 형식 항목만 담은 별도 output 프로퍼티를 노출하지 않아요. SDK 메타데이터가 필요하면 new_items를, 원시 모델 페이로드가 필요하면 raw_responses를 검사하세요.
컴퓨터 도구 항목을 대화 입력으로 재제출하는 것은 원시 Responses 페이로드 형태를 사용해요. Preview 모델 computer_call 항목은 단일 action을 보존하고, gpt-5.5 컴퓨터 호출은 배치된 actions[]를 보존할 수 있어요. to_input_list()와 RunState는 모델이 만든 형태를 유지하므로, 그 항목을 대화 입력으로 수동 재제출·일시중지/재개 흐름·저장된 대본이 preview와 GA 컴퓨터 도구 호출 양쪽에서 계속 동작해요. 로컬 실행 결과는 여전히 new_items에서 computer_call_output 항목으로 나타나요.
새 항목 (New items)
new_items는 실행 동안 무슨 일이 일어났는지 가장 풍부한 뷰를 줘요. 흔한 항목 타입은:
- 재개된 모델 호출 직전
RunState.pending_input에서 받아들인 입력인InputItem - 어시스턴트 메시지용
MessageOutputItem - reasoning 항목용
ReasoningItem - Responses tool search 요청과 로드된 tool-search 결과용
ToolSearchCallItem,ToolSearchOutputItem - 도구 호출과 그 결과용
ToolCallItem,ToolCallOutputItem - 승인을 위해 일시 중지된 도구 호출용
ToolApprovalItem - 호스팅 MCP 승인과 도구 카탈로그용
MCPApprovalRequestItem,MCPApprovalResponseItem,MCPListToolsItem - handoff 요청과 완료된 전송용
HandoffCallItem,HandoffOutputItem
에이전트 연관·도구 출력·handoff 경계·승인 경계가 필요할 때는 to_input_list()보다 new_items를 선택하세요.
호스팅 tool search를 쓸 때, ToolSearchCallItem.raw_item을 검사해 모델이 방출한 검색 요청을 보고, ToolSearchOutputItem.raw_item을 검사해 그 턴에 로드된 네임스페이스·함수·호스팅 MCP 서버를 확인하세요.
Programmatic Tool Calling에서 생성된 프로그램은 ToolCallItem이고, 그 프로그램이 소유한 일반 자식 도구 호출도 ToolCallItem 항목이며, 일치하는 program_output은 ToolCallOutputItem이에요. 프로그램이 소유한 호스팅 MCP mcp_approval_request·mcp_list_tools 항목은 예외인데, MCPApprovalRequestItem·MCPListToolsItem 항목이 돼요.
원시 항목은 타이핑된 Responses 객체일 수도 있고 매핑일 수도 있어요. 특히 프로그램이 소유한 shell·apply-patch 호출은 매핑을 사용해요. 매핑 안전 검사 패턴을 쓰세요.
from collections.abc import Mapping
def raw_field(item, name):
raw_item = item.raw_item
if isinstance(raw_item, Mapping):
return raw_item.get(name)
return getattr(raw_item, name, None)
raw_type = raw_field(item, "type")
caller = raw_field(item, "caller")
caller_id = (
caller.get("caller_id")
if isinstance(caller, Mapping)
else getattr(caller, "caller_id", None)
)
프로그램이 소유한 자식 호출에서 caller의 type 필드는 program이고, caller_id가 부모 프로그램 호출을 식별해요.
대화 계속 또는 재개
다음 턴 에이전트
last_agent에는 마지막으로 실행된 에이전트가 들어 있어요. handoff 이후 다음 사용자 턴에 재사용하기 좋은 에이전트인 경우가 많아요. 스트리밍 모드에서 RunResultStreaming.current_agent는 실행이 진행되면서 갱신되므로, 스트림이 끝나기 전에 handoff를 관찰할 수 있어요.
Interruption과 실행 상태
도구가 승인을 요구하면 대기 중인 승인이 RunResult.interruptions나 RunResultStreaming.interruptions에 노출돼요. 직접 도구, handoff 뒤에 도달한 도구, 중첩 Agent.as_tool() 실행이 일으킨 승인이 모두 포함될 수 있어요.
재개 가능한 RunState를 잡으려면 to_state()를 호출하고, 대기 중인 항목을 승인·거부한 뒤 Runner.run(...)이나 Runner.run_streamed(...)로 재개하세요.
from agents import Agent, Runner
agent = Agent(name="Assistant", instructions="Use tools when needed.")
result = await Runner.run(agent, "Delete temp files that are no longer needed.")
if result.interruptions:
state = result.to_state()
for interruption in result.interruptions:
state.approve(interruption)
result = await Runner.run(agent, state)
ToolCallOutputItem 출력이 Pydantic 모델이나 dataclass라면 RunState는 그 출력을 구조화된 데이터로 직렬화해요. RunState는 딕셔너리·리스트·튜플을 탐색하며 그 컨테이너 안에서 만난 Pydantic 모델이나 dataclass도 변환해요. JSON 왕복 후 튜플은 리스트로 복원돼요. 다른 JSON 비호환 값은 문자열 표현으로 폴백할 수 있으니, 정확한 커스텀 타입이 직렬화를 견뎌야 한다면 명시적으로 JSON 호환 데이터를 반환하세요.
실패한 재개 Session 쓰기 복구
재개된 실행이 승인된 도구 작업을 완료하고도 여전히 다른 모델 호출로 이어질 수 있어요(같은 모델 응답 안의 handoff 포함). 그리고 클라이언트 관리 Session에 완료된 도구 호출과 출력을 쓰는 중에 실패할 수 있어요. 같은 RunState를 유지하거나 직렬화·복원한 뒤, 원래 Session 백엔드와 session_id로 Runner.run(...)이나 Runner.run_streamed(...)를 재시도하세요. 이후 모델 호출 전에 SDK가 대기 중인 배치를 Session 기록과 조정(reconcile)해요. Session이 완전한 배치를 커밋했는데 승인(acknowledgment)만 실패했다면, SDK가 정확한 기록 꼬리를 인식하고 배치를 다시 추가하지 않아요. 쓰기가 커밋되지 않았다면 SDK가 추가를 재시도해요. SDK는 완료된 도구·도구 guardrail·훅·handoff를 다시 실행하지 않아요. 재개된 실행은 완료된 handoff가 선택한 에이전트로 계속돼요.
Session 기록이 정확히 일치하지 않으면 복구는 fail-closed로 끝나요. 원래 Session 백엔드와 session_id를 쓰고 그 기록에 재개 실행이 독점적으로 접근하게 하세요. 다른 작성자가 기록 꼬리를 바꾸거나, 대기 중인 배치의 일부만 있거나, 기록이 모호하면 SDK는 다음 모델 호출 전에 UserError를 발생시켜요. 재개 전에 원래 Session 기록을 복구하고, 완료된 작업을 다시 실행하지 마세요. 대기 중인 배치·선택된 에이전트·누적된 도구 guardrail 결과는 복구가 끝나기 전에 나중 승인 interruption을 포함해 RunState JSON·문자열 왕복을 견뎌요. stream_events()가 Session 쓰기 오류를 발생시킨 뒤에도 RunResultStreaming.to_state()는 같은 복구 데이터를 유지하는 분리된 상태를 돌려줘요.
이 복구는 실행이 최종 출력을 받아들이고, 출력 guardrail과 터미널 훅을 완료하고, 그 마지막 턴을 영속화하는 데 실패한 뒤에는 적용되지 않아요. 그 상태를 재생하면 터미널 lifecycle 효과가 반복될 수 있으니, SDK는 RunState를 복구 불가능으로 표시해요. 이후 그 상태로 Runner.run(...)·Runner.run_streamed(...) 시도는 Session 조정·샌드박스 준비·모델 호출·도구·guardrail·훅 전에 UserError를 발생시켜요. 이 표시는 RunState 직렬화를 견뎌요. 그 상태를 재시도하는 대신 새 실행을 시작하세요. 이 경계는 터미널 function-tool 출력과 기록에 받아들여진 max_turns 핸들러 출력에도 적용돼요.
재개 전에 입력 추가하기
실행이 완료된 턴 뒤에 일시 중지되거나 멈추었는데, 아직 미완료 실행이 다음 모델 호출에 도달하기 전에 새 사용자 입력이 도착한다면 RunState.add_input()을 쓰세요. 문자열은 사용자 메시지가 되고, 여러 호출은 삽입 순서를 보존해요. 스테이징된 입력은 직렬화된 RunState의 일부라서, to_json() / from_json()과 to_string() / from_string() 왕복을 견뎌요.
state = result.to_state()
state.add_input("Also keep the generated report in the project folder.")
for interruption in state.get_interruptions():
state.approve(interruption)
result = await Runner.run(agent, state)
재개 시 러너는 현재 에이전트의 입력 guardrail과 RunConfig의 입력 guardrail을 스테이징된 입력에만 적용해요. 클라이언트 관리 Session이 구성되면 러너는 받아들인 스테이징 입력을 지속적(durable) InputItem으로 변환하고 모델 요청 전에 세션 쓰기를 기다려요. 클라이언트 관리 세션이나 서버 관리 대화가 없으면 러너는 모델 요청 전에 받아들인 스테이징 입력을 InputItem으로 변환해요. 서버 관리 대화의 경우 입력은 서버 요청이 받아들일 때까지 pending으로 남아요. 직렬화·재개·재생 안전 재시도를 가로질러 SDK는 하나의 지속 InputItem 발생을 보존해요. 이 SDK 발생 보장은 프로바이더 전달 보장이 아니에요. 재시도 정책이 요청이 프로바이더에 도달했을 수 있는 뒤에 RetryDecision(approve_unsafe_replay=True)를 반환하면 러너는 스테이징 입력을 재전송하고 프로바이더 측 작업이 반복될 수 있어요. 성공적으로 받아들여진 입력은 new_items에 InputItem으로 나타나요. 분리된 복사본은 RunState.pending_input을 읽고, 재개 전에 스테이징 입력을 모두 버리려면 RunState.clear_pending_input()을 호출하세요.
RunState.add_input()은 터미널 상태, 남은 모델 턴이 없는 상태, 받아들인 모델 응답이 로컬 처리를 기다리는 상태, 대기 중인 도구 결과가 다음 모델 호출 전에 실행을 끝낼 수도 있는 interrupted 상태를 거부해요. 그런 경우 현재 실행을 끝내고 새 사용자 턴을 시작하세요.
스트리밍 실행에서는 먼저 stream_events() 소비를 끝내고, 그다음 result.interruptions를 검사하고 result.to_state()에서 재개하세요. 전체 승인 흐름은 Human-in-the-loop를 참고하세요.
서버 관리 연속화
last_response_id는 실행의 최신 모델 응답 ID예요. 다음 턴에 previous_response_id로 되돌려 전달하면 OpenAI Responses API 체인을 계속할 수 있어요.
이미 to_input_list(), session, conversation_id로 대화를 계속하고 있다면 보통 last_response_id는 필요 없어요. 다단계 실행의 모든 모델 응답이 필요하면 대신 raw_responses를 검사하세요.
에이전트-as-도구 메타데이터
결과가 중첩 Agent.as_tool() 실행에서 나오면 agent_tool_invocation은 둘러싸는 Agent.as_tool() 호출에 대한 불변(immutable) 메타데이터를 노출해요.
tool_nametool_call_idtool_arguments
일반 최상위 실행에서는 agent_tool_invocation이 None이에요. 이건 특히 custom_output_extractor 안에서 유용한데, 중첩 결과를 후처리하면서 둘러싸는 Agent.as_tool() 호출의 도구 이름·호출 ID·원시 인자가 필요할 수 있거든요. 둘러싸는 Agent.as_tool() 패턴은 Tools를 참고하세요.
그 중첩 실행의 파싱된 구조화 입력도 필요하다면 context_wrapper.tool_input을 읽으세요. RunState는 중첩 도구 입력에 대해 이 필드를 일반적으로 직렬화하고, agent_tool_invocation은 결과에 직접 현재 중첩 호출의 메타데이터를 노출해요.
스트리밍 lifecycle과 진단
RunResultStreaming은 위의 결과 표면을 상속받지만 스트리밍 전용 컨트롤을 추가해요.
- 의미론적 스트림 이벤트를 소비하는
stream_events() - 실행 중 활성 에이전트를 추적하는
current_agent - 스트리밍 실행이 완전히 끝났는지 보는
is_complete - 지금 바로 또는 현재 턴 뒤에 실행을 멈추는
cancel(...)
async 반복자가 끝날 때까지 stream_events()를 계속 소비하세요. 스트리밍 실행은 그 반복자가 끝나야 완료되고, final_output, interruptions, raw_responses, 세션 영속화 부수 효과 같은 요약 프로퍼티는 마지막 보이는 토큰이 도착한 뒤에도 아직 정리 중일 수 있어요.
cancel()을 호출했다면 stream_events()를 계속 소비해서 취소와 정리가 올바르게 끝나게 하세요. Python은 별도의 스트리밍 완료 promise나 오류 프로퍼티를 노출하지 않아요. 실행을 종료하는 스트리밍 실패는 stream_events()가 발생시키고, is_complete는 실행이 터미널 상태에 도달했는지 반영해요.
원시 응답 (Raw responses)
raw_responses에는 실행 중 수집된 원시 모델 응답이 들어 있어요. 다단계 실행은 handoff 사이거나 반복된 모델/도구/모델 주기 등에서 응답을 하나 이상 만들 수 있어요. last_response_id는 raw_responses의 마지막 항목의 ID일 뿐이에요.
각 ModelResponse는 그 개별 모델 호출에 적용되는 두 가지 진단도 노출해요.
request_id— 모델 어댑터와 transport가 하나를 전파할 때의 transport 요청 ID. 내장OpenAIResponsesModel과OpenAIChatCompletionsModel은 HTTP·SSE transport 경로에서 사용 가능한 서버 생성x-request-id를 전파해요. 구성된 엔드포인트가 OpenAI API일 때 프로덕션에서None이 아닌 값을 기록해 실패를 OpenAI 지원과 연관지을 수 있어요. OpenAI 호환 프로바이더나 프록시라면 그 서비스의 지원 채널을 쓰세요.OpenAIResponsesWSModel은 현재request_id를None으로 남겨요. 서드파티 어댑터는 요청 ID 전파를 보장하지 않아요. AnyLLM Chat Completions 어댑터와LitellmModel은 현재request_id를None으로 남겨요. Agents SDK AnyLLM Responses 어댑터도 transport 요청 ID를 보존하지 않고 프로바이더 응답을 정규화할 때request_id를None으로 남길 수 있어요.raw_usage— Agents SDK가 페이로드를 정규화하기 전 프로바이더 사용량 페이로드의 opt-in JSON 호환 스냅샷.ModelSettings(preserve_raw_usage=True)로 켜요. 자세한 내용은 Preserving provider usage payloads를 참고하세요.
ModelResponse.request_id와 ModelResponse.raw_usage는 각각 None일 수 있으니, 대화 상태가 아니라 선택적 진단으로 취급하세요.
Guardrail 결과
에이전트 수준 guardrail은 input_guardrail_results와 output_guardrail_results로 노출돼요. 도구 guardrail은 tool_input_guardrail_results와 tool_output_guardrail_results로 별도 노출돼요. 이 배열들은 실행 내내 축적되므로, 결정 기록·추가 guardrail 메타데이터 저장·실행이 왜 막혔는지 디버깅하는 데 유용해요.
에이전트 수준 출력 guardrail이 터미널 function tool이 직접 만든 최종 출력을 막으면 한 가지 편집(redaction) 규칙이 적용돼요. 막힌 현재 응답에 대해 output_guardrail_results는 거부된 에이전트 출력을 교체하고 페이로드를 담은 출력 메타데이터를 지우며, tool_output_guardrail_results는 페이로드를 담은 도구 메타데이터를 교체해요. 이전에 받아들여진 결과는 그대로 남아요. 정리된 출력-guardrail 결과는 OutputGuardrailTripwireTriggered에서 guardrail_result로 노출돼요. 정리된 출력-guardrail 결과와 도구 출력-guardrail 결과는 스트리밍된 결과 상태와 RunState를 통해서도 노출돼요. 자세한 내용은 Output guardrails를 참고하세요.
컨텍스트와 사용량
context_wrapper는 앱 컨텍스트를 SDK 관리 런타임 메타데이터(승인·사용량·중첩 tool_input 등)와 함께 노출해요. 사용량은 context_wrapper.usage로 추적돼요. 스트리밍 실행에서는 스트림의 마지막 청크가 처리될 때까지 사용량 합계가 지연될 수 있어요. 전체 래퍼 형태와 영속화 주의 사항은 Context management를 참고하세요.
더 알아보기 (Learn more)
- OpenAI Agents SDK 문서에서 더 많은 가이드를 확인하세요.