Context management

Context management (컨텍스트 관리)

Context라는 단어는 참 다양한 뜻으로 쓰여요. 여기서 관심 가질 컨텍스트 종류는 크게 둘로 나뉘죠.

  • 코드에 로컬로 제공되는 컨텍스트 — 도구 함수가 실행될 때, on_handoff 같은 콜백에서, lifecycle 훅에서 필요한 데이터와 의존성
  • LLM에 제공되는 컨텍스트 — 모델이 응답을 생성할 때 보는 데이터

출처: 문서

본문

로컬 컨텍스트

로컬 컨텍스트는 RunContextWrapper 클래스와 그 안의 context 프로퍼티로 표현돼요. 동작 방식은 이렇습니다.

  • 원하는 어떤 Python 객체든 만들면 돼요. 흔히 dataclass나 Pydantic 객체를 써요.
  • 그 객체를 여러 run 메서드에 넘겨요(예: Runner.run(..., context=whatever)).
  • 모든 도구 호출·lifecycle 훅 등에는 래퍼 객체 RunContextWrapper[T]가 전달되는데, 여기서 T는 컨텍스트 객체의 타입이에요. 객체 자체는 wrapper.context로 접근할 수 있어요.

일부 런타임별 콜백에서는 SDK가 RunContextWrapper[T]의 더 특화된 서브클래스를 넘길 수 있어요. 예를 들어 FunctionTool 인스턴스의 lifecycle 훅은 보통 ToolContext를 받는데, 이건 tool_call_id, tool_name, tool_arguments 같은 도구 호출 메타데이터도 노출해요.

가장 중요하게 기억할 점: 주어진 에이전트 실행에 대해 모든 에이전트·도구 함수·lifecycle 등은 같은 타입의 컨텍스트를 사용해야 해요.

컨텍스트는 이런 용도로 쓸 수 있어요.

  • 실행의 컨텍스트 데이터(예: 사용자 이름/uid나 사용자에 대한 기타 정보)
  • 의존성(예: 로거 객체, 데이터 페처 등)
  • 헬퍼 함수

Note: 컨텍스트 객체는 LLM에 보내지지 않아요. 순전히 로컬 객체라서 읽고·쓰고·메서드를 호출할 수 있을 뿐이에요.

한 실행 안에서 파생된 래퍼들은 같은 기본 앱 컨텍스트·승인 상태·사용량 추적을 공유해요. 중첩된 Agent.as_tool() 실행은 다른 tool_input을 붙일 수 있지만, 기본적으로 앱 상태의 격리된 복사본을 받지는 않아요.

기능 노출을 위해 로컬 컨텍스트 쓰기

function tool·MCP 도구·handoff가 같은 요청 정책에 의존한다면, 정책 입력이나 헬퍼를 애플리케이션 컨텍스트에 두세요. 각 SDK 표면은 자기만의 콜백으로 현재 실행 컨텍스트를 노출해요.

  • FunctionTool.is_enabledRunContextWrapper를 받아요.
  • Handoff.is_enabledRunContextWrapper를 받아요.
  • MCP tool_filterToolFilterContext를 받는데, 그 run_context 프로퍼티에 현재 RunContextWrapper가 있어요.

별도의 기능 목록을 유지하는 대신 공유 애플리케이션 정책을 이 콜백들에 맞추세요. 콜백은 SDK가 현재 실행에 노출할 기능을 제어할 뿐, 모델이 생성한 인자나 리소스 선택을 승인할 수는 없어요. function tool의 경우 그 판단은 도구 구현 안에서, 또는 적절할 때 도구 입력 guardrail·승인으로 강제하세요. MCP 서버는 자기 보호 작업을 직접 승인해야 해요. input_type이 있는 handoff라면 앱 부수 효과 전에 on_handoff 시작 부분에서 파싱된 입력을 검사하고, 승인 실패 시 반환 대신 예외를 던지세요. 도구 입력 guardrail은 handoff에 대해 실행되지 않아요. 콜백 lifecycle은 handoff inputs를 참고하세요.

RunContextWrapper가 노출하는 것

RunContextWrapper는 앱이 정의한 컨텍스트 객체를 감싸는 래퍼예요. 실제로는 대부분 이걸 씁니다.

  • wrapper.context — 내가 정의한 가변(mutable) 앱 상태와 의존성
  • wrapper.usage — 현재 실행 전체에 걸친 집계 요청·토큰 사용량
  • wrapper.tool_input — 현재 실행이 Agent.as_tool() 안에서 돌고 있을 때의 구조화된 입력
  • wrapper.approve_tool(...) / wrapper.reject_tool(...) — 승인 상태를 프로그래밍 방식으로 갱신해야 할 때

wrapper.context만이 내가 정의한 객체예요. 나머지 필드는 SDK가 관리하는 런타임 메타데이터입니다.

나중에 human-in-the-loop나 지속 작업(durable job) 워크플로를 위해 RunState를 직렬화한다면, 그 런타임 메타데이터도 상태에 함께 저장돼요. 직렬화된 상태를 영속하거나 전송할 예정이라면 RunContextWrapper.context에 비밀을 넣지 마세요.

