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_enabled는RunContextWrapper를 받아요.Handoff.is_enabled는RunContextWrapper를 받아요.- MCP
tool_filter는ToolFilterContext를 받는데, 그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],
)
ToolContext는 RunContextWrapper와 같은 .context 프로퍼티에 더해 현재 도구 호출 특유의 필드를 추가로 제공해요.
tool_name— 호출 중인 도구의 이름tool_call_id— 이 도구 호출의 고유 식별자tool_arguments— 도구에 전달된 원시 인자 문자열tool_namespace— 도구가tool_namespace()나 다른 네임스페이스 표면으로 로드됐을 때 도구 호출의 Responses 네임스페이스qualified_tool_name— 네임스페이스가 있을 때 네임스페이스로 한정된 도구 이름
실행 중 도구 수준 메타데이터가 필요할 때 ToolContext를 쓰세요. 에이전트와 도구 사이의 일반 컨텍스트 공유라면 RunContextWrapper로 충분해요. ToolContext는 RunContextWrapper를 확장하므로, 중첩된 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)
- OpenAI Agents SDK 문서에서 다른 가이드를 확인하세요.