대화 상태는 별개의 관심사예요. 턴을 어떻게 이어갈지에 따라 result.to_input_list(), session, conversation_id, previous_response_id 중에서 고르세요. 그 판단은 results, running agents, sessions를 참고하세요.

import asyncio
from dataclasses import dataclass

from agents import Agent, RunContextWrapper, Runner
from agents.decorators import tool

@dataclass
class UserInfo:  # (1)!
    name: str
    uid: int

@tool
async def fetch_user_age(wrapper: RunContextWrapper[UserInfo]) -> str:  # (2)!
    """Fetch the age of the user. Call this function to get user's age information."""
    return f"The user {wrapper.context.name} is 47 years old"

async def main():
    user_info = UserInfo(name="John", uid=123)

    agent = Agent[UserInfo](  # (3)!
        name="Assistant",
        tools=[fetch_user_age],
    )

    result = await Runner.run(  # (4)!
        starting_agent=agent,
        input="What is the age of the user?",
        context=user_info,
    )

    print(result.final_output)  # (5)!
    # The user John is 47 years old.

if __name__ == "__main__":
    asyncio.run(main())
  • 이것이 컨텍스트 객체예요. 여기서는 dataclass를 썼지만 아무 타입이나 쓸 수 있어요.
  • 이것이 도구예요. RunContextWrapper[UserInfo]를 받는 걸 볼 수 있어요. 도구 구현은 컨텍스트에서 값을 읽어요.
  • 에이전트를 제네릭 UserInfo로 표시해서 타입체커가 오류를 잡게 해요(예: 다른 컨텍스트 타입을 받는 도구를 넘기려 하면).
  • 컨텍스트를 run 함수에 전달해요.
  • 에이전트가 도구를 올바르게 호출해 나이를 얻어요.

고급: ToolContext

경우에 따라 실행 중인 도구의 추가 메타데이터 — 이름, 호출 ID, 원시 인자 문자열 같은 것 — 에 접근하고 싶을 수 있어요. 이럴 땐 RunContextWrapper를 확장하는 ToolContext 클래스를 쓰면 돼요.

from typing import Annotated
from pydantic import BaseModel, Field
from agents import Agent
from agents.decorators import tool
from agents.tool_context import ToolContext

class WeatherContext(BaseModel):
    user_id: str

class Weather(BaseModel):
    city: str = Field(description="The city name")
    temperature_range: str = Field(description="The temperature range in Celsius")
    conditions: str = Field(description="The weather conditions")

@tool
def get_weather(ctx: ToolContext[WeatherContext], city: Annotated[str, "The city to get the weather for"]) -> Weather:
    print(f"[debug] Tool context: (name: {ctx.tool_name}, call_id: {ctx.tool_call_id}, args: {ctx.tool_arguments})")
    return Weather(city=city, temperature_range="14-20C", conditions="Sunny with wind.")

agent = Agent(
    name="Weather Agent",
    instructions="You are a helpful agent that can tell the weather of a given city.",
    tools=[get_weather],
)

ToolContextRunContextWrapper와 같은 .context 프로퍼티에 더해 현재 도구 호출 특유의 필드를 추가로 제공해요.

  • tool_name — 호출 중인 도구의 이름
  • tool_call_id — 이 도구 호출의 고유 식별자
  • tool_arguments — 도구에 전달된 원시 인자 문자열
  • tool_namespace — 도구가 tool_namespace()나 다른 네임스페이스 표면으로 로드됐을 때 도구 호출의 Responses 네임스페이스
  • qualified_tool_name — 네임스페이스가 있을 때 네임스페이스로 한정된 도구 이름

실행 중 도구 수준 메타데이터가 필요할 때 ToolContext를 쓰세요. 에이전트와 도구 사이의 일반 컨텍스트 공유라면 RunContextWrapper로 충분해요. ToolContextRunContextWrapper를 확장하므로, 중첩된 Agent.as_tool() 실행이 구조화된 입력을 제공했다면 .tool_input도 노출할 수 있어요.

에이전트/LLM 컨텍스트

LLM이 호출될 때 볼 수 있는 데이터는 대화 기록뿐이에요. 즉 새 데이터를 LLM에 제공하고 싶다면 그 데이터가 그 기록에 들어가도록 만들어야 해요. 방법은 몇 가지가 있어요.

  • 에이전트 instructions에 추가 — 흔히 "system prompt"나 "developer message"라고 불러요. system prompt는 정적 문자열일 수도, 컨텍스트를 받아 문자열을 출력하는 동적 함수일 수도 있어요. 항상 유용한 정보(예: 사용자 이름, 현재 날짜)에 흔히 쓰는 전략이에요.
  • Runner.run 함수 호출 시 input에 추가 — instructions 전략과 비슷하지만, 명령 체계상 더 낮은 위치의 메시지를 넣을 수 있어요.
  • FunctionTool 인스턴스로 노출 — 주문형(on-demand) 컨텍스트에 유용해요. LLM이 데이터가 필요하다고 판단할 때 도구를 호출해서 가져오면 되죠.
  • retrieval 또는 web search 사용 — 파일이나 데이터베이스(retrieval), 웹(web search)에서 관련 데이터를 가져오는 특수 도구예요. 응답을 관련 컨텍스트 데이터에 "근거 지우는"(grounding) 데 유용해요.

더 알아보기 (Learn more)