chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,428 @@
|
||||
---
|
||||
search:
|
||||
exclude: true
|
||||
---
|
||||
# 에이전트
|
||||
|
||||
에이전트는 앱의 핵심 구성 요소입니다. 에이전트는 instructions, tools, 그리고 핸드오프, 가드레일, structured outputs 같은 선택적 런타임 동작으로 구성된 대규모 언어 모델(LLM)입니다.
|
||||
|
||||
하나의 일반 `Agent`를 정의하거나 사용자 지정하려면 이 페이지를 사용하세요. 여러 에이전트가 협업하는 방식을 결정하려면 [에이전트 오케스트레이션](multi_agent.md)을 읽어보세요. 에이전트가 매니페스트에 정의된 파일과 샌드박스 네이티브 기능을 갖춘 격리된 워크스페이스 내에서 실행되어야 한다면 [샌드박스 에이전트 개념](sandbox/guide.md)을 읽어보세요.
|
||||
|
||||
SDK는 OpenAI 모델에 기본적으로 Responses API를 사용하지만, 여기서 중요한 차이는 오케스트레이션입니다. `Agent`와 `Runner`를 함께 사용하면 SDK가 턴, 도구, 가드레일, 핸드오프, 세션을 대신 관리합니다. 이 루프를 직접 관리하려면 Responses API를 직접 사용하세요.
|
||||
|
||||
## 다음 가이드 선택
|
||||
|
||||
이 페이지를 에이전트 정의를 위한 허브로 사용하세요. 다음으로 내려야 할 결정에 해당하는 인접 가이드로 이동하세요.
|
||||
|
||||
| 원하는 작업 | 다음으로 읽을 문서 |
|
||||
| --- | --- |
|
||||
| 모델 또는 제공자 설정 선택 | [모델](models/index.md) |
|
||||
| 에이전트에 기능 추가 | [도구](tools.md) |
|
||||
| 실제 저장소, 문서 번들 또는 격리된 워크스페이스에서 에이전트 실행 | [샌드박스 에이전트 빠른 시작](sandbox_agents.md) |
|
||||
| 관리자 방식 오케스트레이션과 핸드오프 중 선택 | [에이전트 오케스트레이션](multi_agent.md) |
|
||||
| 핸드오프 동작 구성 | [핸드오프](handoffs.md) |
|
||||
| 턴 실행, 이벤트 스트리밍 또는 대화 상태 관리 | [에이전트 실행](running_agents.md) |
|
||||
| 최종 출력, 실행 항목 또는 재개 가능한 상태 검사 | [결과](results.md) |
|
||||
| 로컬 종속성과 런타임 상태 공유 | [컨텍스트 관리](context.md) |
|
||||
|
||||
## 기본 구성
|
||||
|
||||
에이전트의 가장 일반적인 속성은 다음과 같습니다.
|
||||
|
||||
| 속성 | 필수 여부 | 설명 |
|
||||
| --- | --- | --- |
|
||||
| `name` | 예 | 사람이 읽을 수 있는 에이전트 이름입니다. |
|
||||
| `instructions` | 아니요 | 시스템 프롬프트 또는 동적 instructions 콜백입니다. 사용을 강력히 권장합니다. [동적 instructions](#dynamic-instructions)를 참조하세요. |
|
||||
| `prompt` | 아니요 | OpenAI Responses API 프롬프트 구성입니다. 정적 프롬프트 객체 또는 함수를 허용합니다. [프롬프트 템플릿](#prompt-templates)을 참조하세요. |
|
||||
| `handoff_description` | 아니요 | 이 에이전트가 핸드오프 대상으로 제공될 때 노출되는 간단한 설명입니다. |
|
||||
| `handoffs` | 아니요 | 대화를 전문 에이전트에게 위임합니다. [핸드오프](handoffs.md)를 참조하세요. |
|
||||
| `model` | 아니요 | 사용할 LLM입니다. [모델](models/index.md)을 참조하세요. |
|
||||
| `model_settings` | 아니요 | `temperature`, `top_p`, `tool_choice` 같은 모델 조정 매개변수입니다. |
|
||||
| `tools` | 아니요 | 에이전트가 호출할 수 있는 도구입니다. [도구](tools.md)를 참조하세요. |
|
||||
| `mcp_servers` | 아니요 | 에이전트용 MCP 기반 도구입니다. [MCP 가이드](mcp.md)를 참조하세요. |
|
||||
| `mcp_config` | 아니요 | 엄격한 스키마 변환과 MCP 실패 형식 지정 등 MCP 도구가 준비되는 방식을 세부 조정합니다. [MCP 가이드](mcp.md#agent-level-mcp-configuration)를 참조하세요. |
|
||||
| `input_guardrails` | 아니요 | 이 에이전트 체인의 첫 번째 사용자 입력에 대해 실행되는 가드레일입니다. [가드레일](guardrails.md)을 참조하세요. |
|
||||
| `output_guardrails` | 아니요 | 이 에이전트의 최종 출력에 대해 실행되는 가드레일입니다. [가드레일](guardrails.md)을 참조하세요. |
|
||||
| `output_type` | 아니요 | 일반 텍스트 대신 사용하는 structured outputs 타입입니다. [출력 타입](#output-types)을 참조하세요. |
|
||||
| `hooks` | 아니요 | 에이전트 범위의 수명 주기 콜백입니다. [수명 주기 이벤트(훅)](#lifecycle-events-hooks)를 참조하세요. |
|
||||
| `tool_use_behavior` | 아니요 | 도구 결과를 다시 모델로 전달할지, 아니면 실행을 종료할지 제어합니다. [도구 사용 동작](#tool-use-behavior)을 참조하세요. |
|
||||
| `reset_tool_choice` | 아니요 | 도구 사용 루프를 방지하기 위해 도구 호출 후 `tool_choice`를 재설정합니다(기본값: `True`). [도구 사용 강제](#forcing-tool-use)를 참조하세요. |
|
||||
|
||||
```python
|
||||
from agents import Agent, ModelSettings, function_tool
|
||||
|
||||
@function_tool
|
||||
def get_weather(city: str) -> str:
|
||||
"""returns weather info for the specified city."""
|
||||
return f"The weather in {city} is sunny"
|
||||
|
||||
agent = Agent(
|
||||
name="Haiku agent",
|
||||
instructions="Always respond in haiku form",
|
||||
model="gpt-5-nano",
|
||||
tools=[get_weather],
|
||||
)
|
||||
```
|
||||
|
||||
이 섹션의 모든 내용은 `Agent`에 적용됩니다. `SandboxAgent`는 동일한 개념을 기반으로 하며, 워크스페이스 범위 실행을 위한 `default_manifest`, `base_instructions`, `capabilities`, `run_as`를 추가합니다. [샌드박스 에이전트 개념](sandbox/guide.md)을 참조하세요.
|
||||
|
||||
## 프롬프트 템플릿
|
||||
|
||||
`prompt`를 설정하여 OpenAI 플랫폼에서 생성한 프롬프트 템플릿을 참조할 수 있습니다. 이 기능은 Responses API를 사용하는 OpenAI 모델에서 작동합니다.
|
||||
|
||||
사용 방법은 다음과 같습니다.
|
||||
|
||||
1. https://platform.openai.com/playground/prompts 로 이동합니다
|
||||
2. 새 프롬프트 변수 `poem_style`을 생성합니다
|
||||
3. 다음 내용으로 시스템 프롬프트를 생성합니다.
|
||||
|
||||
```
|
||||
Write a poem in {{poem_style}}
|
||||
```
|
||||
|
||||
4. `--prompt-id` 플래그를 사용하여 예제를 실행합니다.
|
||||
|
||||
```python
|
||||
from agents import Agent
|
||||
|
||||
agent = Agent(
|
||||
name="Prompted assistant",
|
||||
prompt={
|
||||
"id": "pmpt_123",
|
||||
"version": "1",
|
||||
"variables": {"poem_style": "haiku"},
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||
런타임에 프롬프트를 동적으로 생성할 수도 있습니다.
|
||||
|
||||
```python
|
||||
from dataclasses import dataclass
|
||||
|
||||
from agents import Agent, GenerateDynamicPromptData, Runner
|
||||
|
||||
@dataclass
|
||||
class PromptContext:
|
||||
prompt_id: str
|
||||
poem_style: str
|
||||
|
||||
|
||||
async def build_prompt(data: GenerateDynamicPromptData):
|
||||
ctx: PromptContext = data.context.context
|
||||
return {
|
||||
"id": ctx.prompt_id,
|
||||
"version": "1",
|
||||
"variables": {"poem_style": ctx.poem_style},
|
||||
}
|
||||
|
||||
|
||||
agent = Agent(name="Prompted assistant", prompt=build_prompt)
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
"Say hello",
|
||||
context=PromptContext(prompt_id="pmpt_123", poem_style="limerick"),
|
||||
)
|
||||
```
|
||||
|
||||
## 컨텍스트
|
||||
|
||||
에이전트는 `context` 타입에 대해 제네릭입니다. 컨텍스트는 종속성 주입 도구입니다. 직접 객체를 생성해 `Runner.run()`에 전달하면 모든 에이전트, 도구, 핸드오프 등에 전달되며, 에이전트 실행에 필요한 종속성과 상태를 담는 컨테이너 역할을 합니다. 어떤 Python 객체든 컨텍스트로 제공할 수 있습니다.
|
||||
|
||||
전체 `RunContextWrapper` 인터페이스, 공유 사용량 추적, 중첩된 `tool_input`, 직렬화 시 주의 사항은 [컨텍스트 가이드](context.md)를 참조하세요.
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class UserContext:
|
||||
name: str
|
||||
uid: str
|
||||
is_pro_user: bool
|
||||
|
||||
async def fetch_purchases() -> list[Purchase]:
|
||||
return ...
|
||||
|
||||
agent = Agent[UserContext](
|
||||
...,
|
||||
)
|
||||
```
|
||||
|
||||
## 출력 타입
|
||||
|
||||
기본적으로 에이전트는 일반 텍스트(즉, `str`) 출력을 생성합니다. 에이전트가 특정 타입의 출력을 생성하도록 하려면 `output_type` 매개변수를 사용할 수 있습니다. 일반적으로 [Pydantic](https://docs.pydantic.dev/) 객체를 사용하지만, Pydantic [TypeAdapter](https://docs.pydantic.dev/latest/api/type_adapter/)로 래핑할 수 있는 모든 타입(dataclass, 리스트, TypedDict 등)을 지원합니다.
|
||||
|
||||
```python
|
||||
from pydantic import BaseModel
|
||||
from agents import Agent
|
||||
|
||||
|
||||
class CalendarEvent(BaseModel):
|
||||
name: str
|
||||
date: str
|
||||
participants: list[str]
|
||||
|
||||
agent = Agent(
|
||||
name="Calendar extractor",
|
||||
instructions="Extract calendar events from text",
|
||||
output_type=CalendarEvent,
|
||||
)
|
||||
```
|
||||
|
||||
!!! note
|
||||
|
||||
`output_type`을 전달하면 모델은 일반적인 일반 텍스트 응답 대신 [structured outputs](https://platform.openai.com/docs/guides/structured-outputs)을 사용합니다.
|
||||
|
||||
## 멀티 에이전트 시스템 설계 패턴
|
||||
|
||||
멀티 에이전트 시스템을 설계하는 방법은 다양하지만, 일반적으로 폭넓게 적용할 수 있는 다음 두 가지 패턴이 사용됩니다.
|
||||
|
||||
1. 관리자(agents as tools): 중앙 관리자/오케스트레이터가 전문화된 하위 에이전트를 도구로 호출하고 대화에 대한 제어권을 유지합니다.
|
||||
2. 핸드오프: 동등한 에이전트가 대화 제어권을 전문 에이전트에게 넘기고, 해당 에이전트가 대화를 이어받습니다. 이는 분산형 방식입니다.
|
||||
|
||||
자세한 내용은 [에이전트 구축 실전 가이드](https://cdn.openai.com/business-guides-and-resources/a-practical-guide-to-building-agents.pdf)를 참조하세요.
|
||||
|
||||
### 관리자(agents as tools)
|
||||
|
||||
`customer_facing_agent`는 모든 사용자 상호작용을 처리하고 도구로 노출된 전문화된 하위 에이전트를 호출합니다. 자세한 내용은 [도구](tools.md#agents-as-tools) 문서를 참조하세요.
|
||||
|
||||
```python
|
||||
from agents import Agent
|
||||
|
||||
booking_agent = Agent(...)
|
||||
refund_agent = Agent(...)
|
||||
|
||||
customer_facing_agent = Agent(
|
||||
name="Customer-facing agent",
|
||||
instructions=(
|
||||
"Handle all direct user communication. "
|
||||
"Call the relevant tools when specialized expertise is needed."
|
||||
),
|
||||
tools=[
|
||||
booking_agent.as_tool(
|
||||
tool_name="booking_expert",
|
||||
tool_description="Handles booking questions and requests.",
|
||||
),
|
||||
refund_agent.as_tool(
|
||||
tool_name="refund_expert",
|
||||
tool_description="Handles refund questions and requests.",
|
||||
)
|
||||
],
|
||||
)
|
||||
```
|
||||
|
||||
### 핸드오프
|
||||
|
||||
핸드오프는 에이전트가 작업을 위임할 수 있는 하위 에이전트입니다. 핸드오프가 발생하면 위임받은 에이전트가 대화 기록을 전달받고 대화를 이어받습니다. 이 패턴을 사용하면 하나의 작업에 특화된 모듈식 전문 에이전트를 구성할 수 있습니다. 자세한 내용은 [핸드오프](handoffs.md) 문서를 참조하세요.
|
||||
|
||||
```python
|
||||
from agents import Agent
|
||||
|
||||
booking_agent = Agent(...)
|
||||
refund_agent = Agent(...)
|
||||
|
||||
triage_agent = Agent(
|
||||
name="Triage agent",
|
||||
instructions=(
|
||||
"Help the user with their questions. "
|
||||
"If they ask about booking, hand off to the booking agent. "
|
||||
"If they ask about refunds, hand off to the refund agent."
|
||||
),
|
||||
handoffs=[booking_agent, refund_agent],
|
||||
)
|
||||
```
|
||||
|
||||
## 동적 instructions
|
||||
|
||||
대부분의 경우 에이전트를 생성할 때 instructions를 제공할 수 있습니다. 하지만 함수를 통해 동적 instructions를 제공할 수도 있습니다. 함수는 에이전트와 컨텍스트를 전달받아 프롬프트를 반환해야 합니다. 일반 함수와 `async` 함수가 모두 허용됩니다.
|
||||
|
||||
```python
|
||||
def dynamic_instructions(
|
||||
context: RunContextWrapper[UserContext], agent: Agent[UserContext]
|
||||
) -> str:
|
||||
return f"The user's name is {context.context.name}. Help them with their questions."
|
||||
|
||||
|
||||
agent = Agent[UserContext](
|
||||
name="Triage agent",
|
||||
instructions=dynamic_instructions,
|
||||
)
|
||||
```
|
||||
|
||||
## 수명 주기 이벤트(훅)
|
||||
|
||||
경우에 따라 에이전트의 수명 주기를 관찰해야 할 수 있습니다. 예를 들어 특정 이벤트가 발생할 때 이벤트를 기록하거나, 데이터를 미리 가져오거나, 사용량을 기록할 수 있습니다.
|
||||
|
||||
훅의 범위는 두 가지입니다.
|
||||
|
||||
- [`RunHooks`][agents.lifecycle.RunHooks]는 다른 에이전트로의 핸드오프를 포함해 전체 `Runner.run(...)` 호출을 관찰합니다.
|
||||
- [`AgentHooks`][agents.lifecycle.AgentHooks]는 `agent.hooks`를 통해 특정 에이전트 인스턴스에 연결됩니다.
|
||||
|
||||
콜백 컨텍스트도 이벤트에 따라 달라집니다.
|
||||
|
||||
- 에이전트 시작/종료 훅은 [`AgentHookContext`][agents.run_context.AgentHookContext]를 전달받습니다. 이 컨텍스트는 원래 컨텍스트를 래핑하며 공유 실행 사용량 상태를 포함합니다.
|
||||
- LLM, 도구, 핸드오프 훅은 [`RunContextWrapper`][agents.run_context.RunContextWrapper]를 전달받습니다.
|
||||
|
||||
일반적인 훅 실행 시점은 다음과 같습니다.
|
||||
|
||||
- `on_agent_start` / `on_agent_end`: 특정 에이전트가 최종 출력 생성을 시작하거나 완료할 때
|
||||
- `on_llm_start` / `on_llm_end`: 각 모델 호출 직전과 직후
|
||||
- `on_tool_start` / `on_tool_end`: 각 로컬 도구 호출 전후. 함수 도구의 경우 훅 `context`는 일반적으로 `ToolContext`이므로 `tool_call_id` 같은 도구 호출 메타데이터를 검사할 수 있습니다.
|
||||
- `on_handoff`: 제어권이 한 에이전트에서 다른 에이전트로 이동할 때
|
||||
|
||||
전체 워크플로를 위한 단일 관찰자가 필요하면 `RunHooks`를 사용하고, 특정 에이전트에 사용자 지정 부수 효과가 필요하면 `AgentHooks`를 사용하세요.
|
||||
|
||||
```python
|
||||
from agents import Agent, RunHooks, Runner
|
||||
|
||||
|
||||
class LoggingHooks(RunHooks):
|
||||
async def on_agent_start(self, context, agent):
|
||||
print(f"Starting {agent.name}")
|
||||
|
||||
async def on_llm_end(self, context, agent, response):
|
||||
print(f"{agent.name} produced {len(response.output)} output items")
|
||||
|
||||
async def on_agent_end(self, context, agent, output):
|
||||
print(f"{agent.name} finished with usage: {context.usage}")
|
||||
|
||||
|
||||
agent = Agent(name="Assistant", instructions="Be concise.")
|
||||
result = await Runner.run(agent, "Explain quines", hooks=LoggingHooks())
|
||||
print(result.final_output)
|
||||
```
|
||||
|
||||
전체 콜백 인터페이스는 [수명 주기 API 레퍼런스](ref/lifecycle.md)를 참조하세요.
|
||||
|
||||
## 가드레일
|
||||
|
||||
가드레일을 사용하면 에이전트 실행과 병렬로 사용자 입력에 대한 검사/검증을 실행하고, 에이전트 출력이 생성된 후 해당 출력도 검사할 수 있습니다. 예를 들어 사용자 입력과 에이전트 출력의 관련성을 검사할 수 있습니다. 자세한 내용은 [가드레일](guardrails.md) 문서를 참조하세요.
|
||||
|
||||
## 에이전트 복제/복사
|
||||
|
||||
에이전트의 `clone()` 메서드를 사용하면 에이전트를 복제하고 원하는 속성을 선택적으로 변경할 수 있습니다.
|
||||
|
||||
```python
|
||||
pirate_agent = Agent(
|
||||
name="Pirate",
|
||||
instructions="Write like a pirate",
|
||||
model="gpt-5.6-sol",
|
||||
)
|
||||
|
||||
robot_agent = pirate_agent.clone(
|
||||
name="Robot",
|
||||
instructions="Write like a robot",
|
||||
)
|
||||
```
|
||||
|
||||
## 도구 사용 강제
|
||||
|
||||
도구 목록을 제공한다고 해서 LLM이 항상 도구를 사용하는 것은 아닙니다. [`ModelSettings.tool_choice`][agents.model_settings.ModelSettings.tool_choice]를 설정하여 도구 사용을 강제할 수 있습니다. 유효한 값은 다음과 같습니다.
|
||||
|
||||
1. `auto`: LLM이 도구 사용 여부를 결정할 수 있습니다.
|
||||
2. `required`: LLM이 도구를 반드시 사용해야 합니다. 단, 사용할 도구는 지능적으로 결정할 수 있습니다.
|
||||
3. `none`: LLM이 도구를 _사용하지 않도록_ 합니다.
|
||||
4. `my_tool` 같은 특정 문자열: LLM이 해당 도구를 반드시 사용하도록 합니다.
|
||||
|
||||
OpenAI Responses 도구 검색을 사용할 때는 지정된 도구 선택에 더 많은 제약이 있습니다. `tool_choice`로 네임스페이스 이름 자체나 지연 전용 도구를 지정할 수 없으며, `tool_choice="tool_search"`는 [`ToolSearchTool`][agents.tool.ToolSearchTool]을 대상으로 하지 않습니다. 이런 경우에는 `auto` 또는 `required`를 사용하는 것이 좋습니다. Responses 관련 제약은 [호스티드 툴 검색](tools.md#hosted-tool-search)을 참조하세요.
|
||||
|
||||
```python
|
||||
from agents import Agent, Runner, function_tool, ModelSettings
|
||||
|
||||
@function_tool
|
||||
def get_weather(city: str) -> str:
|
||||
"""Returns weather info for the specified city."""
|
||||
return f"The weather in {city} is sunny"
|
||||
|
||||
agent = Agent(
|
||||
name="Weather Agent",
|
||||
instructions="Retrieve weather details.",
|
||||
tools=[get_weather],
|
||||
model_settings=ModelSettings(tool_choice="get_weather")
|
||||
)
|
||||
```
|
||||
|
||||
## 도구 사용 동작
|
||||
|
||||
`Agent` 구성의 `tool_use_behavior` 매개변수는 도구 출력의 처리 방식을 제어합니다.
|
||||
|
||||
- `"run_llm_again"`: 기본값입니다. 도구를 실행한 후 LLM이 결과를 처리하여 최종 응답을 생성합니다.
|
||||
- `"stop_on_first_tool"`: 추가 LLM 처리 없이 첫 번째 도구 호출의 출력을 최종 응답으로 사용합니다.
|
||||
|
||||
```python
|
||||
from agents import Agent, Runner, function_tool, ModelSettings
|
||||
|
||||
@function_tool
|
||||
def get_weather(city: str) -> str:
|
||||
"""Returns weather info for the specified city."""
|
||||
return f"The weather in {city} is sunny"
|
||||
|
||||
agent = Agent(
|
||||
name="Weather Agent",
|
||||
instructions="Retrieve weather details.",
|
||||
tools=[get_weather],
|
||||
tool_use_behavior="stop_on_first_tool"
|
||||
)
|
||||
```
|
||||
|
||||
- `StopAtTools(stop_at_tool_names=[...])`: 지정된 도구 중 하나라도 호출되면 중지하고 해당 출력을 최종 응답으로 사용합니다.
|
||||
|
||||
```python
|
||||
from agents import Agent, Runner, function_tool
|
||||
from agents.agent import StopAtTools
|
||||
|
||||
@function_tool
|
||||
def get_weather(city: str) -> str:
|
||||
"""Returns weather info for the specified city."""
|
||||
return f"The weather in {city} is sunny"
|
||||
|
||||
@function_tool
|
||||
def sum_numbers(a: int, b: int) -> int:
|
||||
"""Adds two numbers."""
|
||||
return a + b
|
||||
|
||||
agent = Agent(
|
||||
name="Stop At Stock Agent",
|
||||
instructions="Get weather or sum numbers.",
|
||||
tools=[get_weather, sum_numbers],
|
||||
tool_use_behavior=StopAtTools(stop_at_tool_names=["get_weather"])
|
||||
)
|
||||
```
|
||||
|
||||
- `ToolsToFinalOutputFunction`: 도구 결과를 처리하고 LLM을 중지할지 계속 실행할지 결정하는 사용자 지정 함수입니다.
|
||||
|
||||
```python
|
||||
from agents import Agent, Runner, function_tool, FunctionToolResult, RunContextWrapper
|
||||
from agents.agent import ToolsToFinalOutputResult
|
||||
from typing import List, Any
|
||||
|
||||
@function_tool
|
||||
def get_weather(city: str) -> str:
|
||||
"""Returns weather info for the specified city."""
|
||||
return f"The weather in {city} is sunny"
|
||||
|
||||
def custom_tool_handler(
|
||||
context: RunContextWrapper[Any],
|
||||
tool_results: List[FunctionToolResult]
|
||||
) -> ToolsToFinalOutputResult:
|
||||
"""Processes tool results to decide final output."""
|
||||
for result in tool_results:
|
||||
if result.output and "sunny" in result.output:
|
||||
return ToolsToFinalOutputResult(
|
||||
is_final_output=True,
|
||||
final_output=f"Final weather: {result.output}"
|
||||
)
|
||||
return ToolsToFinalOutputResult(
|
||||
is_final_output=False,
|
||||
final_output=None
|
||||
)
|
||||
|
||||
agent = Agent(
|
||||
name="Weather Agent",
|
||||
instructions="Retrieve weather details.",
|
||||
tools=[get_weather],
|
||||
tool_use_behavior=custom_tool_handler
|
||||
)
|
||||
```
|
||||
|
||||
!!! note
|
||||
|
||||
무한 루프를 방지하기 위해 프레임워크는 도구 호출 후 `tool_choice`를 자동으로 "auto"로 재설정합니다. 이 동작은 [`agent.reset_tool_choice`][agents.agent.Agent.reset_tool_choice]를 통해 구성할 수 있습니다. 무한 루프가 발생하는 이유는 도구 결과가 LLM으로 전송된 후 `tool_choice`로 인해 LLM이 다시 도구 호출을 생성하며 이 과정이 끝없이 반복되기 때문입니다.
|
||||
@@ -0,0 +1,205 @@
|
||||
---
|
||||
search:
|
||||
exclude: true
|
||||
---
|
||||
# 구성
|
||||
|
||||
이 페이지에서는 애플리케이션 시작 시 일반적으로 한 번 설정하는 SDK 전체 기본값을 다룹니다. 예를 들어 기본 OpenAI 키 또는 클라이언트, 기본 OpenAI API 형태, 트레이싱 내보내기 기본값, 로깅 동작 등이 있습니다.
|
||||
|
||||
이러한 기본값은 샌드박스 기반 워크플로에도 계속 적용되지만, 샌드박스 워크스페이스, 샌드박스 클라이언트, 세션 재사용은 별도로 구성합니다.
|
||||
|
||||
대신 특정 에이전트 또는 실행을 구성해야 한다면 다음부터 시작하세요:
|
||||
|
||||
- [에이전트](agents.md): 일반 `Agent`의 instructions, tools, 출력 유형, 핸드오프, 가드레일
|
||||
- [에이전트 실행](running_agents.md): `RunConfig`, 세션, 대화 상태 옵션
|
||||
- [샌드박스 에이전트](sandbox/guide.md): `SandboxRunConfig`, 매니페스트, 기능, 샌드박스 클라이언트별 워크스페이스 설정
|
||||
- [모델](models/index.md): 모델 선택 및 제공자 구성
|
||||
- [트레이싱](tracing.md): 실행별 트레이싱 메타데이터 및 사용자 지정 트레이스 프로세서
|
||||
|
||||
## API 키 및 클라이언트
|
||||
|
||||
기본적으로 SDK는 LLM 요청과 트레이싱에 `OPENAI_API_KEY` 환경 변수를 사용합니다. 키는 SDK가 처음으로 OpenAI 클라이언트를 생성할 때 확인됩니다(지연 초기화). 따라서 첫 모델 호출 전에 환경 변수를 설정하세요. 앱 시작 전에 해당 환경 변수를 설정할 수 없다면 [set_default_openai_key()][agents.set_default_openai_key] 함수를 사용해 키를 설정할 수 있습니다.
|
||||
|
||||
```python
|
||||
from agents import set_default_openai_key
|
||||
|
||||
set_default_openai_key("sk-...")
|
||||
```
|
||||
|
||||
또는 사용할 OpenAI 클라이언트를 구성할 수도 있습니다. 기본적으로 SDK는 환경 변수의 API 키나 위에서 설정한 기본 키를 사용하여 `AsyncOpenAI` 인스턴스를 생성합니다. [set_default_openai_client()][agents.set_default_openai_client] 함수를 사용해 이를 변경할 수 있습니다.
|
||||
|
||||
```python
|
||||
from openai import AsyncOpenAI
|
||||
from agents import set_default_openai_client
|
||||
|
||||
custom_client = AsyncOpenAI(base_url="...", api_key="...")
|
||||
set_default_openai_client(custom_client)
|
||||
```
|
||||
|
||||
환경 변수 기반 엔드포인트 구성을 선호한다면, 기본 OpenAI 제공자는 `OPENAI_BASE_URL`도 읽습니다. Responses WebSocket 전송을 활성화하면 WebSocket `/responses` 엔드포인트용 `OPENAI_WEBSOCKET_BASE_URL`도 읽습니다.
|
||||
|
||||
```bash
|
||||
export OPENAI_BASE_URL="https://your-openai-compatible-endpoint.example/v1"
|
||||
export OPENAI_WEBSOCKET_BASE_URL="wss://your-openai-compatible-endpoint.example/v1"
|
||||
```
|
||||
|
||||
마지막으로 사용할 OpenAI API를 사용자 지정할 수도 있습니다. 기본적으로 OpenAI Responses API를 사용합니다. [set_default_openai_api()][agents.set_default_openai_api] 함수를 사용해 이를 재정의하여 Chat Completions API를 사용할 수 있습니다.
|
||||
|
||||
```python
|
||||
from agents import set_default_openai_api
|
||||
|
||||
set_default_openai_api("chat_completions")
|
||||
```
|
||||
|
||||
## OpenAI 제공자 기본값
|
||||
|
||||
OpenAI 기반 제공자는 모델 이름을 확인할 때도 SDK 전체 기본값을 읽습니다. OpenAI Responses 모델이 기본적으로 WebSocket 전송을 사용하도록 하려면 [`set_default_openai_responses_transport()`][agents.set_default_openai_responses_transport]를 사용하세요:
|
||||
|
||||
```python
|
||||
from agents import set_default_openai_responses_transport
|
||||
|
||||
set_default_openai_responses_transport("websocket")
|
||||
```
|
||||
|
||||
이는 기본 OpenAI 제공자가 확인한 OpenAI Responses 모델에 영향을 줍니다. 제공자 수준 설정, 연결 재사용, keepalive 옵션, 사용자 지정 WebSocket 엔드포인트는 [Responses WebSocket 전송](models/index.md#responses-websocket-transport)을 참조하세요.
|
||||
|
||||
OpenAI 설정에서 제공자 수준 에이전트 등록 메타데이터를 기대하는 경우, 시작 시 기본 harness ID를 한 번 구성하세요:
|
||||
|
||||
```python
|
||||
from agents import set_default_openai_harness
|
||||
|
||||
set_default_openai_harness("your-harness-id")
|
||||
```
|
||||
|
||||
전체 등록 객체를 전달할 수도 있습니다:
|
||||
|
||||
```python
|
||||
from agents import OpenAIAgentRegistrationConfig, set_default_openai_agent_registration
|
||||
|
||||
set_default_openai_agent_registration(
|
||||
OpenAIAgentRegistrationConfig(harness_id="your-harness-id")
|
||||
)
|
||||
```
|
||||
|
||||
SDK 기본값이 설정되어 있지 않으면 OpenAI 기반 제공자는 `OPENAI_AGENT_HARNESS_ID` 환경 변수로 폴백합니다. harness ID가 구성되어 있으면, SDK는 `RunConfig.trace_metadata`에 해당 키가 이미 있는 경우를 제외하고 이를 `agent_harness_id`로 트레이스 메타데이터에 추가합니다.
|
||||
|
||||
## 트레이싱
|
||||
|
||||
트레이싱은 기본적으로 활성화되어 있습니다. 기본적으로 위 섹션의 모델 요청과 동일한 OpenAI API 키(즉, 환경 변수 또는 설정한 기본 키)를 사용합니다. [`set_tracing_export_api_key`][agents.set_tracing_export_api_key] 함수를 사용하여 트레이싱에 사용할 API 키를 별도로 설정할 수 있습니다.
|
||||
|
||||
```python
|
||||
from agents import set_tracing_export_api_key
|
||||
|
||||
set_tracing_export_api_key("sk-...")
|
||||
```
|
||||
|
||||
모델 트래픽에는 한 키나 클라이언트를 사용하지만 트레이싱에는 다른 OpenAI 키를 사용해야 한다면, 기본 키나 클라이언트를 설정할 때 `use_for_tracing=False`를 전달한 다음 트레이싱을 별도로 구성하세요. 사용자 지정 클라이언트를 사용하지 않는 경우 [`set_default_openai_key()`][agents.set_default_openai_key]에서도 동일한 패턴을 사용할 수 있습니다.
|
||||
|
||||
```python
|
||||
from openai import AsyncOpenAI
|
||||
from agents import (
|
||||
set_default_openai_client,
|
||||
set_tracing_export_api_key,
|
||||
)
|
||||
|
||||
custom_client = AsyncOpenAI(base_url="https://your-openai-compatible-endpoint.example/v1", api_key="provider-key")
|
||||
set_default_openai_client(custom_client, use_for_tracing=False)
|
||||
|
||||
set_tracing_export_api_key("sk-tracing")
|
||||
```
|
||||
|
||||
기본 익스포터를 사용할 때 트레이스를 특정 조직 또는 프로젝트에 귀속해야 한다면 앱 시작 전에 다음 환경 변수를 설정하세요:
|
||||
|
||||
```bash
|
||||
export OPENAI_ORG_ID="org_..."
|
||||
export OPENAI_PROJECT_ID="proj_..."
|
||||
```
|
||||
|
||||
전역 익스포터를 변경하지 않고 실행별로 트레이싱 API 키를 설정할 수도 있습니다.
|
||||
|
||||
```python
|
||||
from agents import Runner, RunConfig
|
||||
|
||||
await Runner.run(
|
||||
agent,
|
||||
input="Hello",
|
||||
run_config=RunConfig(tracing={"api_key": "sk-tracing-123"}),
|
||||
)
|
||||
```
|
||||
|
||||
[`set_tracing_disabled()`][agents.set_tracing_disabled] 함수를 사용하여 트레이싱을 완전히 비활성화할 수도 있습니다.
|
||||
|
||||
```python
|
||||
from agents import set_tracing_disabled
|
||||
|
||||
set_tracing_disabled(True)
|
||||
```
|
||||
|
||||
트레이싱은 활성화한 상태로 유지하되 트레이스 페이로드에서 민감할 수 있는 입력/출력을 제외하려면 [`RunConfig.trace_include_sensitive_data`][agents.run.RunConfig.trace_include_sensitive_data]를 `False`로 설정하세요:
|
||||
|
||||
```python
|
||||
from agents import Runner, RunConfig
|
||||
|
||||
await Runner.run(
|
||||
agent,
|
||||
input="Hello",
|
||||
run_config=RunConfig(trace_include_sensitive_data=False),
|
||||
)
|
||||
```
|
||||
|
||||
앱 시작 전에 다음 환경 변수를 설정하면 코드 없이도 기본값을 변경할 수 있습니다:
|
||||
|
||||
```bash
|
||||
export OPENAI_AGENTS_TRACE_INCLUDE_SENSITIVE_DATA=0
|
||||
```
|
||||
|
||||
전체 트레이싱 제어 옵션은 [트레이싱 가이드](tracing.md)를 참조하세요.
|
||||
|
||||
## 디버그 로깅
|
||||
|
||||
SDK는 두 개의 Python 로거(`openai.agents` 및 `openai.agents.tracing`)를 정의하며, 기본적으로 핸들러를 연결하지 않습니다. 로그는 애플리케이션의 Python 로깅 구성을 따릅니다.
|
||||
|
||||
상세 로깅을 활성화하려면 [`enable_verbose_stdout_logging()`][agents.enable_verbose_stdout_logging] 함수를 사용하세요.
|
||||
|
||||
```python
|
||||
from agents import enable_verbose_stdout_logging
|
||||
|
||||
enable_verbose_stdout_logging()
|
||||
```
|
||||
|
||||
또는 핸들러, 필터, 포매터 등을 추가하여 로그를 사용자 지정할 수 있습니다. 자세한 내용은 [Python 로깅 가이드](https://docs.python.org/3/howto/logging.html)를 참조하세요.
|
||||
|
||||
```python
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger("openai.agents") # or openai.agents.tracing for the Tracing logger
|
||||
|
||||
# To make all logs show up
|
||||
logger.setLevel(logging.DEBUG)
|
||||
# To make info and above show up
|
||||
logger.setLevel(logging.INFO)
|
||||
# To make warning and above show up
|
||||
logger.setLevel(logging.WARNING)
|
||||
# etc
|
||||
|
||||
# You can customize this as needed, but this will output to `stderr` by default
|
||||
logger.addHandler(logging.StreamHandler())
|
||||
```
|
||||
|
||||
### 로그의 민감한 데이터
|
||||
|
||||
일부 로그에는 민감한 데이터(예: 사용자 데이터)가 포함될 수 있습니다.
|
||||
|
||||
기본적으로 SDK는 LLM 입력/출력이나 도구 입력/출력을 로그로 기록하지 **않습니다**. 이러한 보호 기능은 다음으로 제어됩니다:
|
||||
|
||||
```bash
|
||||
OPENAI_AGENTS_DONT_LOG_MODEL_DATA=1
|
||||
OPENAI_AGENTS_DONT_LOG_TOOL_DATA=1
|
||||
```
|
||||
|
||||
디버깅을 위해 이 데이터를 일시적으로 포함해야 한다면, 앱 시작 전에 둘 중 하나의 변수를 `0`(또는 `false`)으로 설정하세요:
|
||||
|
||||
```bash
|
||||
export OPENAI_AGENTS_DONT_LOG_MODEL_DATA=0
|
||||
export OPENAI_AGENTS_DONT_LOG_TOOL_DATA=0
|
||||
```
|
||||
@@ -0,0 +1,148 @@
|
||||
---
|
||||
search:
|
||||
exclude: true
|
||||
---
|
||||
# 컨텍스트 관리
|
||||
|
||||
컨텍스트는 여러 의미로 쓰이는 용어입니다. 주로 고려해야 할 컨텍스트에는 두 가지 주요 유형이 있습니다.
|
||||
|
||||
1. 코드에서 로컬로 사용할 수 있는 컨텍스트: 도구 함수가 실행될 때, `on_handoff` 같은 콜백 중에, 생명주기 훅 등에서 필요할 수 있는 데이터와 의존성입니다.
|
||||
2. LLM이 사용할 수 있는 컨텍스트: LLM이 응답을 생성할 때 보는 데이터입니다.
|
||||
|
||||
## 로컬 컨텍스트
|
||||
|
||||
이는 [`RunContextWrapper`][agents.run_context.RunContextWrapper] 클래스와 그 안의 [`context`][agents.run_context.RunContextWrapper.context] 속성으로 표현됩니다. 작동 방식은 다음과 같습니다.
|
||||
|
||||
1. 원하는 Python 객체를 만듭니다. 일반적인 패턴은 dataclass나 Pydantic 객체를 사용하는 것입니다.
|
||||
2. 해당 객체를 다양한 run 메서드에 전달합니다(예: `Runner.run(..., context=whatever)`).
|
||||
3. 모든 도구 호출, 생명주기 훅 등에는 래퍼 객체인 `RunContextWrapper[T]`가 전달되며, 여기서 `T`는 `wrapper.context`를 통해 접근할 수 있는 컨텍스트 객체 타입을 나타냅니다.
|
||||
|
||||
일부 런타임별 콜백의 경우 SDK가 `RunContextWrapper[T]`의 더 특화된 서브클래스를 전달할 수 있습니다. 예를 들어 함수 도구 생명주기 훅은 일반적으로 `ToolContext`를 받으며, 이는 `tool_call_id`, `tool_name`, `tool_arguments` 같은 도구 호출 메타데이터도 노출합니다.
|
||||
|
||||
알아두어야 할 **가장 중요한** 점은 특정 에이전트 실행에 포함되는 모든 에이전트, 도구 함수, 생명주기 등은 동일한 컨텍스트 _타입_을 사용해야 한다는 것입니다.
|
||||
|
||||
컨텍스트는 다음과 같은 용도로 사용할 수 있습니다.
|
||||
|
||||
- 실행을 위한 컨텍스트 데이터(예: 사용자 이름/uid 또는 사용자에 대한 기타 정보)
|
||||
- 의존성(예: 로거 객체, 데이터 페처 등)
|
||||
- 헬퍼 함수
|
||||
|
||||
!!! danger "참고"
|
||||
|
||||
컨텍스트 객체는 LLM으로 전송되지 **않습니다**. 이는 오직 로컬 객체이며, 읽고 쓰거나 해당 객체의 메서드를 호출할 수 있습니다.
|
||||
|
||||
단일 실행 내에서 파생된 래퍼들은 동일한 기본 앱 컨텍스트, 승인 상태, 사용량 추적을 공유합니다. 중첩된 [`Agent.as_tool()`][agents.agent.Agent.as_tool] 실행은 다른 `tool_input`을 붙일 수 있지만, 기본적으로 앱 상태의 격리된 복사본을 받지는 않습니다.
|
||||
|
||||
### `RunContextWrapper`의 노출 항목
|
||||
|
||||
[`RunContextWrapper`][agents.run_context.RunContextWrapper]는 앱에서 정의한 컨텍스트 객체를 감싸는 래퍼입니다. 실제로는 대부분 다음을 사용하게 됩니다.
|
||||
|
||||
- [`wrapper.context`][agents.run_context.RunContextWrapper.context]: 직접 사용하는 변경 가능한 앱 상태와 의존성
|
||||
- [`wrapper.usage`][agents.run_context.RunContextWrapper.usage]: 현재 실행 전반의 집계된 요청 및 토큰 사용량
|
||||
- [`wrapper.tool_input`][agents.run_context.RunContextWrapper.tool_input]: 현재 실행이 [`Agent.as_tool()`][agents.agent.Agent.as_tool] 안에서 실행 중일 때의 구조화된 입력
|
||||
- [`wrapper.approve_tool(...)`][agents.run_context.RunContextWrapper.approve_tool] / [`wrapper.reject_tool(...)`][agents.run_context.RunContextWrapper.reject_tool]: 승인 상태를 프로그래밍 방식으로 업데이트해야 할 때 사용
|
||||
|
||||
`wrapper.context`만 앱에서 정의한 객체입니다. 다른 필드는 SDK가 관리하는 런타임 메타데이터입니다.
|
||||
|
||||
나중에 휴먼인더루프 (HITL) 또는 내구성 있는 작업 워크플로를 위해 [`RunState`][agents.run_state.RunState]를 직렬화하면, 해당 런타임 메타데이터가 상태와 함께 저장됩니다. 직렬화된 상태를 영속화하거나 전송할 계획이라면 [`RunContextWrapper.context`][agents.run_context.RunContextWrapper.context]에 비밀 정보를 넣지 마세요.
|
||||
|
||||
대화 상태는 별개의 문제입니다. 턴을 이어가는 방식에 따라 `result.to_input_list()`, `session`, `conversation_id`, 또는 `previous_response_id`를 사용하세요. 이 결정에 대해서는 [결과](results.md), [에이전트 실행](running_agents.md), [세션](sessions/index.md)을 참고하세요.
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
from dataclasses import dataclass
|
||||
|
||||
from agents import Agent, RunContextWrapper, Runner, function_tool
|
||||
|
||||
@dataclass
|
||||
class UserInfo: # (1)!
|
||||
name: str
|
||||
uid: int
|
||||
|
||||
@function_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())
|
||||
```
|
||||
|
||||
1. 이것이 컨텍스트 객체입니다. 여기서는 dataclass를 사용했지만, 어떤 타입이든 사용할 수 있습니다.
|
||||
2. 이것은 도구입니다. `RunContextWrapper[UserInfo]`를 받는 것을 볼 수 있습니다. 도구 구현은 컨텍스트에서 읽습니다.
|
||||
3. 에이전트에 제네릭 `UserInfo`를 표시하여, 타입 검사기가 오류를 잡을 수 있도록 합니다(예를 들어 다른 컨텍스트 타입을 받는 도구를 전달하려고 한 경우).
|
||||
4. 컨텍스트가 `run` 함수에 전달됩니다.
|
||||
5. 에이전트가 도구를 올바르게 호출하고 나이를 가져옵니다.
|
||||
|
||||
---
|
||||
|
||||
### 고급: `ToolContext`
|
||||
|
||||
어떤 경우에는 실행 중인 도구에 대한 추가 메타데이터(예: 이름, 호출 ID, 원문 인수 문자열)에 접근하고 싶을 수 있습니다.
|
||||
이를 위해 `RunContextWrapper`를 확장하는 [`ToolContext`][agents.tool_context.ToolContext] 클래스를 사용할 수 있습니다.
|
||||
|
||||
```python
|
||||
from typing import Annotated
|
||||
from pydantic import BaseModel, Field
|
||||
from agents import Agent, Runner, function_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")
|
||||
|
||||
@function_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이 볼 수 있는 **유일한** 데이터는 대화 기록에 있는 데이터입니다. 즉, LLM이 어떤 새 데이터를 사용할 수 있게 하려면 해당 기록에서 사용할 수 있는 방식으로 제공해야 합니다. 이를 수행하는 방법은 몇 가지가 있습니다.
|
||||
|
||||
1. Agent `instructions`에 추가할 수 있습니다. 이는 "시스템 프롬프트" 또는 "개발자 메시지"라고도 합니다. 시스템 프롬프트는 정적 문자열일 수도 있고, 컨텍스트를 받아 문자열을 출력하는 동적 함수일 수도 있습니다. 이는 항상 유용한 정보(예: 사용자의 이름 또는 현재 날짜)에 흔히 사용하는 전략입니다.
|
||||
2. `Runner.run` 함수를 호출할 때 `input`에 추가합니다. 이는 `instructions` 전략과 유사하지만, [명령 체계](https://cdn.openai.com/spec/model-spec-2024-05-08.html#follow-the-chain-of-command)에서 더 낮은 위치의 메시지를 사용할 수 있게 해줍니다.
|
||||
3. 함수 도구를 통해 노출합니다. 이는 _온디맨드_ 컨텍스트에 유용합니다. LLM이 어떤 데이터가 필요한 시점을 결정하고, 해당 데이터를 가져오기 위해 도구를 호출할 수 있습니다.
|
||||
4. 검색 또는 웹 검색을 사용합니다. 이는 파일이나 데이터베이스에서 관련 데이터를 가져올 수 있는 특수 도구(검색) 또는 웹에서 가져올 수 있는 특수 도구(웹 검색)입니다. 이는 응답을 관련 컨텍스트 데이터에 "근거화"하는 데 유용합니다.
|
||||
@@ -0,0 +1,136 @@
|
||||
---
|
||||
search:
|
||||
exclude: true
|
||||
---
|
||||
# 코드 예제
|
||||
|
||||
[리포지토리](https://github.com/openai/openai-agents-python/tree/main/examples)의 코드 예제 섹션에서 SDK의 다양한 샘플 구현을 확인해 보세요. 코드 예제는 다양한 패턴과 기능을 보여 주는 여러 카테고리로 구성되어 있습니다.
|
||||
|
||||
## 카테고리
|
||||
|
||||
- **[agent_patterns](https://github.com/openai/openai-agents-python/tree/main/examples/agent_patterns):** 이 카테고리의 코드 예제는 다음과 같은 일반적인 에이전트 설계 패턴을 보여 줍니다
|
||||
|
||||
- 결정론적 워크플로
|
||||
- Agents as tools
|
||||
- 스트리밍 이벤트를 사용하는 Agents as tools (`examples/agent_patterns/agents_as_tools_streaming.py`)
|
||||
- 구조화된 입력 매개변수를 사용하는 Agents as tools (`examples/agent_patterns/agents_as_tools_structured.py`)
|
||||
- 병렬 에이전트 실행
|
||||
- 조건부 도구 사용
|
||||
- 다양한 동작으로 도구 사용 강제 (`examples/agent_patterns/forcing_tool_use.py`)
|
||||
- 입력/출력 가드레일
|
||||
- 판정자로서의 LLM
|
||||
- 라우팅
|
||||
- 스트리밍 가드레일
|
||||
- 도구 승인 및 상태 직렬화를 사용하는 휴먼인더루프 (HITL) (`examples/agent_patterns/human_in_the_loop.py`)
|
||||
- 스트리밍을 사용하는 휴먼인더루프 (HITL) (`examples/agent_patterns/human_in_the_loop_stream.py`)
|
||||
- 승인 흐름을 위한 사용자 정의 거부 메시지 (`examples/agent_patterns/human_in_the_loop_custom_rejection.py`)
|
||||
|
||||
- **[basic](https://github.com/openai/openai-agents-python/tree/main/examples/basic):** 이 코드 예제는 다음과 같은 SDK의 기본 기능을 보여 줍니다
|
||||
|
||||
- Hello world 코드 예제(기본 모델, GPT-5, 오픈 웨이트 모델)
|
||||
- 에이전트 수명 주기 관리
|
||||
- 실행 훅 및 에이전트 훅 수명 주기 코드 예제 (`examples/basic/lifecycle_example.py`)
|
||||
- 동적 시스템 프롬프트
|
||||
- 기본적인 도구 사용 (`examples/basic/tools.py`)
|
||||
- 도구 입력/출력 가드레일 (`examples/basic/tool_guardrails.py`)
|
||||
- 이미지 도구 출력 (`examples/basic/image_tool_output.py`)
|
||||
- 스트리밍 출력(텍스트, 항목, 함수 호출 인수)
|
||||
- 여러 턴에서 공유 세션 헬퍼를 사용하는 Responses WebSocket 전송 (`examples/basic/stream_ws.py`)
|
||||
- 프롬프트 템플릿
|
||||
- 파일 처리(로컬 및 원격, 이미지 및 PDF)
|
||||
- 사용량 추적
|
||||
- Runner가 관리하는 재시도 설정 (`examples/basic/retry.py`)
|
||||
- 서드 파티 어댑터를 통해 Runner가 관리하는 재시도 (`examples/basic/retry_litellm.py`)
|
||||
- 비엄격 출력 유형
|
||||
- 이전 응답 ID 사용
|
||||
|
||||
- **[customer_service](https://github.com/openai/openai-agents-python/tree/main/examples/customer_service):** 항공사를 위한 고객 서비스 시스템 코드 예제입니다.
|
||||
|
||||
- **[financial_research_agent](https://github.com/openai/openai-agents-python/tree/main/examples/financial_research_agent):** 금융 데이터 분석을 위한 에이전트와 도구를 사용하여 구조화된 리서치 워크플로를 보여 주는 금융 리서치 에이전트입니다.
|
||||
|
||||
- **[handoffs](https://github.com/openai/openai-agents-python/tree/main/examples/handoffs):** 메시지 필터링을 사용하는 에이전트 핸드오프의 실용적인 코드 예제는 다음과 같습니다:
|
||||
|
||||
- 메시지 필터 코드 예제 (`examples/handoffs/message_filter.py`)
|
||||
- 스트리밍을 사용하는 메시지 필터 (`examples/handoffs/message_filter_streaming.py`)
|
||||
|
||||
- **[hosted_mcp](https://github.com/openai/openai-agents-python/tree/main/examples/hosted_mcp):** OpenAI Responses API에서 호스티드 MCP(Model Context Protocol)를 사용하는 방법을 보여 주는 코드 예제는 다음과 같습니다:
|
||||
|
||||
- 승인이 없는 간단한 호스티드 MCP (`examples/hosted_mcp/simple.py`)
|
||||
- Google Calendar와 같은 MCP 커넥터 (`examples/hosted_mcp/connectors.py`)
|
||||
- 인터럽션(중단 처리) 기반 승인을 사용하는 휴먼인더루프 (HITL) (`examples/hosted_mcp/human_in_the_loop.py`)
|
||||
- MCP 도구 호출을 위한 승인 시 콜백 (`examples/hosted_mcp/on_approval.py`)
|
||||
|
||||
- **[mcp](https://github.com/openai/openai-agents-python/tree/main/examples/mcp):** 다음을 포함하여 MCP(Model Context Protocol)로 에이전트를 구축하는 방법을 알아봅니다:
|
||||
|
||||
- 파일 시스템 코드 예제
|
||||
- Git 코드 예제
|
||||
- MCP 프롬프트 서버 코드 예제
|
||||
- SSE(Server-Sent Events) 코드 예제
|
||||
- SSE 원격 서버 연결 (`examples/mcp/sse_remote_example`)
|
||||
- 스트리밍 가능한 HTTP 코드 예제
|
||||
- 스트리밍 가능한 HTTP 원격 연결 (`examples/mcp/streamable_http_remote_example`)
|
||||
- 스트리밍 가능한 HTTP를 위한 사용자 정의 HTTP 클라이언트 팩토리 (`examples/mcp/streamablehttp_custom_client_example`)
|
||||
- `MCPUtil.get_all_function_tools`를 사용하여 모든 MCP 도구 미리 가져오기 (`examples/mcp/get_all_mcp_tools_example`)
|
||||
- FastAPI와 함께 사용하는 MCPServerManager (`examples/mcp/manager_example`)
|
||||
- MCP 도구 필터링 (`examples/mcp/tool_filter_example`)
|
||||
|
||||
- **[memory](https://github.com/openai/openai-agents-python/tree/main/examples/memory):** 에이전트를 위한 다양한 메모리 구현 코드 예제는 다음과 같습니다:
|
||||
|
||||
- SQLite 세션 저장소
|
||||
- 고급 SQLite 세션 저장소
|
||||
- Redis 세션 저장소
|
||||
- SQLAlchemy 세션 저장소
|
||||
- Dapr 상태 저장소 기반 세션 저장소
|
||||
- 암호화된 세션 저장소
|
||||
- OpenAI Conversations 세션 저장소
|
||||
- Responses 압축 세션 저장소
|
||||
- `ModelSettings(store=False)`를 사용하는 무상태 Responses 압축 (`examples/memory/compaction_session_stateless_example.py`)
|
||||
- 파일 기반 세션 저장소 (`examples/memory/file_session.py`)
|
||||
- 휴먼인더루프 (HITL)를 사용하는 파일 기반 세션 (`examples/memory/file_hitl_example.py`)
|
||||
- 휴먼인더루프 (HITL)를 사용하는 SQLite 인메모리 세션 (`examples/memory/memory_session_hitl_example.py`)
|
||||
- 휴먼인더루프 (HITL)를 사용하는 OpenAI Conversations 세션 (`examples/memory/openai_session_hitl_example.py`)
|
||||
- 여러 세션에 걸친 HITL 승인/거부 시나리오 (`examples/memory/hitl_session_scenario.py`)
|
||||
|
||||
- **[model_providers](https://github.com/openai/openai-agents-python/tree/main/examples/model_providers):** 사용자 정의 제공업체와 서드 파티 어댑터를 포함하여 SDK에서 OpenAI 이외의 모델을 사용하는 방법을 살펴봅니다.
|
||||
|
||||
- **[realtime](https://github.com/openai/openai-agents-python/tree/main/examples/realtime):** SDK를 사용하여 실시간 경험을 구축하는 방법을 보여 주는 코드 예제는 다음과 같습니다:
|
||||
|
||||
- 구조화된 텍스트 및 이미지 메시지를 사용하는 웹 애플리케이션 패턴
|
||||
- 명령줄 오디오 루프 및 재생 처리
|
||||
- WebSocket을 통한 Twilio Media Streams 통합
|
||||
- Realtime Calls API 연결 흐름을 사용하는 Twilio SIP 통합
|
||||
|
||||
- **[reasoning_content](https://github.com/openai/openai-agents-python/tree/main/examples/reasoning_content):** 추론 콘텐츠를 사용하는 방법을 보여 주는 코드 예제는 다음과 같습니다:
|
||||
|
||||
- Runner API에서 스트리밍 및 비스트리밍 방식으로 사용하는 추론 콘텐츠 (`examples/reasoning_content/runner_example.py`)
|
||||
- OpenRouter를 통해 OSS 모델에서 사용하는 추론 콘텐츠 (`examples/reasoning_content/gpt_oss_stream.py`)
|
||||
- 기본 추론 콘텐츠 코드 예제 (`examples/reasoning_content/main.py`)
|
||||
|
||||
- **[research_bot](https://github.com/openai/openai-agents-python/tree/main/examples/research_bot):** 복잡한 다중 에이전트 리서치 워크플로를 보여 주는 간단한 딥 리서치 클론입니다.
|
||||
|
||||
- **[sandbox](https://github.com/openai/openai-agents-python/tree/main/examples/sandbox):** 격리된 작업 공간에서 에이전트를 실행하는 코드 예제는 다음과 같습니다:
|
||||
|
||||
- 기본 샌드박스 에이전트 설정 (`examples/sandbox/basic.py`)
|
||||
- Unix 로컬 및 Docker 샌드박스 수명 주기 코드 예제
|
||||
- 샌드박스 기반 핸드오프 (`examples/sandbox/handoffs.py`)
|
||||
- 샌드박스 메모리 및 스냅샷 재개 (`examples/sandbox/memory.py`)
|
||||
- 도구로 노출된 샌드박스 에이전트 (`examples/sandbox/sandbox_agents_as_tools.py`)
|
||||
|
||||
- **[tools](https://github.com/openai/openai-agents-python/tree/main/examples/tools):** 다음과 같은 OpenAI 호스트하는 도구와 실험적 Codex 도구를 구현하는 방법을 알아봅니다:
|
||||
|
||||
- 웹 검색 및 필터를 사용하는 웹 검색
|
||||
- 파일 검색
|
||||
- Code interpreter
|
||||
- 파일 편집 및 승인을 지원하는 패치 적용 도구 (`examples/tools/apply_patch.py`)
|
||||
- 승인 콜백을 사용하는 셸 도구 실행 (`examples/tools/shell.py`)
|
||||
- 휴먼인더루프 (HITL) 인터럽션(중단 처리) 기반 승인을 사용하는 셸 도구 (`examples/tools/shell_human_in_the_loop.py`)
|
||||
- 인라인 스킬을 사용하는 호스티드 컨테이너 셸 (`examples/tools/container_shell_inline_skill.py`)
|
||||
- 스킬 참조를 사용하는 호스티드 컨테이너 셸 (`examples/tools/container_shell_skill_reference.py`)
|
||||
- 로컬 스킬을 사용하는 로컬 셸 (`examples/tools/local_shell_skill.py`)
|
||||
- 네임스페이스 및 지연된 도구를 사용하는 도구 검색 (`examples/tools/tool_search.py`)
|
||||
- 컴퓨터 사용
|
||||
- 이미지 생성
|
||||
- 실험적 Codex 도구 워크플로 (`examples/tools/codex.py`)
|
||||
- 실험적 Codex 동일 스레드 워크플로 (`examples/tools/codex_same_thread.py`)
|
||||
|
||||
- **[voice](https://github.com/openai/openai-agents-python/tree/main/examples/voice):** 스트리밍 음성 코드 예제를 포함하여 TTS 및 STT 모델을 사용하는 음성 에이전트 코드 예제를 살펴봅니다.
|
||||
@@ -0,0 +1,234 @@
|
||||
---
|
||||
search:
|
||||
exclude: true
|
||||
---
|
||||
# 가드레일
|
||||
|
||||
가드레일을 사용하면 사용자 입력과 에이전트 출력에 대한 검사 및 검증을 수행할 수 있습니다. 예를 들어, 고객 요청을 돕기 위해 매우 똑똑한(따라서 느리고 비용이 많이 드는) 모델을 사용하는 에이전트가 있다고 가정해 보겠습니다. 악의적인 사용자가 모델에게 수학 숙제를 도와달라고 요청하는 것은 원치 않을 것입니다. 따라서 빠르고 저렴한 모델로 가드레일을 실행할 수 있습니다. 가드레일이 악의적 사용을 감지하면 즉시 오류를 발생시켜 비용이 많이 드는 모델이 실행되지 않도록 하여 시간과 비용을 절약할 수 있습니다(**차단형 가드레일을 사용할 때입니다. 병렬 가드레일의 경우, 가드레일이 완료되기 전에 비용이 많이 드는 모델이 이미 실행을 시작했을 수 있습니다. 자세한 내용은 아래의 "실행 모드"를 참조하세요**).
|
||||
|
||||
가드레일에는 두 가지 종류가 있습니다:
|
||||
|
||||
1. 입력 가드레일은 최초 사용자 입력에서 실행됩니다
|
||||
2. 출력 가드레일은 최종 에이전트 출력에서 실행됩니다
|
||||
|
||||
## 워크플로 경계
|
||||
|
||||
가드레일은 에이전트와 도구에 연결되지만, 워크플로의 모든 지점에서 실행되는 것은 아닙니다:
|
||||
|
||||
- **입력 가드레일**은 체인의 첫 번째 에이전트에 대해서만 실행됩니다.
|
||||
- **출력 가드레일**은 최종 출력을 생성하는 에이전트에 대해서만 실행됩니다.
|
||||
- **도구 가드레일**은 모든 사용자 지정 함수 도구 호출마다 실행되며, 실행 전에는 입력 가드레일이, 실행 후에는 출력 가드레일이 실행됩니다.
|
||||
|
||||
매니저, 핸드오프 또는 위임된 전문 에이전트를 포함하는 워크플로에서 각 사용자 지정 함수 도구 호출 전후로 검사가 필요하다면, 에이전트 수준의 입력/출력 가드레일에만 의존하지 말고 도구 가드레일을 사용하세요.
|
||||
|
||||
## 입력 가드레일
|
||||
|
||||
입력 가드레일은 3단계로 실행됩니다:
|
||||
|
||||
1. 먼저 가드레일은 에이전트에 전달된 것과 동일한 입력을 받습니다.
|
||||
2. 다음으로 가드레일 함수가 실행되어 [`GuardrailFunctionOutput`][agents.guardrail.GuardrailFunctionOutput]을 생성하고, 이는 다시 [`InputGuardrailResult`][agents.guardrail.InputGuardrailResult]로 래핑됩니다
|
||||
3. 마지막으로 [`.tripwire_triggered`][agents.guardrail.GuardrailFunctionOutput.tripwire_triggered]가 true인지 확인합니다. true이면 [`InputGuardrailTripwireTriggered`][agents.exceptions.InputGuardrailTripwireTriggered] 예외가 발생하므로, 사용자에게 적절히 응답하거나 예외를 처리할 수 있습니다.
|
||||
|
||||
!!! Note
|
||||
|
||||
입력 가드레일은 사용자 입력에서 실행되도록 설계되었으므로, 에이전트의 가드레일은 해당 에이전트가 *첫 번째* 에이전트인 경우에만 실행됩니다. 가드레일을 `Runner.run`에 전달하지 않고 에이전트의 `guardrails` 속성에 두는 이유가 궁금할 수 있습니다. 이는 가드레일이 실제 에이전트와 관련되는 경우가 많기 때문입니다. 에이전트마다 서로 다른 가드레일을 실행하게 되므로, 코드를 한곳에 배치하는 것이 가독성에 유용합니다.
|
||||
|
||||
### 실행 모드
|
||||
|
||||
입력 가드레일은 두 가지 실행 모드를 지원합니다:
|
||||
|
||||
- **병렬 실행**(기본값, `run_in_parallel=True`): 가드레일은 에이전트 실행과 동시에 실행됩니다. 둘 다 같은 시점에 시작하므로 지연 시간이 가장 짧습니다. 그러나 가드레일이 실패하면, 취소되기 전에 에이전트가 이미 토큰을 소비하고 도구를 실행했을 수 있습니다.
|
||||
|
||||
- **차단 실행**(`run_in_parallel=False`): 가드레일은 에이전트가 시작되기 *전에* 실행되어 완료됩니다. 가드레일 트립와이어가 트리거되면 에이전트는 전혀 실행되지 않아 토큰 소비와 도구 실행을 방지합니다. 이는 비용 최적화에 이상적이며 도구 호출의 잠재적 부작용을 피하고 싶을 때 적합합니다.
|
||||
|
||||
## 출력 가드레일
|
||||
|
||||
출력 가드레일은 3단계로 실행됩니다:
|
||||
|
||||
1. 먼저 가드레일은 에이전트가 생성한 출력을 받습니다.
|
||||
2. 다음으로 가드레일 함수가 실행되어 [`GuardrailFunctionOutput`][agents.guardrail.GuardrailFunctionOutput]을 생성하고, 이는 다시 [`OutputGuardrailResult`][agents.guardrail.OutputGuardrailResult]로 래핑됩니다
|
||||
3. 마지막으로 [`.tripwire_triggered`][agents.guardrail.GuardrailFunctionOutput.tripwire_triggered]가 true인지 확인합니다. true이면 [`OutputGuardrailTripwireTriggered`][agents.exceptions.OutputGuardrailTripwireTriggered] 예외가 발생하므로, 사용자에게 적절히 응답하거나 예외를 처리할 수 있습니다.
|
||||
|
||||
!!! Note
|
||||
|
||||
출력 가드레일은 최종 에이전트 출력에서 실행되도록 설계되었으므로, 에이전트의 가드레일은 해당 에이전트가 *마지막* 에이전트인 경우에만 실행됩니다. 입력 가드레일과 마찬가지로, 이는 가드레일이 실제 에이전트와 관련되는 경우가 많기 때문입니다. 에이전트마다 서로 다른 가드레일을 실행하게 되므로, 코드를 한곳에 배치하는 것이 가독성에 유용합니다.
|
||||
|
||||
출력 가드레일은 에이전트가 완료된 후에 항상 실행되므로 `run_in_parallel` 매개변수를 지원하지 않습니다.
|
||||
|
||||
## 도구 가드레일
|
||||
|
||||
도구 가드레일은 **함수 도구**를 감싸며 실행 전후에 도구 호출을 검증하거나 차단할 수 있게 합니다. 도구 자체에 구성되며 해당 도구가 호출될 때마다 실행됩니다.
|
||||
|
||||
- 입력 도구 가드레일은 도구 실행 전에 실행되며 호출을 건너뛰거나, 출력을 메시지로 대체하거나, 트립와이어를 트리거할 수 있습니다.
|
||||
- 출력 도구 가드레일은 도구 실행 후에 실행되며 출력을 대체하거나 트립와이어를 트리거할 수 있습니다.
|
||||
- 함수 도구에 승인이 필요한 경우, 입력 도구 가드레일은 일반적으로 승인 후 실행 직전에 실행됩니다. 해당 입력 검사를 승인 대기 인터럽션(중단 처리)이 발생하기 전에 실행하려면 [`RunConfig.tool_execution`][agents.run.RunConfig.tool_execution]을 [`ToolExecutionConfig(pre_approval_tool_input_guardrails=True)`][agents.run.ToolExecutionConfig]로 설정하세요. 이 승인 전 검사를 통과한 호출도 승인 이후 도구가 실행되기 전에 다시 검사됩니다.
|
||||
- 도구 가드레일은 [`function_tool`][agents.tool.function_tool]로 생성된 함수 도구에만 적용됩니다. 핸드오프는 일반 함수 도구 파이프라인이 아니라 SDK의 핸드오프 파이프라인을 통해 실행되므로, 도구 가드레일은 핸드오프 호출 자체에는 적용되지 않습니다. 호스티드 툴(`WebSearchTool`, `FileSearchTool`, `HostedMCPTool`, `CodeInterpreterTool`, `ImageGenerationTool`)과 기본 제공 실행 도구(`ComputerTool`, `ShellTool`, `ApplyPatchTool`, `LocalShellTool`)도 이 가드레일 파이프라인을 사용하지 않으며, [`Agent.as_tool()`][agents.agent.Agent.as_tool]은 현재 도구 가드레일 옵션을 직접 노출하지 않습니다.
|
||||
|
||||
자세한 내용은 아래 코드 스니펫을 참조하세요.
|
||||
|
||||
## 트립와이어
|
||||
|
||||
입력 또는 출력이 가드레일 검사를 통과하지 못하면, 가드레일은 이를 트립와이어로 신호할 수 있습니다. 트립와이어를 트리거한 가드레일이 확인되는 즉시, `{Input,Output}GuardrailTripwireTriggered` 예외를 발생시키고 에이전트 실행을 중단합니다.
|
||||
|
||||
## 가드레일 구현
|
||||
|
||||
입력을 받아 [`GuardrailFunctionOutput`][agents.guardrail.GuardrailFunctionOutput]을 반환하는 함수를 제공해야 합니다. 이 예제에서는 내부적으로 에이전트를 실행해 이를 수행합니다.
|
||||
|
||||
```python
|
||||
from pydantic import BaseModel
|
||||
from agents import (
|
||||
Agent,
|
||||
GuardrailFunctionOutput,
|
||||
InputGuardrailTripwireTriggered,
|
||||
RunContextWrapper,
|
||||
Runner,
|
||||
TResponseInputItem,
|
||||
input_guardrail,
|
||||
)
|
||||
|
||||
class MathHomeworkOutput(BaseModel):
|
||||
is_math_homework: bool
|
||||
reasoning: str
|
||||
|
||||
guardrail_agent = Agent( # (1)!
|
||||
name="Guardrail check",
|
||||
instructions="Check if the user is asking you to do their math homework.",
|
||||
output_type=MathHomeworkOutput,
|
||||
)
|
||||
|
||||
|
||||
@input_guardrail
|
||||
async def math_guardrail( # (2)!
|
||||
ctx: RunContextWrapper[None], agent: Agent, input: str | list[TResponseInputItem]
|
||||
) -> GuardrailFunctionOutput:
|
||||
result = await Runner.run(guardrail_agent, input, context=ctx.context)
|
||||
|
||||
return GuardrailFunctionOutput(
|
||||
output_info=result.final_output, # (3)!
|
||||
tripwire_triggered=result.final_output.is_math_homework,
|
||||
)
|
||||
|
||||
|
||||
agent = Agent( # (4)!
|
||||
name="Customer support agent",
|
||||
instructions="You are a customer support agent. You help customers with their questions.",
|
||||
input_guardrails=[math_guardrail],
|
||||
)
|
||||
|
||||
async def main():
|
||||
# This should trip the guardrail
|
||||
try:
|
||||
await Runner.run(agent, "Hello, can you help me solve for x: 2x + 3 = 11?")
|
||||
print("Guardrail didn't trip - this is unexpected")
|
||||
|
||||
except InputGuardrailTripwireTriggered:
|
||||
print("Math homework guardrail tripped")
|
||||
```
|
||||
|
||||
1. 이 에이전트를 가드레일 함수에서 사용합니다.
|
||||
2. 이것은 에이전트의 입력/컨텍스트를 받아 결과를 반환하는 가드레일 함수입니다.
|
||||
3. 가드레일 결과에 추가 정보를 포함할 수 있습니다.
|
||||
4. 이것은 워크플로를 정의하는 실제 에이전트입니다.
|
||||
|
||||
출력 가드레일도 유사합니다.
|
||||
|
||||
```python
|
||||
from pydantic import BaseModel
|
||||
from agents import (
|
||||
Agent,
|
||||
GuardrailFunctionOutput,
|
||||
OutputGuardrailTripwireTriggered,
|
||||
RunContextWrapper,
|
||||
Runner,
|
||||
output_guardrail,
|
||||
)
|
||||
class MessageOutput(BaseModel): # (1)!
|
||||
response: str
|
||||
|
||||
class MathOutput(BaseModel): # (2)!
|
||||
reasoning: str
|
||||
is_math: bool
|
||||
|
||||
guardrail_agent = Agent(
|
||||
name="Guardrail check",
|
||||
instructions="Check if the output includes any math.",
|
||||
output_type=MathOutput,
|
||||
)
|
||||
|
||||
@output_guardrail
|
||||
async def math_guardrail( # (3)!
|
||||
ctx: RunContextWrapper, agent: Agent, output: MessageOutput
|
||||
) -> GuardrailFunctionOutput:
|
||||
result = await Runner.run(guardrail_agent, output.response, context=ctx.context)
|
||||
|
||||
return GuardrailFunctionOutput(
|
||||
output_info=result.final_output,
|
||||
tripwire_triggered=result.final_output.is_math,
|
||||
)
|
||||
|
||||
agent = Agent( # (4)!
|
||||
name="Customer support agent",
|
||||
instructions="You are a customer support agent. You help customers with their questions.",
|
||||
output_guardrails=[math_guardrail],
|
||||
output_type=MessageOutput,
|
||||
)
|
||||
|
||||
async def main():
|
||||
# This should trip the guardrail
|
||||
try:
|
||||
await Runner.run(agent, "Hello, can you help me solve for x: 2x + 3 = 11?")
|
||||
print("Guardrail didn't trip - this is unexpected")
|
||||
|
||||
except OutputGuardrailTripwireTriggered:
|
||||
print("Math output guardrail tripped")
|
||||
```
|
||||
|
||||
1. 이것은 실제 에이전트의 출력 타입입니다.
|
||||
2. 이것은 가드레일의 출력 타입입니다.
|
||||
3. 이것은 에이전트의 출력을 받아 결과를 반환하는 가드레일 함수입니다.
|
||||
4. 이것은 워크플로를 정의하는 실제 에이전트입니다.
|
||||
|
||||
마지막으로, 다음은 도구 가드레일의 코드 예제입니다.
|
||||
|
||||
```python
|
||||
import json
|
||||
from agents import (
|
||||
Agent,
|
||||
Runner,
|
||||
ToolGuardrailFunctionOutput,
|
||||
function_tool,
|
||||
tool_input_guardrail,
|
||||
tool_output_guardrail,
|
||||
)
|
||||
|
||||
@tool_input_guardrail
|
||||
def block_secrets(data):
|
||||
args = json.loads(data.context.tool_arguments or "{}")
|
||||
if "sk-" in json.dumps(args):
|
||||
return ToolGuardrailFunctionOutput.reject_content(
|
||||
"Remove secrets before calling this tool."
|
||||
)
|
||||
return ToolGuardrailFunctionOutput.allow()
|
||||
|
||||
|
||||
@tool_output_guardrail
|
||||
def redact_output(data):
|
||||
text = str(data.output or "")
|
||||
if "sk-" in text:
|
||||
return ToolGuardrailFunctionOutput.reject_content("Output contained sensitive data.")
|
||||
return ToolGuardrailFunctionOutput.allow()
|
||||
|
||||
|
||||
@function_tool(
|
||||
tool_input_guardrails=[block_secrets],
|
||||
tool_output_guardrails=[redact_output],
|
||||
)
|
||||
def classify_text(text: str) -> str:
|
||||
"""Classify text for internal routing."""
|
||||
return f"length:{len(text)}"
|
||||
|
||||
|
||||
agent = Agent(name="Classifier", tools=[classify_text])
|
||||
result = Runner.run_sync(agent, "hello world")
|
||||
print(result.final_output)
|
||||
```
|
||||
@@ -0,0 +1,156 @@
|
||||
---
|
||||
search:
|
||||
exclude: true
|
||||
---
|
||||
# 핸드오프
|
||||
|
||||
핸드오프를 사용하면 에이전트가 다른 에이전트에게 작업을 위임할 수 있습니다. 이는 서로 다른 에이전트가 각기 다른 영역을 전문으로 하는 시나리오에서 특히 유용합니다. 예를 들어 고객 지원 앱에는 주문 상태, 환불, FAQ 등의 작업을 각각 전담하는 에이전트가 있을 수 있습니다.
|
||||
|
||||
핸드오프는 LLM에 도구로 표시됩니다. 따라서 `Refund Agent`라는 에이전트로 핸드오프가 있으면 도구는 `transfer_to_refund_agent`라고 호출됩니다.
|
||||
|
||||
## 핸드오프 생성
|
||||
|
||||
모든 에이전트에는 [`handoffs`][agents.agent.Agent.handoffs] 매개변수가 있으며, 이 매개변수는 `Agent`를 직접 받거나 핸드오프를 사용자 지정하는 `Handoff` 객체를 받을 수 있습니다.
|
||||
|
||||
일반 `Agent` 인스턴스를 전달하면 해당 [`handoff_description`][agents.agent.Agent.handoff_description](설정된 경우)이 기본 도구 설명에 추가됩니다. 전체 `handoff()` 객체를 작성하지 않고도 모델이 해당 핸드오프를 선택해야 하는 시점을 힌트로 제공하는 데 사용하세요.
|
||||
|
||||
Agents SDK에서 제공하는 [`handoff()`][agents.handoffs.handoff] 함수를 사용하여 핸드오프를 만들 수 있습니다. 이 함수로 핸드오프할 에이전트와 선택적 재정의 및 입력 필터를 지정할 수 있습니다.
|
||||
|
||||
### 기본 사용법
|
||||
|
||||
간단한 핸드오프를 만드는 방법은 다음과 같습니다:
|
||||
|
||||
```python
|
||||
from agents import Agent, handoff
|
||||
|
||||
billing_agent = Agent(name="Billing agent")
|
||||
refund_agent = Agent(name="Refund agent")
|
||||
|
||||
# (1)!
|
||||
triage_agent = Agent(name="Triage agent", handoffs=[billing_agent, handoff(refund_agent)])
|
||||
```
|
||||
|
||||
1. 에이전트를 직접 사용할 수도 있고(`billing_agent`처럼), `handoff()` 함수를 사용할 수도 있습니다.
|
||||
|
||||
### `handoff()` 함수를 통한 핸드오프 사용자 지정
|
||||
|
||||
[`handoff()`][agents.handoffs.handoff] 함수를 사용하면 여러 항목을 사용자 지정할 수 있습니다.
|
||||
|
||||
- `agent`: 핸드오프 대상 에이전트입니다.
|
||||
- `tool_name_override`: 기본적으로 `Handoff.default_tool_name()` 함수가 사용되며, 이는 `transfer_to_<agent_name>`으로 해석됩니다. 이를 재정의할 수 있습니다.
|
||||
- `tool_description_override`: `Handoff.default_tool_description()`의 기본 도구 설명을 재정의합니다.
|
||||
- `on_handoff`: 핸드오프가 호출될 때 실행되는 콜백 함수입니다. 핸드오프가 호출된다는 사실을 알게 되는 즉시 일부 데이터 가져오기를 시작하는 등의 작업에 유용합니다. 이 함수는 에이전트 컨텍스트를 받으며, 선택적으로 LLM이 생성한 입력도 받을 수 있습니다. 입력 데이터는 `input_type` 매개변수로 제어됩니다.
|
||||
- `input_type`: 핸드오프 도구 호출 인수의 스키마입니다. 설정하면 파싱된 페이로드가 `on_handoff`에 전달됩니다.
|
||||
- `input_filter`: 이를 통해 다음 에이전트가 받는 입력을 필터링할 수 있습니다. 자세한 내용은 아래를 참고하세요.
|
||||
- `is_enabled`: 핸드오프가 활성화되어 있는지 여부입니다. 불리언이거나 불리언을 반환하는 함수일 수 있으며, 런타임에 핸드오프를 동적으로 활성화하거나 비활성화할 수 있습니다.
|
||||
- `nest_handoff_history`: RunConfig 수준의 `nest_handoff_history` 설정에 대한 호출별 선택적 재정의입니다. `None`이면 활성 실행 구성에 정의된 값이 대신 사용됩니다.
|
||||
|
||||
[`handoff()`][agents.handoffs.handoff] 헬퍼는 항상 전달한 특정 `agent`로 제어권을 넘깁니다. 가능한 목적지가 여러 개라면 목적지마다 하나의 핸드오프를 등록하고 모델이 그중에서 선택하도록 하세요. 자체 핸드오프 코드가 호출 시점에 어떤 에이전트를 반환할지 결정해야 하는 경우에만 사용자 지정 [`Handoff`][agents.handoffs.Handoff]를 사용하세요.
|
||||
|
||||
```python
|
||||
from agents import Agent, handoff, RunContextWrapper
|
||||
|
||||
def on_handoff(ctx: RunContextWrapper[None]):
|
||||
print("Handoff called")
|
||||
|
||||
agent = Agent(name="My agent")
|
||||
|
||||
handoff_obj = handoff(
|
||||
agent=agent,
|
||||
on_handoff=on_handoff,
|
||||
tool_name_override="custom_handoff_tool",
|
||||
tool_description_override="Custom description",
|
||||
)
|
||||
```
|
||||
|
||||
## 핸드오프 입력
|
||||
|
||||
특정 상황에서는 LLM이 핸드오프를 호출할 때 일부 데이터를 제공하도록 하고 싶을 수 있습니다. 예를 들어 "에스컬레이션 에이전트"로 핸드오프한다고 가정해 보겠습니다. 로그로 남길 수 있도록 모델이 사유를 제공하길 원할 수 있습니다.
|
||||
|
||||
```python
|
||||
from pydantic import BaseModel
|
||||
|
||||
from agents import Agent, handoff, RunContextWrapper
|
||||
|
||||
class EscalationData(BaseModel):
|
||||
reason: str
|
||||
|
||||
async def on_handoff(ctx: RunContextWrapper[None], input_data: EscalationData):
|
||||
print(f"Escalation agent called with reason: {input_data.reason}")
|
||||
|
||||
agent = Agent(name="Escalation agent")
|
||||
|
||||
handoff_obj = handoff(
|
||||
agent=agent,
|
||||
on_handoff=on_handoff,
|
||||
input_type=EscalationData,
|
||||
)
|
||||
```
|
||||
|
||||
`input_type`은 핸드오프 도구 호출 자체의 인수를 설명합니다. SDK는 해당 스키마를 핸드오프 도구의 `parameters`로 모델에 노출하고, 반환된 JSON을 로컬에서 검증한 뒤 파싱된 값을 `on_handoff`에 전달합니다.
|
||||
|
||||
이는 다음 에이전트의 기본 입력을 대체하지 않으며, 다른 목적지를 선택하지도 않습니다. [`handoff()`][agents.handoffs.handoff] 헬퍼는 여전히 래핑한 특정 에이전트로 전달하며, [`input_filter`][agents.handoffs.Handoff.input_filter] 또는 중첩 핸드오프 기록 설정으로 변경하지 않는 한 수신 에이전트는 여전히 대화 기록을 보게 됩니다.
|
||||
|
||||
`input_type`은 [`RunContextWrapper.context`][agents.run_context.RunContextWrapper.context]와도 별개입니다. 로컬에 이미 있는 애플리케이션 상태나 의존성이 아니라, 핸드오프 시점에 모델이 결정하는 메타데이터에 `input_type`을 사용하세요.
|
||||
|
||||
### `input_type` 사용 시점
|
||||
|
||||
핸드오프에 `reason`, `language`, `priority`, `summary`와 같은 작은 규모의 모델 생성 메타데이터가 필요할 때 `input_type`을 사용하세요. 예를 들어 분류 에이전트는 `{ "reason": "duplicate_charge", "priority": "high" }`와 함께 환불 에이전트로 핸드오프할 수 있으며, `on_handoff`는 환불 에이전트가 이어받기 전에 해당 메타데이터를 로그로 남기거나 영속화할 수 있습니다.
|
||||
|
||||
목표가 다를 경우에는 다른 메커니즘을 선택하세요:
|
||||
|
||||
- 기존 애플리케이션 상태와 의존성은 [`RunContextWrapper.context`][agents.run_context.RunContextWrapper.context]에 넣으세요. [컨텍스트 가이드](context.md)를 참고하세요.
|
||||
- 수신 에이전트가 보게 되는 기록을 변경하려면 [`input_filter`][agents.handoffs.Handoff.input_filter], [`RunConfig.nest_handoff_history`][agents.run.RunConfig.nest_handoff_history] 또는 [`RunConfig.handoff_history_mapper`][agents.run.RunConfig.handoff_history_mapper]를 사용하세요.
|
||||
- 가능한 전문 에이전트가 여러 개라면 목적지마다 하나의 핸드오프를 등록하세요. `input_type`은 선택된 핸드오프에 메타데이터를 추가할 수 있지만, 목적지 간 라우팅을 수행하지는 않습니다.
|
||||
- 대화를 이전하지 않고 중첩된 전문 에이전트에 구조화된 입력을 제공하려면 [`Agent.as_tool(parameters=...)`][agents.agent.Agent.as_tool]를 우선 사용하세요. [도구](tools.md#structured-input-for-tool-agents)를 참고하세요.
|
||||
|
||||
## 입력 필터
|
||||
|
||||
핸드오프가 발생하면 새 에이전트가 대화를 이어받는 것과 같으며, 이전 대화 기록 전체를 볼 수 있습니다. 이를 변경하려면 [`input_filter`][agents.handoffs.Handoff.input_filter]를 설정할 수 있습니다. 입력 필터는 [`HandoffInputData`][agents.handoffs.HandoffInputData]를 통해 기존 입력을 받는 함수이며, 새 `HandoffInputData`를 반환해야 합니다.
|
||||
|
||||
[`HandoffInputData`][agents.handoffs.HandoffInputData]에는 다음이 포함됩니다:
|
||||
|
||||
- `input_history`: `Runner.run(...)`이 시작되기 전의 입력 기록입니다.
|
||||
- `pre_handoff_items`: 핸드오프가 호출된 에이전트 턴 이전에 생성된 항목입니다.
|
||||
- `new_items`: 현재 턴 중 생성된 항목이며, 핸드오프 호출과 핸드오프 출력 항목을 포함합니다.
|
||||
- `input_items`: `new_items` 대신 다음 에이전트에 전달할 선택적 항목입니다. 이를 통해 세션 기록용으로 `new_items`는 그대로 유지하면서 모델 입력을 필터링할 수 있습니다.
|
||||
- `run_context`: 핸드오프가 호출된 시점의 활성 [`RunContextWrapper`][agents.run_context.RunContextWrapper]입니다.
|
||||
|
||||
중첩 핸드오프는 명시적으로 활성화해야 하는 베타 기능으로 제공되며, 안정화하는 동안 기본적으로 비활성화되어 있습니다. [`RunConfig.nest_handoff_history`][agents.run.RunConfig.nest_handoff_history]를 활성화하면 러너는 이전 대화 기록을 하나의 어시스턴트 요약 메시지로 압축하고, 동일한 실행 중 여러 핸드오프가 발생할 때 새 턴을 계속 추가하는 `<CONVERSATION HISTORY>` 블록으로 감쌉니다. 전체 `input_filter`를 작성하지 않고도 [`RunConfig.handoff_history_mapper`][agents.run.RunConfig.handoff_history_mapper]를 통해 자체 매핑 함수를 제공하여 생성된 메시지를 대체할 수 있습니다. 이 명시적 활성화는 핸드오프와 실행 모두 명시적 `input_filter`를 제공하지 않는 경우에만 적용되므로, 이미 페이로드를 사용자 지정하는 기존 코드(이 저장소의 코드 예제를 포함)는 변경 없이 현재 동작을 유지합니다. 단일 핸드오프에 대해서는 [`handoff(...)`][agents.handoffs.handoff]에 `nest_handoff_history=True` 또는 `False`를 전달하여 중첩 동작을 재정의할 수 있으며, 이는 [`Handoff.nest_handoff_history`][agents.handoffs.Handoff.nest_handoff_history]를 설정합니다. 생성된 요약의 래퍼 텍스트만 변경하면 된다면, 에이전트를 실행하기 전에 [`set_conversation_history_wrappers`][agents.handoffs.set_conversation_history_wrappers]를 호출하세요(그리고 선택적으로 [`reset_conversation_history_wrappers`][agents.handoffs.reset_conversation_history_wrappers]도 호출할 수 있습니다).
|
||||
|
||||
핸드오프와 활성 [`RunConfig.handoff_input_filter`][agents.run.RunConfig.handoff_input_filter]가 모두 필터를 정의하는 경우, 해당 특정 핸드오프에는 핸드오프별 [`input_filter`][agents.handoffs.Handoff.input_filter]가 우선합니다.
|
||||
|
||||
!!! note
|
||||
|
||||
핸드오프는 단일 실행 내에 머뭅니다. 입력 가드레일은 여전히 체인의 첫 번째 에이전트에만 적용되고, 출력 가드레일은 최종 출력을 생성하는 에이전트에만 적용됩니다. 워크플로 내부의 각 사용자 지정 함수 도구 호출에 대한 검사가 필요할 때는 도구 가드레일을 사용하세요.
|
||||
|
||||
몇 가지 일반적인 패턴(예: 기록에서 모든 도구 호출 제거)은 [`agents.extensions.handoff_filters`][]에 구현되어 있습니다
|
||||
|
||||
```python
|
||||
from agents import Agent, handoff
|
||||
from agents.extensions import handoff_filters
|
||||
|
||||
agent = Agent(name="FAQ agent")
|
||||
|
||||
handoff_obj = handoff(
|
||||
agent=agent,
|
||||
input_filter=handoff_filters.remove_all_tools, # (1)!
|
||||
)
|
||||
```
|
||||
|
||||
1. 이렇게 하면 `FAQ agent`가 호출될 때 기록에서 모든 도구가 자동으로 제거됩니다.
|
||||
|
||||
## 권장 프롬프트
|
||||
|
||||
LLM이 핸드오프를 올바르게 이해하도록 하려면, 에이전트에 핸드오프 관련 정보를 포함하는 것을 권장합니다. 제안된 접두사는 [`agents.extensions.handoff_prompt.RECOMMENDED_PROMPT_PREFIX`][]에 있으며, 또는 [`agents.extensions.handoff_prompt.prompt_with_handoff_instructions`][]를 호출하여 프롬프트에 권장 데이터를 자동으로 추가할 수 있습니다.
|
||||
|
||||
```python
|
||||
from agents import Agent
|
||||
from agents.extensions.handoff_prompt import RECOMMENDED_PROMPT_PREFIX
|
||||
|
||||
billing_agent = Agent(
|
||||
name="Billing agent",
|
||||
instructions=f"""{RECOMMENDED_PROMPT_PREFIX}
|
||||
<Fill in the rest of your prompt here>.""",
|
||||
)
|
||||
```
|
||||
@@ -0,0 +1,201 @@
|
||||
---
|
||||
search:
|
||||
exclude: true
|
||||
---
|
||||
# 휴먼인더루프 (HITL)
|
||||
|
||||
휴먼인더루프 (HITL) 흐름을 사용해 사람이 민감한 도구 호출을 승인하거나 거부할 때까지 에이전트 실행을 일시 중지합니다. 도구는 승인이 필요한 시점을 선언하고, 실행 결과는 보류 중인 승인을 인터럽션(중단 처리)으로 노출하며, `RunState`를 사용하면 결정이 내려진 뒤 실행을 직렬화하고 재개할 수 있습니다.
|
||||
|
||||
이 승인 처리는 실행 전체 범위에 적용되며, 현재 최상위 에이전트로 제한되지 않습니다. 도구가 현재 에이전트에 속한 경우, 핸드오프로 도달한 에이전트에 속한 경우, 또는 중첩된 [`Agent.as_tool()`][agents.agent.Agent.as_tool] 실행에 속한 경우에도 같은 패턴이 적용됩니다. 중첩된 `Agent.as_tool()`의 경우에도 인터럽션(중단 처리)은 외부 실행에 노출되므로, 외부 `RunState`에서 승인하거나 거부한 뒤 원래 최상위 실행을 재개합니다.
|
||||
|
||||
`Agent.as_tool()`에서는 두 가지 계층에서 승인이 발생할 수 있습니다. 에이전트 도구 자체가 `Agent.as_tool(..., needs_approval=...)`를 통해 승인을 요구할 수 있고, 중첩된 에이전트 내부의 도구가 중첩 실행이 시작된 뒤 자체 승인 요청을 나중에 발생시킬 수 있습니다. 둘 다 동일한 외부 실행 인터럽션(중단 처리) 흐름을 통해 처리됩니다.
|
||||
|
||||
이 페이지는 `interruptions`를 통한 수동 승인 흐름에 중점을 둡니다. 앱이 코드로 결정을 내릴 수 있다면, 일부 도구 유형은 실행을 일시 중지하지 않고 계속 진행할 수 있도록 프로그래밍 방식 승인 콜백도 지원합니다.
|
||||
|
||||
## 승인이 필요한 도구 표시
|
||||
|
||||
항상 승인을 요구하려면 `needs_approval`을 `True`로 설정하거나, 호출마다 결정하는 비동기 함수를 제공합니다. 호출 가능한 함수는 실행 컨텍스트, 파싱된 도구 매개변수, 도구 호출 ID를 받습니다.
|
||||
|
||||
```python
|
||||
from agents import Agent, Runner, function_tool
|
||||
|
||||
|
||||
@function_tool(needs_approval=True)
|
||||
async def cancel_order(order_id: int) -> str:
|
||||
return f"Cancelled order {order_id}"
|
||||
|
||||
|
||||
async def requires_review(_ctx, params, _call_id) -> bool:
|
||||
return "refund" in params.get("subject", "").lower()
|
||||
|
||||
|
||||
@function_tool(needs_approval=requires_review)
|
||||
async def send_email(subject: str, body: str) -> str:
|
||||
return f"Sent '{subject}'"
|
||||
|
||||
|
||||
agent = Agent(
|
||||
name="Support agent",
|
||||
instructions="Handle tickets and ask for approval when needed.",
|
||||
tools=[cancel_order, send_email],
|
||||
)
|
||||
```
|
||||
|
||||
`needs_approval`은 [`function_tool`][agents.tool.function_tool], [`Agent.as_tool`][agents.agent.Agent.as_tool], [`ShellTool`][agents.tool.ShellTool], [`ApplyPatchTool`][agents.tool.ApplyPatchTool]에서 사용할 수 있습니다. 로컬 MCP 서버도 [`MCPServerStdio`][agents.mcp.server.MCPServerStdio], [`MCPServerSse`][agents.mcp.server.MCPServerSse], [`MCPServerStreamableHttp`][agents.mcp.server.MCPServerStreamableHttp]의 `require_approval`을 통해 승인을 지원합니다. 호스티드 MCP 서버는 `tool_config={"require_approval": "always"}`와 선택적 `on_approval_request` 콜백을 사용해 [`HostedMCPTool`][agents.tool.HostedMCPTool]을 통한 승인을 지원합니다. 셸 및 apply_patch 도구는 인터럽션(중단 처리)을 노출하지 않고 자동 승인 또는 자동 거부하려는 경우 `on_approval` 콜백을 허용합니다.
|
||||
|
||||
## 승인 흐름의 작동 방식
|
||||
|
||||
1. 모델이 도구 호출을 내보내면, 러너는 해당 승인 규칙(`needs_approval`, `require_approval` 또는 호스티드 MCP 대응 기능)을 평가합니다.
|
||||
2. 해당 도구 호출에 대한 승인 결정이 이미 [`RunContextWrapper`][agents.run_context.RunContextWrapper]에 저장되어 있으면, 러너는 프롬프트를 표시하지 않고 진행합니다. 호출별 승인은 특정 호출 ID 범위로 한정됩니다. 실행의 나머지 동안 해당 도구에 대한 이후 호출에도 같은 결정을 유지하려면 `always_approve=True` 또는 `always_reject=True`를 전달합니다.
|
||||
3. 그렇지 않으면 실행이 일시 중지되고 `RunResult.interruptions`(또는 `RunResultStreaming.interruptions`)에 `agent.name`, `tool_name`, `arguments` 같은 세부 정보가 포함된 [`ToolApprovalItem`][agents.items.ToolApprovalItem] 항목이 들어갑니다. 여기에는 핸드오프 이후 또는 중첩된 `Agent.as_tool()` 실행 내부에서 발생한 승인도 포함됩니다.
|
||||
4. `result.to_state()`로 결과를 `RunState`로 변환하고, `state.approve(...)` 또는 `state.reject(...)`를 호출한 다음, `Runner.run(agent, state)` 또는 `Runner.run_streamed(agent, state)`로 재개합니다. 여기서 `agent`는 해당 실행의 원래 최상위 에이전트입니다.
|
||||
5. 재개된 실행은 중단된 지점부터 계속 진행되며, 새 승인이 필요하면 이 흐름으로 다시 들어갑니다.
|
||||
|
||||
`always_approve=True` 또는 `always_reject=True`로 생성된 고정 결정은 실행 상태에 저장되므로, 나중에 같은 일시 중지된 실행을 재개할 때 `state.to_string()` / `RunState.from_string(...)` 및 `state.to_json()` / `RunState.from_json(...)` 이후에도 유지됩니다.
|
||||
|
||||
보류 중인 모든 승인을 같은 단계에서 해결할 필요는 없습니다. `interruptions`에는 일반 함수 도구, 호스티드 MCP 승인, 중첩된 `Agent.as_tool()` 승인이 섞여 있을 수 있습니다. 일부 항목만 승인하거나 거부한 뒤 다시 실행하면, 해결된 호출은 계속 진행될 수 있고 해결되지 않은 호출은 `interruptions`에 남아 실행을 다시 일시 중지합니다.
|
||||
|
||||
## 사용자 지정 거부 메시지
|
||||
|
||||
기본적으로 거부된 도구 호출은 SDK의 표준 거부 텍스트를 실행으로 다시 반환합니다. 이 메시지는 두 계층에서 사용자 지정할 수 있습니다.
|
||||
|
||||
- 실행 전체 폴백: 전체 실행에서 승인 거부에 대해 모델에 표시되는 기본 메시지를 제어하려면 [`RunConfig.tool_error_formatter`][agents.run.RunConfig.tool_error_formatter]를 설정합니다.
|
||||
- 호출별 재정의: 특정 거부된 도구 호출 하나에 다른 메시지를 노출하려면 `state.reject(...)`에 `rejection_message=...`를 전달합니다.
|
||||
|
||||
둘 다 제공되면 호출별 `rejection_message`가 실행 전체 포매터보다 우선합니다.
|
||||
|
||||
```python
|
||||
from agents import RunConfig, ToolErrorFormatterArgs
|
||||
|
||||
|
||||
def format_rejection(args: ToolErrorFormatterArgs[None]) -> str | None:
|
||||
if args.kind != "approval_rejected":
|
||||
return None
|
||||
return "Publish action was canceled because approval was rejected."
|
||||
|
||||
|
||||
run_config = RunConfig(tool_error_formatter=format_rejection)
|
||||
|
||||
# Later, while resolving a specific interruption:
|
||||
state.reject(
|
||||
interruption,
|
||||
rejection_message="Publish action was canceled because the reviewer denied approval.",
|
||||
)
|
||||
```
|
||||
|
||||
두 계층을 함께 보여 주는 전체 코드 예제는 [`examples/agent_patterns/human_in_the_loop_custom_rejection.py`](https://github.com/openai/openai-agents-python/tree/main/examples/agent_patterns/human_in_the_loop_custom_rejection.py)를 참고하세요.
|
||||
|
||||
## 자동 승인 결정
|
||||
|
||||
수동 `interruptions`가 가장 일반적인 패턴이지만, 유일한 방식은 아닙니다.
|
||||
|
||||
- 로컬 [`ShellTool`][agents.tool.ShellTool] 및 [`ApplyPatchTool`][agents.tool.ApplyPatchTool]은 코드에서 즉시 승인하거나 거부하기 위해 `on_approval`을 사용할 수 있습니다.
|
||||
- [`HostedMCPTool`][agents.tool.HostedMCPTool]은 같은 유형의 프로그래밍 방식 결정을 위해 `tool_config={"require_approval": "always"}`를 `on_approval_request`와 함께 사용할 수 있습니다.
|
||||
- 일반 [`function_tool`][agents.tool.function_tool] 도구와 [`Agent.as_tool()`][agents.agent.Agent.as_tool]는 이 페이지의 수동 인터럽션(중단 처리) 흐름을 사용합니다.
|
||||
|
||||
이러한 콜백이 결정을 반환하면, 실행은 사람의 응답을 기다리기 위해 일시 중지하지 않고 계속됩니다. Realtime 및 음성 세션 API의 경우 [Realtime 가이드](realtime/guide.md)의 승인 흐름을 참고하세요.
|
||||
|
||||
## 스트리밍과 세션
|
||||
|
||||
동일한 인터럽션(중단 처리) 흐름은 스트리밍 실행에서도 작동합니다. 스트리밍 실행이 일시 중지된 뒤에는 반복자가 끝날 때까지 [`RunResultStreaming.stream_events()`][agents.result.RunResultStreaming.stream_events]를 계속 소비하고, [`RunResultStreaming.interruptions`][agents.result.RunResultStreaming.interruptions]를 검사해 해결한 다음, 재개된 출력도 계속 스트리밍되게 하려면 [`Runner.run_streamed(...)`][agents.run.Runner.run_streamed]로 재개합니다. 이 패턴의 스트리밍 버전은 [스트리밍](streaming.md)을 참고하세요.
|
||||
|
||||
세션도 함께 사용하는 경우 `RunState`에서 재개할 때 같은 세션 인스턴스를 계속 전달하거나, 동일한 백킹 스토어를 가리키는 다른 세션 객체를 전달합니다. 그러면 재개된 턴이 동일하게 저장된 대화 기록에 추가됩니다. 세션 수명 주기 세부 정보는 [세션](sessions/index.md)을 참고하세요.
|
||||
|
||||
## 예제: 일시 중지, 승인, 재개
|
||||
|
||||
아래 스니펫은 JavaScript HITL 가이드와 동일한 흐름을 따릅니다. 도구에 승인이 필요할 때 일시 중지하고, 상태를 디스크에 저장한 뒤, 다시 로드하고, 결정을 수집한 후 재개합니다.
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from agents import Agent, Runner, RunState, function_tool
|
||||
|
||||
|
||||
async def needs_oakland_approval(_ctx, params, _call_id) -> bool:
|
||||
return "Oakland" in params.get("city", "")
|
||||
|
||||
|
||||
@function_tool(needs_approval=needs_oakland_approval)
|
||||
async def get_temperature(city: str) -> str:
|
||||
return f"The temperature in {city} is 20° Celsius"
|
||||
|
||||
|
||||
agent = Agent(
|
||||
name="Weather assistant",
|
||||
instructions="Answer weather questions with the provided tools.",
|
||||
tools=[get_temperature],
|
||||
)
|
||||
|
||||
STATE_PATH = Path(".cache/hitl_state.json")
|
||||
|
||||
|
||||
def prompt_approval(tool_name: str, arguments: str | None) -> bool:
|
||||
answer = input(f"Approve {tool_name} with {arguments}? [y/N]: ").strip().lower()
|
||||
return answer in {"y", "yes"}
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
result = await Runner.run(agent, "What is the temperature in Oakland?")
|
||||
|
||||
while result.interruptions:
|
||||
# Persist the paused state.
|
||||
state = result.to_state()
|
||||
STATE_PATH.parent.mkdir(parents=True, exist_ok=True)
|
||||
STATE_PATH.write_text(state.to_string())
|
||||
|
||||
# Load the state later (could be a different process).
|
||||
stored = json.loads(STATE_PATH.read_text())
|
||||
state = await RunState.from_json(agent, stored)
|
||||
|
||||
for interruption in result.interruptions:
|
||||
approved = await asyncio.get_running_loop().run_in_executor(
|
||||
None, prompt_approval, interruption.name or "unknown_tool", interruption.arguments
|
||||
)
|
||||
if approved:
|
||||
state.approve(interruption, always_approve=False)
|
||||
else:
|
||||
state.reject(interruption)
|
||||
|
||||
result = await Runner.run(agent, state)
|
||||
|
||||
print(result.final_output)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
이 예제에서 `prompt_approval`은 `input()`을 사용하고 `run_in_executor(...)`로 실행되기 때문에 동기 함수입니다. 승인 소스가 이미 비동기인 경우(예: HTTP 요청 또는 비동기 데이터베이스 쿼리), 대신 `async def` 함수를 사용하고 직접 `await`할 수 있습니다.
|
||||
|
||||
승인을 기다리는 동안 출력을 스트리밍하려면 `Runner.run_streamed`를 호출하고, 완료될 때까지 `result.stream_events()`를 소비한 다음, 위에 표시된 것과 동일한 `result.to_state()` 및 재개 단계를 따릅니다.
|
||||
|
||||
## 리포지토리 패턴과 코드 예제
|
||||
|
||||
- **스트리밍 승인**: `examples/agent_patterns/human_in_the_loop_stream.py`는 `stream_events()`를 모두 소비한 다음, `Runner.run_streamed(agent, state)`로 재개하기 전에 보류 중인 도구 호출을 승인하는 방법을 보여 줍니다.
|
||||
- **사용자 지정 거부 텍스트**: `examples/agent_patterns/human_in_the_loop_custom_rejection.py`는 승인이 거부될 때 실행 수준 `tool_error_formatter`와 호출별 `rejection_message` 재정의를 결합하는 방법을 보여 줍니다.
|
||||
- **도구로 사용하는 에이전트 승인**: `Agent.as_tool(..., needs_approval=...)`는 위임된 에이전트 작업에 검토가 필요할 때 동일한 인터럽션(중단 처리) 흐름을 적용합니다. 중첩된 인터럽션(중단 처리)은 여전히 외부 실행에 노출되므로, 중첩된 에이전트가 아니라 원래 최상위 에이전트를 재개합니다.
|
||||
- **로컬 셸 및 apply_patch 도구**: `ShellTool` 및 `ApplyPatchTool`도 `needs_approval`을 지원합니다. 향후 호출에 대한 결정을 캐시하려면 `state.approve(interruption, always_approve=True)` 또는 `state.reject(..., always_reject=True)`를 사용합니다. 자동 결정을 위해서는 `on_approval`을 제공하세요(`examples/tools/shell.py` 참고). 수동 결정을 위해서는 인터럽션(중단 처리)을 처리하세요(`examples/tools/shell_human_in_the_loop.py` 참고). 호스티드 셸 환경은 `needs_approval` 또는 `on_approval`을 지원하지 않습니다. [도구 가이드](tools.md)를 참고하세요.
|
||||
- **로컬 MCP 서버**: MCP 도구 호출을 제한하려면 `MCPServerStdio` / `MCPServerSse` / `MCPServerStreamableHttp`에서 `require_approval`을 사용합니다(`examples/mcp/get_all_mcp_tools_example/main.py` 및 `examples/mcp/tool_filter_example/main.py` 참고).
|
||||
- **호스티드 MCP 서버**: HITL을 강제하려면 `HostedMCPTool`에서 `require_approval`을 `"always"`로 설정하고, 선택적으로 자동 승인 또는 거부를 위해 `on_approval_request`를 제공합니다(`examples/hosted_mcp/human_in_the_loop.py` 및 `examples/hosted_mcp/on_approval.py` 참고). 신뢰할 수 있는 서버에는 `"never"`를 사용합니다(`examples/hosted_mcp/simple.py`).
|
||||
- **세션과 메모리**: 승인과 대화 기록이 여러 턴 동안 유지되도록 `Runner.run`에 세션을 전달합니다. SQLite 및 OpenAI Conversations 세션 변형은 `examples/memory/memory_session_hitl_example.py` 및 `examples/memory/openai_session_hitl_example.py`에 있습니다.
|
||||
- **실시간 에이전트**: 실시간 데모는 `RealtimeSession`의 `approve_tool_call` / `reject_tool_call`을 통해 도구 호출을 승인하거나 거부하는 WebSocket 메시지를 노출합니다. 서버 측 핸들러는 `examples/realtime/app/server.py`를, API 인터페이스는 [Realtime 가이드](realtime/guide.md#tool-approvals)를 참고하세요.
|
||||
|
||||
## 장기 실행 승인
|
||||
|
||||
`RunState`는 내구성을 갖도록 설계되었습니다. `state.to_json()` 또는 `state.to_string()`을 사용해 보류 중인 작업을 데이터베이스나 큐에 저장하고, 나중에 `RunState.from_json(...)` 또는 `RunState.from_string(...)`으로 다시 생성합니다.
|
||||
|
||||
유용한 직렬화 옵션:
|
||||
|
||||
- `context_serializer`: 비매핑 컨텍스트 객체가 직렬화되는 방식을 사용자 지정합니다.
|
||||
- `context_deserializer`: `RunState.from_json(...)` 또는 `RunState.from_string(...)`으로 상태를 로드할 때 비매핑 컨텍스트 객체를 다시 빌드합니다.
|
||||
- `strict_context=True`: 컨텍스트가 이미 매핑이거나 적절한 serializer/deserializer를 제공한 경우가 아니면 직렬화 또는 역직렬화에 실패합니다.
|
||||
- `context_override`: 상태를 로드할 때 직렬화된 컨텍스트를 대체합니다. 원래 컨텍스트 객체를 복원하고 싶지 않을 때 유용하지만, 이미 직렬화된 페이로드에서 해당 컨텍스트를 제거하지는 않습니다.
|
||||
- `include_tracing_api_key=True`: 재개된 작업이 동일한 자격 증명으로 트레이스를 계속 내보내야 하는 경우, 직렬화된 트레이스 페이로드에 트레이싱 API 키를 포함합니다.
|
||||
|
||||
직렬화된 실행 상태에는 앱 컨텍스트와 함께 승인, 사용량, 직렬화된 `tool_input`, 중첩된 agent-as-tool 재개, 트레이스 메타데이터, 서버 관리 대화 설정 같은 SDK 관리 런타임 메타데이터가 포함됩니다. 직렬화된 상태를 저장하거나 전송하려는 경우 `RunContextWrapper.context`를 영속화된 데이터로 취급하고, 상태와 함께 이동하기를 의도한 경우가 아니라면 그 안에 비밀 정보를 두지 마세요.
|
||||
|
||||
## 보류 중인 작업 버전 관리
|
||||
|
||||
승인이 한동안 대기할 수 있다면, 에이전트 정의 또는 SDK의 버전 표시자를 직렬화된 상태와 함께 저장하세요. 그러면 모델, 프롬프트 또는 도구 정의가 변경될 때 비호환성을 피하기 위해 역직렬화를 일치하는 코드 경로로 라우팅할 수 있습니다.
|
||||
@@ -0,0 +1,101 @@
|
||||
---
|
||||
search:
|
||||
exclude: true
|
||||
---
|
||||
# OpenAI Agents SDK
|
||||
|
||||
[OpenAI Agents SDK](https://github.com/openai/openai-agents-python)를 사용하면 매우 적은 추상화만으로 가볍고 사용하기 쉬운 패키지에서 에이전트형 AI 앱을 구축할 수 있습니다. 이는 이전 에이전트 실험 프로젝트인 [Swarm](https://github.com/openai/swarm/tree/main)을 프로덕션에 바로 사용할 수 있도록 업그레이드한 것입니다. Agents SDK는 매우 작은 기본 구성 요소 집합을 갖습니다.
|
||||
|
||||
- **에이전트**: instructions와 tools를 갖춘 LLM
|
||||
- **Agents as tools / 핸드오프**: 에이전트가 특정 작업을 다른 에이전트에 위임할 수 있게 하는 기능
|
||||
- **가드레일**: 에이전트 입력과 출력을 검증할 수 있게 하는 기능
|
||||
|
||||
Python과 결합하면 이러한 기본 구성 요소만으로도 도구와 에이전트 간의 복잡한 관계를 표현하기에 충분히 강력하며, 가파른 학습 곡선 없이 실제 애플리케이션을 구축할 수 있습니다. 또한 SDK에는 에이전트형 흐름을 시각화하고 디버깅하며, 이를 평가하고 애플리케이션에 맞게 모델을 파인튜닝할 수 있는 내장 **트레이싱** 기능이 포함되어 있습니다.
|
||||
|
||||
## Agents SDK를 사용하는 이유
|
||||
|
||||
SDK에는 두 가지 핵심 설계 원칙이 있습니다.
|
||||
|
||||
1. 사용할 가치가 있을 만큼 충분한 기능을 제공하되, 빠르게 배울 수 있을 만큼 기본 구성 요소는 적게 유지합니다.
|
||||
2. 기본 설정만으로도 잘 작동하지만, 어떤 일이 일어나는지는 정확하게 사용자 지정할 수 있습니다.
|
||||
|
||||
SDK의 주요 기능은 다음과 같습니다.
|
||||
|
||||
- **에이전트 루프**: 도구 호출을 처리하고, 결과를 LLM에 다시 보내며, 작업이 완료될 때까지 계속 실행하는 내장 에이전트 루프
|
||||
- **파이썬 우선**: 새로운 추상화를 배울 필요 없이, 내장 언어 기능을 사용해 에이전트를 오케스트레이션하고 체인으로 연결
|
||||
- **Agents as tools / 핸드오프**: 여러 에이전트 간 작업을 조율하고 위임하기 위한 강력한 메커니즘
|
||||
- **샌드박스 에이전트**: 매니페스트로 정의된 파일, 샌드박스 클라이언트 선택, 재개 가능한 샌드박스 세션을 통해 실제 격리된 워크스페이스 안에서 전문가 실행
|
||||
- **가드레일**: 에이전트 실행과 병렬로 입력 검증 및 안전성 검사를 실행하고, 검사를 통과하지 못하면 빠르게 실패 처리
|
||||
- **함수 도구**: 자동 스키마 생성 및 Pydantic 기반 검증을 통해 모든 Python 함수를 도구로 변환
|
||||
- **MCP 서버 도구 호출**: 함수 도구와 동일한 방식으로 작동하는 내장 MCP 서버 도구 통합
|
||||
- **세션**: 에이전트 루프 내에서 작업 컨텍스트를 유지하기 위한 영속 메모리 계층
|
||||
- **휴먼인더루프 (HITL)**: 에이전트 실행 전반에 사람을 참여시키기 위한 내장 메커니즘
|
||||
- **트레이싱**: OpenAI의 평가, 파인튜닝, 증류 도구 모음 지원과 함께 워크플로를 시각화, 디버깅, 모니터링하기 위한 내장 트레이싱
|
||||
- **실시간 에이전트**: `gpt-realtime-2.1`, 자동 인터럽션(중단 처리) 감지, 컨텍스트 관리, 가드레일 등을 활용해 강력한 음성 에이전트 구축
|
||||
|
||||
## Agents SDK 또는 Responses API
|
||||
|
||||
SDK는 OpenAI 모델에 기본적으로 Responses API를 사용하지만, 모델 호출 주변에 더 높은 수준의 런타임을 추가합니다.
|
||||
|
||||
다음과 같은 경우 Responses API를 직접 사용하세요.
|
||||
|
||||
- 루프, 도구 디스패치, 상태 처리를 직접 관리하려는 경우
|
||||
- 워크플로가 짧게 실행되며 주로 모델의 응답을 반환하는 것이 목적인 경우
|
||||
|
||||
다음과 같은 경우 Agents SDK를 사용하세요.
|
||||
|
||||
- 런타임이 턴, 도구 실행, 가드레일, 핸드오프 또는 세션을 관리하기를 원하는 경우
|
||||
- 에이전트가 아티팩트를 생성하거나 여러 조율된 단계에 걸쳐 동작해야 하는 경우
|
||||
- 실제 워크스페이스나 [샌드박스 에이전트](sandbox_agents.md)를 통한 재개 가능한 실행이 필요한 경우
|
||||
|
||||
둘 중 하나를 전역적으로 선택할 필요는 없습니다. 많은 애플리케이션은 관리형 워크플로에는 SDK를 사용하고, 더 낮은 수준의 경로에는 Responses API를 직접 호출합니다.
|
||||
|
||||
## 설치
|
||||
|
||||
```bash
|
||||
pip install openai-agents
|
||||
```
|
||||
|
||||
## Hello world 예제
|
||||
|
||||
```python
|
||||
from agents import Agent, Runner
|
||||
|
||||
agent = Agent(name="Assistant", instructions="You are a helpful assistant")
|
||||
|
||||
result = Runner.run_sync(agent, "Write a haiku about recursion in programming.")
|
||||
print(result.final_output)
|
||||
|
||||
# Code within the code,
|
||||
# Functions calling themselves,
|
||||
# Infinite loop's dance.
|
||||
```
|
||||
|
||||
(_이를 실행하는 경우 `OPENAI_API_KEY` 환경 변수를 설정했는지 확인하세요_)
|
||||
|
||||
```bash
|
||||
export OPENAI_API_KEY=sk-...
|
||||
```
|
||||
|
||||
## 시작 지점
|
||||
|
||||
- [빠른 시작](quickstart.md)으로 첫 텍스트 기반 에이전트를 구축하세요.
|
||||
- 그런 다음 [에이전트 실행](running_agents.md#choose-a-memory-strategy)에서 턴 간 상태를 어떻게 유지할지 결정하세요.
|
||||
- 작업이 실제 파일, 리포지토리 또는 에이전트별 격리된 워크스페이스 상태에 의존한다면 [샌드박스 에이전트 빠른 시작](sandbox_agents.md)을 읽어 보세요.
|
||||
- 핸드오프와 매니저 스타일 오케스트레이션 중에서 결정하는 중이라면 [에이전트 오케스트레이션](multi_agent.md)을 읽어 보세요.
|
||||
|
||||
## 경로 선택
|
||||
|
||||
하려는 작업은 알고 있지만 어느 페이지에서 설명하는지 모를 때 이 표를 사용하세요.
|
||||
|
||||
| 목표 | 시작 지점 |
|
||||
| --- | --- |
|
||||
| 첫 텍스트 에이전트를 만들고 전체 실행 한 번 확인 | [빠른 시작](quickstart.md) |
|
||||
| 함수 도구, 호스티드 툴 또는 agents as tools 추가 | [도구](tools.md) |
|
||||
| 실제 격리된 워크스페이스 안에서 코딩, 리뷰 또는 문서 에이전트 실행 | [샌드박스 에이전트 빠른 시작](sandbox_agents.md) 및 [샌드박스 클라이언트](sandbox/clients.md) |
|
||||
| 핸드오프와 매니저 스타일 오케스트레이션 중에서 결정 | [에이전트 오케스트레이션](multi_agent.md) |
|
||||
| 턴 간 메모리 유지 | [에이전트 실행](running_agents.md#choose-a-memory-strategy) 및 [세션](sessions/index.md) |
|
||||
| OpenAI 모델, 웹소켓 전송 또는 비 OpenAI 제공자 사용 | [모델](models/index.md) |
|
||||
| 출력, 실행 항목, 인터럽션(중단 처리), 재개 상태 검토 | [결과](results.md) |
|
||||
| `gpt-realtime-2.1`로 지연 시간이 낮은 음성 에이전트 구축 | [실시간 에이전트 빠른 시작](realtime/quickstart.md) 및 [실시간 전송](realtime/transport.md) |
|
||||
| 음성-텍스트 변환 / 에이전트 / 텍스트-음성 변환 파이프라인 구축 | [음성 파이프라인 빠른 시작](voice/quickstart.md) |
|
||||
+467
@@ -0,0 +1,467 @@
|
||||
---
|
||||
search:
|
||||
exclude: true
|
||||
---
|
||||
# Model context protocol (MCP)
|
||||
|
||||
[Model context protocol](https://modelcontextprotocol.io/introduction) (MCP)는 애플리케이션이 도구와
|
||||
컨텍스트를 언어 모델에 노출하는 방식을 표준화합니다. 공식 문서에 따르면 다음과 같습니다.
|
||||
|
||||
> MCP는 애플리케이션이 LLMs에 컨텍스트를 제공하는 방식을 표준화하는 개방형 프로토콜입니다. MCP를 AI
|
||||
> 애플리케이션을 위한 USB-C 포트처럼 생각해 보세요. USB-C가 기기를 다양한 주변 장치와 액세서리에 연결하는 표준화된 방식을 제공하듯이, MCP는
|
||||
> AI 모델을 다양한 데이터 소스와 도구에 연결하는 표준화된 방식을 제공합니다.
|
||||
|
||||
Agents Python SDK는 여러 MCP 전송 방식을 지원합니다. 이를 통해 기존 MCP 서버를 재사용하거나, 파일 시스템, HTTP 또는 커넥터 기반 도구를 에이전트에 노출하도록 직접 빌드할 수 있습니다.
|
||||
|
||||
## MCP 통합 선택
|
||||
|
||||
MCP 서버를 에이전트에 연결하기 전에 도구 호출을 어디서 실행해야 하는지, 어떤 전송 방식에 접근할 수 있는지 결정하세요. 아래 표는 Python SDK가 지원하는 옵션을 요약합니다.
|
||||
|
||||
| 필요한 사항 | 권장 옵션 |
|
||||
| ------------------------------------------------------------------------------------ | ----------------------------------------------------- |
|
||||
| OpenAI의 Responses API가 모델을 대신해 공개적으로 접근 가능한 MCP 서버를 호출하도록 하기| [`HostedMCPTool`][agents.tool.HostedMCPTool]을 통한 **호스티드 MCP 서버 도구** |
|
||||
| 로컬 또는 원격에서 실행하는 Streamable HTTP 서버에 연결하기 | [`MCPServerStreamableHttp`][agents.mcp.server.MCPServerStreamableHttp]를 통한 **Streamable HTTP MCP 서버** |
|
||||
| Server-Sent Events가 포함된 HTTP를 구현하는 서버와 통신하기 | [`MCPServerSse`][agents.mcp.server.MCPServerSse]를 통한 **HTTP with SSE MCP 서버** |
|
||||
| 로컬 프로세스를 시작하고 stdin/stdout을 통해 통신하기 | [`MCPServerStdio`][agents.mcp.server.MCPServerStdio]를 통한 **stdio MCP 서버** |
|
||||
|
||||
아래 섹션에서는 각 옵션, 구성 방법, 그리고 어떤 경우에 한 전송 방식을 다른 전송 방식보다 선호해야 하는지 살펴봅니다.
|
||||
|
||||
## 에이전트 수준 MCP 구성
|
||||
|
||||
전송 방식을 선택하는 것 외에도 `Agent.mcp_config`를 설정하여 MCP 도구가 준비되는 방식을 조정할 수 있습니다.
|
||||
|
||||
```python
|
||||
from agents import Agent
|
||||
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
mcp_servers=[server],
|
||||
mcp_config={
|
||||
# Try to convert MCP tool schemas to strict JSON schema.
|
||||
"convert_schemas_to_strict": True,
|
||||
# If None, MCP tool failures are raised as exceptions instead of
|
||||
# returning model-visible error text.
|
||||
"failure_error_function": None,
|
||||
# Prefix local MCP tool names with their server name.
|
||||
"include_server_in_tool_names": True,
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||
참고:
|
||||
|
||||
- `convert_schemas_to_strict`는 최선 노력 방식입니다. 스키마를 변환할 수 없으면 원래 스키마가 사용됩니다.
|
||||
- `failure_error_function`은 MCP 도구 호출 실패가 모델에 어떻게 표시되는지 제어합니다.
|
||||
- `failure_error_function`이 설정되지 않은 경우 SDK는 기본 도구 오류 포매터를 사용합니다.
|
||||
- 서버 수준의 `failure_error_function`은 해당 서버에 대해 `Agent.mcp_config["failure_error_function"]`을 재정의합니다.
|
||||
- `include_server_in_tool_names`는 옵트인 방식입니다. 활성화하면 각 로컬 MCP 도구가 결정적인 서버 접두사 이름으로 모델에 노출되어, 여러 MCP 서버가 같은 이름의 도구를 게시할 때 충돌을 피하는 데 도움이 됩니다. 생성된 이름은 ASCII-safe이고, 함수 도구 이름 길이 제한 내에 있으며, 동일한 에이전트에 있는 기존 로컬 함수 도구 이름 및 활성화된 핸드오프 이름과 충돌하지 않습니다. SDK는 여전히 원래 서버에서 원래 MCP 도구 이름을 호출합니다.
|
||||
|
||||
## 전송 방식 전반의 공통 패턴
|
||||
|
||||
전송 방식을 선택한 뒤에는 대부분의 통합에서 다음과 같은 후속 결정이 필요합니다.
|
||||
|
||||
- 도구의 일부만 노출하는 방법([도구 필터링](#tool-filtering)).
|
||||
- 서버가 재사용 가능한 프롬프트도 제공하는지 여부([프롬프트](#prompts)).
|
||||
- `list_tools()`를 캐시해야 하는지 여부([캐싱](#caching)).
|
||||
- MCP 활동이 트레이스에 표시되는 방식([트레이싱](#tracing)).
|
||||
|
||||
로컬 MCP 서버(`MCPServerStdio`, `MCPServerSse`, `MCPServerStreamableHttp`)의 경우 승인 정책과 호출별 `_meta` 페이로드도 공통 개념입니다. Streamable HTTP 섹션은 가장 완전한 예를 보여주며, 동일한 패턴이 다른 로컬 전송 방식에도 적용됩니다.
|
||||
|
||||
## 1. 호스티드 MCP 서버 도구
|
||||
|
||||
호스티드 툴은 전체 도구 왕복 과정을 OpenAI 인프라로 보냅니다. 코드가 도구를 나열하고 호출하는 대신, [`HostedMCPTool`][agents.tool.HostedMCPTool]은 서버 레이블(및 선택적 커넥터 메타데이터)을 Responses API로 전달합니다. 모델은 Python 프로세스에 대한 추가 콜백 없이 원격 서버의 도구를 나열하고 호출합니다. 현재 호스티드 툴은 Responses API의 호스티드 MCP 통합을 지원하는 OpenAI 모델에서 작동합니다.
|
||||
|
||||
### 기본 호스티드 MCP 도구
|
||||
|
||||
에이전트의 `tools` 목록에 [`HostedMCPTool`][agents.tool.HostedMCPTool]을 추가하여 호스티드 툴을 만듭니다. `tool_config`
|
||||
dict는 REST API로 보낼 JSON과 동일한 구조입니다.
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
|
||||
from agents import Agent, HostedMCPTool, Runner
|
||||
|
||||
async def main() -> None:
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
instructions="Use the DeepWiki hosted MCP server to inspect openai/openai-agents-python.",
|
||||
tools=[
|
||||
HostedMCPTool(
|
||||
tool_config={
|
||||
"type": "mcp",
|
||||
"server_label": "deepwiki",
|
||||
"server_url": "https://mcp.deepwiki.com/mcp",
|
||||
"require_approval": "never",
|
||||
}
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
"Which language is the repository openai/openai-agents-python written in?",
|
||||
)
|
||||
print(result.final_output)
|
||||
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
호스티드 서버는 자체 도구를 자동으로 노출하므로, 이를 `mcp_servers`에 추가하지 않습니다.
|
||||
|
||||
호스티드 툴 검색이 호스티드 MCP 서버를 지연 로드하도록 하려면 `tool_config["defer_loading"] = True`를 설정하고 [`ToolSearchTool`][agents.tool.ToolSearchTool]을 에이전트에 추가하세요. 이는 OpenAI Responses 모델에서만 지원됩니다. 전체 도구 검색 설정 및 제약 사항은 [도구](tools.md#hosted-tool-search)를 참조하세요.
|
||||
|
||||
### 호스티드 MCP 결과 스트리밍
|
||||
|
||||
호스티드 툴은 함수 도구와 정확히 같은 방식으로 스트리밍 결과를 지원합니다. 모델이 계속 작업하는 동안
|
||||
증분 MCP 출력을 소비하려면 `Runner.run_streamed`를 사용하세요.
|
||||
|
||||
```python
|
||||
result = Runner.run_streamed(agent, "Summarise this repository's top languages")
|
||||
async for event in result.stream_events():
|
||||
if event.type == "run_item_stream_event":
|
||||
print(f"Received: {event.item}")
|
||||
print(result.final_output)
|
||||
```
|
||||
|
||||
### 선택적 승인 흐름
|
||||
|
||||
서버가 민감한 작업을 수행할 수 있다면 각 도구 실행 전에 사람 또는 프로그램 방식의 승인을 요구할 수 있습니다. `tool_config`에서 `require_approval`을 단일 정책(`"always"`, `"never"`) 또는 도구 이름을 정책에 매핑하는 dict로 구성하세요. Python 내부에서 결정을 내리려면 `on_approval_request` 콜백을 제공하세요.
|
||||
|
||||
```python
|
||||
from agents import MCPToolApprovalFunctionResult, MCPToolApprovalRequest
|
||||
|
||||
SAFE_TOOLS = {"read_wiki_structure", "read_wiki_contents", "ask_question"}
|
||||
|
||||
def approve_tool(request: MCPToolApprovalRequest) -> MCPToolApprovalFunctionResult:
|
||||
if request.data.name in SAFE_TOOLS:
|
||||
return {"approve": True}
|
||||
return {"approve": False, "reason": "Escalate to a human reviewer"}
|
||||
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
tools=[
|
||||
HostedMCPTool(
|
||||
tool_config={
|
||||
"type": "mcp",
|
||||
"server_label": "deepwiki",
|
||||
"server_url": "https://mcp.deepwiki.com/mcp",
|
||||
"require_approval": "always",
|
||||
},
|
||||
on_approval_request=approve_tool,
|
||||
)
|
||||
],
|
||||
)
|
||||
```
|
||||
|
||||
콜백은 동기 또는 비동기일 수 있으며, 모델이 계속 실행하기 위해 승인 데이터가 필요할 때마다 호출됩니다.
|
||||
|
||||
### 커넥터 기반 호스티드 서버
|
||||
|
||||
호스티드 MCP는 OpenAI 커넥터도 지원합니다. `server_url`을 지정하는 대신 `connector_id`와 액세스 토큰을 제공하세요. Responses API가 인증을 처리하고 호스티드 서버가 커넥터의 도구를 노출합니다.
|
||||
|
||||
```python
|
||||
import os
|
||||
|
||||
HostedMCPTool(
|
||||
tool_config={
|
||||
"type": "mcp",
|
||||
"server_label": "google_calendar",
|
||||
"connector_id": "connector_googlecalendar",
|
||||
"authorization": os.environ["GOOGLE_CALENDAR_AUTHORIZATION"],
|
||||
"require_approval": "never",
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
스트리밍, 승인, 커넥터를 포함해 완전히 동작하는 호스티드 툴 샘플은 [`examples/hosted_mcp`](https://github.com/openai/openai-agents-python/tree/main/examples/hosted_mcp)에 있습니다.
|
||||
|
||||
## 2. Streamable HTTP MCP 서버
|
||||
|
||||
네트워크 연결을 직접 관리하려면 [`MCPServerStreamableHttp`][agents.mcp.server.MCPServerStreamableHttp]를 사용하세요. Streamable HTTP 서버는 전송 방식을 제어하거나, 지연 시간을 낮게 유지하면서 자체 인프라 내부에서 서버를 실행하려는 경우에 적합합니다.
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
import os
|
||||
|
||||
from agents import Agent, Runner
|
||||
from agents.mcp import MCPServerStreamableHttp
|
||||
from agents.model_settings import ModelSettings
|
||||
|
||||
async def main() -> None:
|
||||
token = os.environ["MCP_SERVER_TOKEN"]
|
||||
async with MCPServerStreamableHttp(
|
||||
name="Streamable HTTP Python Server",
|
||||
params={
|
||||
"url": "http://localhost:8000/mcp",
|
||||
"headers": {"Authorization": f"Bearer {token}"},
|
||||
"timeout": 10,
|
||||
},
|
||||
cache_tools_list=True,
|
||||
max_retry_attempts=3,
|
||||
) as server:
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
instructions="Use the MCP tools to answer the questions.",
|
||||
mcp_servers=[server],
|
||||
model_settings=ModelSettings(tool_choice="required"),
|
||||
)
|
||||
|
||||
result = await Runner.run(agent, "Add 7 and 22.")
|
||||
print(result.final_output)
|
||||
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
생성자는 추가 옵션을 받습니다.
|
||||
|
||||
- `client_session_timeout_seconds`는 HTTP 읽기 타임아웃을 제어합니다.
|
||||
- `use_structured_content`는 텍스트 출력보다 `tool_result.structured_content`를 선호할지 여부를 전환합니다.
|
||||
- `max_retry_attempts`와 `retry_backoff_seconds_base`는 `list_tools()` 및 `call_tool()`에 대한 자동 재시도를 추가합니다.
|
||||
- `tool_filter`를 사용하면 도구의 일부만 노출할 수 있습니다([도구 필터링](#tool-filtering) 참조).
|
||||
- `require_approval`은 로컬 MCP 도구에서 휴먼인더루프 (HITL) 승인 정책을 활성화합니다.
|
||||
- `failure_error_function`은 모델에 표시되는 MCP 도구 실패 메시지를 사용자 지정합니다. 오류를 대신 발생시키려면 이를 `None`으로 설정하세요.
|
||||
- `tool_meta_resolver`는 `call_tool()` 전에 호출별 MCP `_meta` 페이로드를 주입합니다.
|
||||
|
||||
### 로컬 MCP 서버의 승인 정책
|
||||
|
||||
`MCPServerStdio`, `MCPServerSse`, `MCPServerStreamableHttp`는 모두 `require_approval`을 받습니다.
|
||||
|
||||
지원되는 형식:
|
||||
|
||||
- 모든 도구에 대해 `"always"` 또는 `"never"`
|
||||
- `True` / `False`(`always`/`never`와 동일)
|
||||
- 도구별 맵, 예: `{"delete_file": "always", "read_file": "never"}`
|
||||
- 그룹화된 객체: `{"always": {"tool_names": [...]}, "never": {"tool_names": [...]}}`
|
||||
|
||||
```python
|
||||
async with MCPServerStreamableHttp(
|
||||
name="Filesystem MCP",
|
||||
params={"url": "http://localhost:8000/mcp"},
|
||||
require_approval={"always": {"tool_names": ["delete_file"]}},
|
||||
) as server:
|
||||
...
|
||||
```
|
||||
|
||||
전체 일시 중지/재개 흐름은 [휴먼인더루프 (HITL)](human_in_the_loop.md)와 `examples/mcp/get_all_mcp_tools_example/main.py`를 참조하세요.
|
||||
|
||||
### 호출별 메타데이터와 `tool_meta_resolver`
|
||||
|
||||
MCP 서버가 `_meta`에서 요청 메타데이터(예: 테넌트 ID 또는 트레이스 컨텍스트)를 기대하는 경우 `tool_meta_resolver`를 사용하세요. 아래 예시는 `Runner.run(...)`에 `context`로 `dict`를 전달한다고 가정합니다.
|
||||
|
||||
```python
|
||||
from agents.mcp import MCPServerStreamableHttp, MCPToolMetaContext
|
||||
|
||||
|
||||
def resolve_meta(context: MCPToolMetaContext) -> dict[str, str] | None:
|
||||
run_context_data = context.run_context.context or {}
|
||||
tenant_id = run_context_data.get("tenant_id")
|
||||
if tenant_id is None:
|
||||
return None
|
||||
return {"tenant_id": str(tenant_id), "source": "agents-sdk"}
|
||||
|
||||
|
||||
server = MCPServerStreamableHttp(
|
||||
name="Metadata-aware MCP",
|
||||
params={"url": "http://localhost:8000/mcp"},
|
||||
tool_meta_resolver=resolve_meta,
|
||||
)
|
||||
```
|
||||
|
||||
실행 컨텍스트가 Pydantic 모델, dataclass 또는 사용자 지정 클래스라면 속성 접근으로 테넌트 ID를 읽으세요.
|
||||
|
||||
### MCP 도구 출력: 텍스트와 이미지
|
||||
|
||||
MCP 도구가 이미지 콘텐츠를 반환하면 SDK는 이를 이미지 도구 출력 항목으로 자동 매핑합니다. 텍스트/이미지 혼합 응답은 출력 항목 목록으로 전달되므로, 에이전트는 일반 함수 도구의 이미지 출력을 소비하는 것과 같은 방식으로 MCP 이미지 결과를 소비할 수 있습니다.
|
||||
|
||||
## 3. HTTP with SSE MCP 서버
|
||||
|
||||
!!! warning
|
||||
|
||||
MCP 프로젝트는 Server-Sent Events 전송 방식을 deprecated 처리했습니다. 새 통합에는 Streamable HTTP 또는 stdio를 선호하고 SSE는 레거시 서버에만 유지하세요.
|
||||
|
||||
MCP 서버가 HTTP with SSE 전송 방식을 구현하는 경우 [`MCPServerSse`][agents.mcp.server.MCPServerSse]를 인스턴스화하세요. 전송 방식을 제외하면 API는 Streamable HTTP 서버와 동일합니다.
|
||||
|
||||
```python
|
||||
|
||||
from agents import Agent, Runner
|
||||
from agents.model_settings import ModelSettings
|
||||
from agents.mcp import MCPServerSse
|
||||
|
||||
workspace_id = "demo-workspace"
|
||||
|
||||
async with MCPServerSse(
|
||||
name="SSE Python Server",
|
||||
params={
|
||||
"url": "http://localhost:8000/sse",
|
||||
"headers": {"X-Workspace": workspace_id},
|
||||
},
|
||||
cache_tools_list=True,
|
||||
) as server:
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
mcp_servers=[server],
|
||||
model_settings=ModelSettings(tool_choice="required"),
|
||||
)
|
||||
result = await Runner.run(agent, "What's the weather in Tokyo?")
|
||||
print(result.final_output)
|
||||
```
|
||||
|
||||
## 4. stdio MCP 서버
|
||||
|
||||
로컬 하위 프로세스로 실행되는 MCP 서버에는 [`MCPServerStdio`][agents.mcp.server.MCPServerStdio]를 사용하세요. SDK는 프로세스를 생성하고 파이프를 열린 상태로 유지하며, 컨텍스트 관리자가 종료될 때 자동으로 닫습니다. 이 옵션은 빠른 개념 증명이나 서버가 명령줄 엔트리 포인트만 노출하는 경우에 유용합니다.
|
||||
|
||||
```python
|
||||
from pathlib import Path
|
||||
from agents import Agent, Runner
|
||||
from agents.mcp import MCPServerStdio
|
||||
|
||||
current_dir = Path(__file__).parent
|
||||
samples_dir = current_dir / "sample_files"
|
||||
|
||||
async with MCPServerStdio(
|
||||
name="Filesystem Server via npx",
|
||||
params={
|
||||
"command": "npx",
|
||||
"args": ["-y", "@modelcontextprotocol/server-filesystem", str(samples_dir)],
|
||||
},
|
||||
) as server:
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
instructions="Use the files in the sample directory to answer questions.",
|
||||
mcp_servers=[server],
|
||||
)
|
||||
result = await Runner.run(agent, "List the files available to you.")
|
||||
print(result.final_output)
|
||||
```
|
||||
|
||||
## 5. MCP 서버 관리자
|
||||
|
||||
MCP 서버가 여러 개 있다면 `MCPServerManager`를 사용해 미리 연결하고 연결된 하위 집합을 에이전트에 노출하세요. 생성자 옵션 및 재연결 동작은 [MCPServerManager API 참조](ref/mcp/manager.md)를 참조하세요.
|
||||
|
||||
```python
|
||||
from agents import Agent, Runner
|
||||
from agents.mcp import MCPServerManager, MCPServerStreamableHttp
|
||||
|
||||
servers = [
|
||||
MCPServerStreamableHttp(name="calendar", params={"url": "http://localhost:8000/mcp"}),
|
||||
MCPServerStreamableHttp(name="docs", params={"url": "http://localhost:8001/mcp"}),
|
||||
]
|
||||
|
||||
async with MCPServerManager(servers) as manager:
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
instructions="Use MCP tools when they help.",
|
||||
mcp_servers=manager.active_servers,
|
||||
)
|
||||
result = await Runner.run(agent, "Which MCP tools are available?")
|
||||
print(result.final_output)
|
||||
```
|
||||
|
||||
주요 동작:
|
||||
|
||||
- `drop_failed_servers=True`(기본값)인 경우 `active_servers`에는 성공적으로 연결된 서버만 포함됩니다.
|
||||
- 실패는 `failed_servers`와 `errors`에 추적됩니다.
|
||||
- 첫 번째 연결 실패 시 오류를 발생시키려면 `strict=True`를 설정하세요.
|
||||
- 실패한 서버를 다시 시도하려면 `reconnect(failed_only=True)`를 호출하고, 모든 서버를 다시 시작하려면 `reconnect(failed_only=False)`를 호출하세요.
|
||||
- 수명 주기 동작을 조정하려면 `connect_timeout_seconds`, `cleanup_timeout_seconds`, `connect_in_parallel`을 사용하세요.
|
||||
|
||||
## 공통 서버 기능
|
||||
|
||||
아래 섹션은 MCP 서버 전송 방식 전반에 적용됩니다(정확한 API 범위는 서버 클래스에 따라 달라짐).
|
||||
|
||||
## 도구 필터링
|
||||
|
||||
각 MCP 서버는 도구 필터를 지원하므로 에이전트에 필요한 함수만 노출할 수 있습니다. 필터링은 생성 시점에 수행하거나 실행별로 동적으로 수행할 수 있습니다.
|
||||
|
||||
### 정적 도구 필터링
|
||||
|
||||
간단한 허용/차단 목록을 구성하려면 [`create_static_tool_filter`][agents.mcp.create_static_tool_filter]를 사용하세요.
|
||||
|
||||
```python
|
||||
from pathlib import Path
|
||||
|
||||
from agents.mcp import MCPServerStdio, create_static_tool_filter
|
||||
|
||||
samples_dir = Path("/path/to/files")
|
||||
|
||||
filesystem_server = MCPServerStdio(
|
||||
params={
|
||||
"command": "npx",
|
||||
"args": ["-y", "@modelcontextprotocol/server-filesystem", str(samples_dir)],
|
||||
},
|
||||
tool_filter=create_static_tool_filter(allowed_tool_names=["read_file", "write_file"]),
|
||||
)
|
||||
```
|
||||
|
||||
`allowed_tool_names`와 `blocked_tool_names`가 모두 제공되면 SDK는 허용 목록을 먼저 적용한 뒤 남은 집합에서 차단된 도구를 제거합니다.
|
||||
|
||||
### 동적 도구 필터링
|
||||
|
||||
더 복잡한 로직의 경우 [`ToolFilterContext`][agents.mcp.ToolFilterContext]를 받는 호출 가능 객체를 전달하세요. 호출 가능 객체는 동기 또는 비동기일 수 있으며, 도구를 노출해야 할 때 `True`를 반환합니다.
|
||||
|
||||
```python
|
||||
from pathlib import Path
|
||||
|
||||
from agents.mcp import MCPServerStdio, ToolFilterContext
|
||||
|
||||
samples_dir = Path("/path/to/files")
|
||||
|
||||
async def context_aware_filter(context: ToolFilterContext, tool) -> bool:
|
||||
if context.agent.name == "Code Reviewer" and tool.name.startswith("danger_"):
|
||||
return False
|
||||
return True
|
||||
|
||||
async with MCPServerStdio(
|
||||
params={
|
||||
"command": "npx",
|
||||
"args": ["-y", "@modelcontextprotocol/server-filesystem", str(samples_dir)],
|
||||
},
|
||||
tool_filter=context_aware_filter,
|
||||
) as server:
|
||||
...
|
||||
```
|
||||
|
||||
필터 컨텍스트는 활성 `run_context`, 도구를 요청하는 `agent`, 그리고 `server_name`을 노출합니다.
|
||||
|
||||
## 프롬프트
|
||||
|
||||
MCP 서버는 에이전트 지침을 동적으로 생성하는 프롬프트도 제공할 수 있습니다. 프롬프트를 지원하는 서버는 두 가지
|
||||
메서드를 노출합니다.
|
||||
|
||||
- `list_prompts()`는 사용 가능한 프롬프트 템플릿을 열거합니다.
|
||||
- `get_prompt(name, arguments)`는 매개변수를 선택적으로 포함하여 구체적인 프롬프트를 가져옵니다.
|
||||
|
||||
```python
|
||||
from agents import Agent
|
||||
|
||||
prompt_result = await server.get_prompt(
|
||||
"generate_code_review_instructions",
|
||||
{"focus": "security vulnerabilities", "language": "python"},
|
||||
)
|
||||
instructions = prompt_result.messages[0].content.text
|
||||
|
||||
agent = Agent(
|
||||
name="Code Reviewer",
|
||||
instructions=instructions,
|
||||
mcp_servers=[server],
|
||||
)
|
||||
```
|
||||
|
||||
## 캐싱
|
||||
|
||||
각 에이전트 실행은 모든 MCP 서버에서 `list_tools()`를 호출합니다. 원격 서버는 눈에 띄는 지연 시간을 유발할 수 있으므로, 모든 MCP 서버 클래스는 `cache_tools_list` 옵션을 노출합니다. 도구 정의가 자주 변경되지 않는다고 확신하는 경우에만 이를 `True`로 설정하세요. 나중에 최신 목록을 강제로 가져오려면 서버 인스턴스에서 `invalidate_tools_cache()`를 호출하세요.
|
||||
|
||||
## 트레이싱
|
||||
|
||||
[트레이싱](./tracing.md)은 다음을 포함한 MCP 활동을 자동으로 캡처합니다.
|
||||
|
||||
1. 도구 목록을 나열하기 위한 MCP 서버 호출
|
||||
2. 도구 호출의 MCP 관련 정보
|
||||
|
||||

|
||||
|
||||
## 추가 자료
|
||||
|
||||
- [Model Context Protocol](https://modelcontextprotocol.io/) – 사양 및 설계 가이드
|
||||
- [examples/mcp](https://github.com/openai/openai-agents-python/tree/main/examples/mcp) – 실행 가능한 stdio, SSE 및 Streamable HTTP 샘플
|
||||
- [examples/hosted_mcp](https://github.com/openai/openai-agents-python/tree/main/examples/hosted_mcp) – 승인 및 커넥터를 포함한 완전한 호스티드 MCP 데모
|
||||
@@ -0,0 +1,674 @@
|
||||
---
|
||||
search:
|
||||
exclude: true
|
||||
---
|
||||
# 모델
|
||||
|
||||
Agents SDK는 두 가지 방식으로 OpenAI 모델을 기본 지원합니다.
|
||||
|
||||
- **권장**: 새로운 [Responses API](https://platform.openai.com/docs/api-reference/responses)를 사용하여 OpenAI API를 호출하는 [`OpenAIResponsesModel`][agents.models.openai_responses.OpenAIResponsesModel]
|
||||
- [Chat Completions API](https://platform.openai.com/docs/api-reference/chat)를 사용하여 OpenAI API를 호출하는 [`OpenAIChatCompletionsModel`][agents.models.openai_chatcompletions.OpenAIChatCompletionsModel]
|
||||
|
||||
## 모델 설정 선택
|
||||
|
||||
설정에 맞는 가장 간단한 경로부터 시작하세요.
|
||||
|
||||
| 하려는 작업 | 권장 경로 | 자세히 보기 |
|
||||
| --- | --- | --- |
|
||||
| OpenAI 모델만 사용 | Responses 모델 경로와 함께 기본 OpenAI 공급자 사용 | [OpenAI 모델](#openai-models) |
|
||||
| WebSocket 전송을 통해 OpenAI Responses API 사용 | Responses 모델 경로를 유지하고 WebSocket 전송 활성화 | [Responses WebSocket 전송](#responses-websocket-transport) |
|
||||
| OpenAI에서 호스팅되는 서브에이전트 사용 | 실험적 호스티드 멀티 에이전트 모델 사용 | [호스티드 멀티 에이전트](#hosted-multi-agent-experimental) |
|
||||
| OpenAI 이외의 공급자 하나 사용 | 기본 제공 공급자 통합 지점으로 시작 | [OpenAI 이외의 모델](#non-openai-models) |
|
||||
| 에이전트 간에 모델 또는 공급자 혼합 | 실행별 또는 에이전트별로 공급자를 선택하고 기능 차이 검토 | [하나의 워크플로에서 모델 혼합](#mixing-models-in-one-workflow) 및 [공급자 간 모델 혼합](#mixing-models-across-providers) |
|
||||
| 고급 OpenAI Responses 요청 설정 조정 | OpenAI Responses 경로에서 `ModelSettings` 사용 | [고급 OpenAI Responses 설정](#advanced-openai-responses-settings) |
|
||||
| OpenAI 이외의 공급자 또는 혼합 공급자 라우팅에 서드파티 어댑터 사용 | 지원되는 베타 어댑터를 비교하고 출시할 공급자 경로 검증 | [서드파티 어댑터](#third-party-adapters) |
|
||||
|
||||
## OpenAI 모델
|
||||
|
||||
OpenAI만 사용하는 대부분의 앱에서는 기본 OpenAI 공급자와 문자열 모델 이름을 사용하고 Responses 모델 경로를 유지하는 방식을 권장합니다.
|
||||
|
||||
`Agent`를 초기화할 때 모델을 지정하지 않으면 기본 모델이 사용됩니다. 현재 기본 모델은 지연 시간이 짧은 에이전트 워크플로를 위해 `reasoning.effort="none"` 및 `verbosity="low"`로 설정된 [`gpt-5.4-mini`](https://developers.openai.com/api/docs/models/gpt-5.4-mini)입니다. 사용 권한이 있다면 명시적인 `model_settings`를 유지하면서 더 높은 품질을 얻을 수 있도록 에이전트를 `gpt-5.6-sol`로 설정하는 것을 권장합니다.
|
||||
|
||||
`gpt-5.6-sol`과 같은 다른 모델로 전환하려면 두 가지 방법으로 에이전트를 구성할 수 있습니다.
|
||||
|
||||
### 기본 모델
|
||||
|
||||
첫째, 사용자 지정 모델을 설정하지 않은 모든 에이전트에서 특정 모델을 일관되게 사용하려면 에이전트를 실행하기 전에 `OPENAI_DEFAULT_MODEL` 환경 변수를 설정하세요.
|
||||
|
||||
```bash
|
||||
export OPENAI_DEFAULT_MODEL=gpt-5.6-sol
|
||||
python3 my_awesome_agent.py
|
||||
```
|
||||
|
||||
둘째, `RunConfig`를 통해 실행의 기본 모델을 설정할 수 있습니다. 에이전트에 모델을 설정하지 않으면 이 실행의 모델이 사용됩니다.
|
||||
|
||||
```python
|
||||
from agents import Agent, RunConfig, Runner
|
||||
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
instructions="You're a helpful agent.",
|
||||
)
|
||||
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
"Hello",
|
||||
run_config=RunConfig(model="gpt-5.6-sol"),
|
||||
)
|
||||
```
|
||||
|
||||
#### GPT-5 모델
|
||||
|
||||
이 방식으로 `gpt-5.6-sol`과 같은 GPT-5 모델을 사용하면 SDK가 기본 `ModelSettings`를 적용합니다. 대부분의 사용 사례에 가장 적합한 설정이 적용됩니다. 기본 모델의 추론 강도를 조정하려면 자체 `ModelSettings`를 전달하세요.
|
||||
|
||||
```python
|
||||
from openai.types.shared import Reasoning
|
||||
from agents import Agent, ModelSettings
|
||||
|
||||
my_agent = Agent(
|
||||
name="My Agent",
|
||||
instructions="You're a helpful agent.",
|
||||
# If OPENAI_DEFAULT_MODEL=gpt-5.6-sol is set, passing only model_settings works.
|
||||
# It's also fine to pass a GPT-5 model name explicitly:
|
||||
model="gpt-5.6-sol",
|
||||
model_settings=ModelSettings(reasoning=Reasoning(effort="high"), verbosity="low")
|
||||
)
|
||||
```
|
||||
|
||||
지연 시간을 줄이려면 GPT-5 모델에 `reasoning.effort="none"`을 사용하는 것이 좋습니다.
|
||||
|
||||
GPT-5.6은 기존 `reasoning` 설정을 통해 추론 모드, 지속되는 추론 컨텍스트, `"max"` 강도 수준도 지원합니다. 이러한 제어 기능은 Responses API 경로에서 사용할 수 있습니다.
|
||||
|
||||
```python
|
||||
from openai.types.shared import Reasoning
|
||||
from agents import Agent, ModelSettings
|
||||
|
||||
agent = Agent(
|
||||
name="Deep research agent",
|
||||
model="gpt-5.6-sol",
|
||||
model_settings=ModelSettings(
|
||||
reasoning=Reasoning(
|
||||
mode="pro",
|
||||
effort="max",
|
||||
context="all_turns",
|
||||
),
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
`reasoning.mode`와 `reasoning.context`는 Responses 전용 설정입니다. Chat Completions는 `reasoning.effort`만 사용하며, 지원되는 강도 수준은 모델과 API 인터페이스에 따라 달라집니다. GPT-5.6의 `"max"` 강도에는 Responses API를 사용하세요. Chat Completions 어댑터는 경고와 함께 모드와 컨텍스트를 무시합니다. 이 경고를 오류로 전환하려면 OpenAI 공급자에서 `strict_feature_validation=True`를 설정하세요.
|
||||
|
||||
`context="all_turns"`를 사용할 때는 `previous_response_id`, 서버 측 대화 또는 이전 추론 항목 재생을 통해 대화를 보존하세요. 상태 비저장 `store=False` 호출에서는 응답에 `reasoning.encrypted_content`를 포함하고 다음 요청에서 해당 추론 항목을 다시 재생하세요.
|
||||
|
||||
#### ComputerTool 모델 선택
|
||||
|
||||
에이전트에 [`ComputerTool`][agents.tool.ComputerTool]이 포함되어 있으면 실제 Responses 요청의 유효 모델에 따라 SDK가 전송하는 컴퓨터 도구 페이로드가 결정됩니다. 명시적인 `gpt-5.5` 요청은 GA 기본 제공 `computer` 도구를 사용하고, 명시적인 `computer-use-preview` 요청은 이전 `computer_use_preview` 페이로드를 유지합니다.
|
||||
|
||||
프롬프트 관리형 호출은 주요 예외입니다. 프롬프트 템플릿이 모델을 소유하고 SDK가 요청에서 `model`을 생략하면, SDK는 프롬프트에 고정된 모델을 추측하지 않도록 프리뷰 호환 컴퓨터 페이로드를 기본값으로 사용합니다. 이 흐름에서 GA 경로를 유지하려면 요청에 `model="gpt-5.5"`를 명시하거나 `ModelSettings(tool_choice="computer")` 또는 `ModelSettings(tool_choice="computer_use")`를 사용해 GA 선택기를 강제하세요.
|
||||
|
||||
등록된 [`ComputerTool`][agents.tool.ComputerTool]이 있으면 `tool_choice="computer"`, `"computer_use"`, `"computer_use_preview"`가 유효 요청 모델과 일치하는 기본 제공 선택기로 정규화됩니다. 등록된 `ComputerTool`이 없으면 이 문자열들은 계속 일반 함수 이름처럼 동작합니다.
|
||||
|
||||
프리뷰 호환 요청은 `environment`와 디스플레이 크기를 미리 직렬화해야 하므로, [`ComputerProvider`][agents.tool.ComputerProvider] 팩토리를 사용하는 프롬프트 관리형 흐름에서는 구체적인 `Computer` 또는 `AsyncComputer` 인스턴스를 전달하거나 요청을 보내기 전에 GA 선택기를 강제해야 합니다. 전체 마이그레이션 세부 정보는 [도구](../tools.md#computertool-and-the-responses-computer-tool)를 참조하세요.
|
||||
|
||||
#### GPT-5 이외의 모델
|
||||
|
||||
사용자 지정 `model_settings` 없이 GPT-5 이외의 모델 이름을 전달하면 SDK는 모든 모델과 호환되는 일반 `ModelSettings`로 되돌아갑니다.
|
||||
|
||||
### Responses 전용 도구 검색 기능
|
||||
|
||||
다음 도구 기능은 OpenAI Responses 모델에서만 지원됩니다.
|
||||
|
||||
- [`ToolSearchTool`][agents.tool.ToolSearchTool]
|
||||
- [`tool_namespace()`][agents.tool.tool_namespace]
|
||||
- `@function_tool(defer_loading=True)` 및 기타 지연 로딩 Responses 도구 인터페이스
|
||||
|
||||
이 기능들은 Chat Completions 모델과 Responses 이외의 백엔드에서는 거부됩니다. 지연 로딩 도구를 사용할 때는 에이전트에 `ToolSearchTool()`을 추가하고, 단순 네임스페이스 이름이나 지연 전용 함수 이름을 강제하는 대신 모델이 `auto` 또는 `required` 도구 선택을 통해 도구를 로드하도록 하세요. 설정 세부 정보와 현재 제약 사항은 [도구](../tools.md#hosted-tool-search)를 참조하세요.
|
||||
|
||||
### Responses WebSocket 전송
|
||||
|
||||
기본적으로 OpenAI Responses API 요청은 HTTP 전송을 사용합니다. OpenAI 기반 모델을 사용할 때 WebSocket 전송을 선택적으로 활성화할 수 있습니다.
|
||||
|
||||
#### 기본 설정
|
||||
|
||||
```python
|
||||
from agents import set_default_openai_responses_transport
|
||||
|
||||
set_default_openai_responses_transport("websocket")
|
||||
```
|
||||
|
||||
이는 기본 OpenAI 공급자가 해석하는 OpenAI Responses 모델에 영향을 주며, `"gpt-5.6-sol"`과 같은 문자열 모델 이름도 포함됩니다.
|
||||
|
||||
SDK가 모델 이름을 모델 인스턴스로 해석할 때 전송 방식이 선택됩니다. 구체적인 [`Model`][agents.models.interface.Model] 객체를 전달하면 해당 전송 방식은 이미 고정되어 있습니다. [`OpenAIResponsesWSModel`][agents.models.openai_responses.OpenAIResponsesWSModel]은 WebSocket을 사용하고, [`OpenAIResponsesModel`][agents.models.openai_responses.OpenAIResponsesModel]은 HTTP를 사용하며, [`OpenAIChatCompletionsModel`][agents.models.openai_chatcompletions.OpenAIChatCompletionsModel]은 Chat Completions를 계속 사용합니다. `RunConfig(model_provider=...)`를 전달하면 전역 기본값 대신 해당 공급자가 전송 방식 선택을 제어합니다.
|
||||
|
||||
#### 공급자 또는 실행 수준 설정
|
||||
|
||||
공급자별 또는 실행별로 WebSocket 전송을 구성할 수도 있습니다.
|
||||
|
||||
```python
|
||||
from agents import Agent, OpenAIProvider, RunConfig, Runner
|
||||
|
||||
provider = OpenAIProvider(
|
||||
use_responses_websocket=True,
|
||||
# Optional; if omitted, OPENAI_WEBSOCKET_BASE_URL is used when set.
|
||||
websocket_base_url="wss://your-proxy.example/v1",
|
||||
# Optional low-level websocket keepalive settings.
|
||||
responses_websocket_options={"ping_interval": 20.0, "ping_timeout": 60.0},
|
||||
)
|
||||
|
||||
agent = Agent(name="Assistant")
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
"Hello",
|
||||
run_config=RunConfig(model_provider=provider),
|
||||
)
|
||||
```
|
||||
|
||||
OpenAI 기반 공급자는 선택적인 에이전트 등록 구성도 허용합니다. 이는 OpenAI 설정에서 하네스 ID와 같은 공급자 수준 등록 메타데이터가 필요한 경우를 위한 고급 옵션입니다.
|
||||
|
||||
```python
|
||||
from agents import (
|
||||
Agent,
|
||||
OpenAIAgentRegistrationConfig,
|
||||
OpenAIProvider,
|
||||
RunConfig,
|
||||
Runner,
|
||||
)
|
||||
|
||||
provider = OpenAIProvider(
|
||||
use_responses_websocket=True,
|
||||
agent_registration=OpenAIAgentRegistrationConfig(harness_id="your-harness-id"),
|
||||
)
|
||||
|
||||
agent = Agent(name="Assistant")
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
"Hello",
|
||||
run_config=RunConfig(model_provider=provider),
|
||||
)
|
||||
```
|
||||
|
||||
#### `MultiProvider`를 사용한 고급 라우팅
|
||||
|
||||
접두사 기반 모델 라우팅이 필요한 경우, 예를 들어 한 실행에서 `openai/...` 및 `any-llm/...` 모델 이름을 혼합하려면 [`MultiProvider`][agents.MultiProvider]를 사용하고 여기에서 `openai_use_responses_websocket=True`를 설정하세요.
|
||||
|
||||
`MultiProvider`는 다음 두 가지 기존 기본 동작을 유지합니다.
|
||||
|
||||
- `openai/...`는 OpenAI 공급자의 별칭으로 처리되므로 `openai/gpt-4.1`은 모델 `gpt-4.1`로 라우팅됩니다.
|
||||
- 알 수 없는 접두사는 그대로 전달되지 않고 `UserError`를 발생시킵니다.
|
||||
|
||||
OpenAI 공급자가 리터럴 네임스페이스 모델 ID를 요구하는 OpenAI 호환 엔드포인트를 가리키는 경우, 명시적으로 그대로 전달하는 동작을 활성화하세요. WebSocket이 활성화된 설정에서는 `MultiProvider`에도 `openai_use_responses_websocket=True`를 유지하세요.
|
||||
|
||||
```python
|
||||
from agents import Agent, MultiProvider, RunConfig, Runner
|
||||
|
||||
provider = MultiProvider(
|
||||
openai_base_url="https://openrouter.ai/api/v1",
|
||||
openai_api_key="...",
|
||||
openai_use_responses_websocket=True,
|
||||
openai_prefix_mode="model_id",
|
||||
unknown_prefix_mode="model_id",
|
||||
)
|
||||
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
instructions="Be concise.",
|
||||
model="openai/gpt-4.1",
|
||||
)
|
||||
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
"Hello",
|
||||
run_config=RunConfig(model_provider=provider),
|
||||
)
|
||||
```
|
||||
|
||||
백엔드가 리터럴 `openai/...` 문자열을 요구하는 경우 `openai_prefix_mode="model_id"`를 사용하세요. 백엔드가 `openrouter/openai/gpt-4.1-mini`와 같은 다른 네임스페이스 모델 ID를 요구하는 경우 `unknown_prefix_mode="model_id"`를 사용하세요. 이러한 옵션은 WebSocket 전송 외부의 `MultiProvider`에서도 작동합니다. 이 예제에서는 이 섹션에서 설명하는 전송 설정의 일부이므로 WebSocket을 활성화한 상태로 유지합니다. 같은 옵션은 [`responses_websocket_session()`][agents.responses_websocket_session]에서도 사용할 수 있습니다.
|
||||
|
||||
`MultiProvider`를 통해 라우팅하면서 동일한 공급자 수준 등록 메타데이터가 필요한 경우 `openai_agent_registration=OpenAIAgentRegistrationConfig(...)`를 전달하면 기본 OpenAI 공급자로 전달됩니다.
|
||||
|
||||
사용자 지정 OpenAI 호환 엔드포인트나 프록시를 사용하는 경우 WebSocket 전송에도 호환되는 WebSocket `/responses` 엔드포인트가 필요합니다. 이러한 설정에서는 `websocket_base_url`을 명시적으로 설정해야 할 수 있습니다.
|
||||
|
||||
#### 참고 사항
|
||||
|
||||
- 이는 WebSocket 전송을 통한 Responses API이며 [Realtime API](../realtime/guide.md)가 아닙니다. Chat Completions 또는 OpenAI 이외의 공급자가 Responses WebSocket `/responses` 엔드포인트를 지원하지 않는 한 해당 공급자에는 적용되지 않습니다.
|
||||
- 환경에 아직 설치되어 있지 않다면 `websockets` 패키지를 설치하세요.
|
||||
- WebSocket 전송을 활성화한 후 [`Runner.run_streamed()`][agents.run.Runner.run_streamed]를 직접 사용할 수 있습니다. 여러 턴과 중첩된 에이전트 도구 호출에서 동일한 WebSocket 연결을 재사용하려는 멀티턴 워크플로에는 [`responses_websocket_session()`][agents.responses_websocket_session] 헬퍼를 권장합니다. [에이전트 실행](../running_agents.md) 가이드와 [`examples/basic/stream_ws.py`](https://github.com/openai/openai-agents-python/tree/main/examples/basic/stream_ws.py)를 참조하세요.
|
||||
- 추론 턴이 길거나 지연 시간 급증이 있는 네트워크에서는 `responses_websocket_options`로 WebSocket 연결 유지 동작을 사용자 지정하세요. 지연된 pong 프레임을 허용하려면 `ping_timeout`을 늘리고, ping은 활성 상태로 유지하면서 하트비트 제한 시간을 비활성화하려면 `ping_timeout=None`을 설정하세요. WebSocket 지연 시간보다 안정성이 더 중요하면 HTTP/SSE 전송을 사용하세요.
|
||||
- 기본적으로 SDK는 수신 메시지 크기 제한을 비활성화합니다(`max_size=None`). 프록시 뒤에서 장기간 실행되는 에이전트 프로세스나 메모리가 제한된 컨테이너에서는 메시지별 메모리 사용량을 제한하도록 `responses_websocket_options={"max_size": 8 * 1024 * 1024}`를 설정하세요.
|
||||
|
||||
### 호스티드 멀티 에이전트(실험적)
|
||||
|
||||
OpenAI Responses API 호스티드 멀티 에이전트 베타를 사용하면 GPT-5.6 루트 모델이 서버에서 호스팅되는 서브에이전트를 생성하고 조정할 수 있습니다. Agents SDK는 기존 `Runner`를 계속 사용할 수 있습니다. 호스티드 오케스트레이션은 서비스에서 수행되고, 개발자가 정의한 함수 도구는 애플리케이션에서 실행됩니다.
|
||||
|
||||
이 통합은 실험적이며, 활성 호스티드 에이전트에 `response.inject`를 사용하여 로컬 함수 출력을 반환할 수 있도록 Responses WebSocket 전송을 사용합니다. `client.beta.responses.connect`를 노출하는 베타 빌드를 포함한 `openai[realtime]>=2.45.0`이 필요합니다. 인터페이스와 베타 항목 스키마는 정식 출시 전에 변경될 수 있습니다.
|
||||
|
||||
#### 모델 구성
|
||||
|
||||
실험적 모듈에서 모델을 가져와 SDK `Agent`에 할당하세요.
|
||||
|
||||
```python
|
||||
from agents import Agent
|
||||
from agents.extensions.experimental.hosted_multi_agent import OpenAIHostedMultiAgentModel
|
||||
|
||||
agent = Agent(
|
||||
name="Research coordinator",
|
||||
instructions="Delegate independent research tasks, then synthesize the findings.",
|
||||
model=OpenAIHostedMultiAgentModel(model="gpt-5.6-sol", config={"max_concurrent_subagents": 3}),
|
||||
)
|
||||
```
|
||||
|
||||
`OpenAIHostedMultiAgentModel`을 생성하면 `multi_agent.enabled`가 활성화되고 `OpenAI-Beta: responses_multi_agent=v1` WebSocket 헤더가 전송됩니다. `openai_client`를 제공하지 않으면 모델은 기본 OpenAI 클라이언트를 사용합니다. `max_concurrent_subagents`를 생략하면 서비스 기본값이 사용됩니다.
|
||||
|
||||
#### 로컬 함수 도구
|
||||
|
||||
모든 호스티드 에이전트는 요청에 구성된 모델과 도구를 공유합니다. Responses API가 어떤 호스티드 에이전트에서 함수를 호출할지 결정합니다. 일반 SDK Runner는 함수를 로컬에서 실행하고 동일한 호출 ID를 가진 `function_call_output`을 활성 WebSocket 응답에 주입합니다. 이를 통해 서비스가 원래 호스티드 호출자를 재개할 수 있습니다. 함수 실행에는 여전히 Runner의 일반 가드레일, 훅, 실패 변환이 적용됩니다. SDK 도구 승인 인터럽션(중단 처리)은 지원되지 않습니다. `needs_approval` 설정이 `False`가 아닌 함수 도구는 요청을 보내기 전에 거부됩니다.
|
||||
|
||||
도구에서 호출자를 인식하는 로깅이나 권한 부여가 필요한 경우 `get_hosted_agent_metadata()`를 사용하세요.
|
||||
|
||||
```python
|
||||
from typing import Any
|
||||
|
||||
from agents import function_tool
|
||||
from agents.extensions.experimental.hosted_multi_agent import get_hosted_agent_metadata
|
||||
from agents.tool_context import ToolContext
|
||||
|
||||
@function_tool
|
||||
def lookup_document(ctx: ToolContext[Any], section: str) -> str:
|
||||
metadata = get_hosted_agent_metadata(ctx)
|
||||
caller = metadata.agent_name if metadata else "unknown"
|
||||
print(f"tool caller: {caller}; call ID: {ctx.tool_call_id}")
|
||||
return f"Contents for {section}"
|
||||
```
|
||||
|
||||
호스티드 에이전트 이름은 관찰용 메타데이터일 뿐이며 로컬 라우팅 메커니즘이 아닙니다. SDK가 제공하는 호출 ID를 사용하여 출력을 라우팅하세요. 부작용이 있는 도구에서는 해당 호출 ID를 멱등성 키로 사용하고, 도구 실행 전이나 도중에 필요한 권한 부여를 애플리케이션 코드에서 적용하세요. 이 모델에서는 `needs_approval`을 사용하지 마세요. 도구 인수와 출력은 Responses API 경계를 통과합니다.
|
||||
|
||||
#### 출력 및 스트리밍 동작
|
||||
|
||||
`final_answer` 단계를 가진 `/root`의 메시지만 일반 최종 메시지가 됩니다. 실험적 어댑터는 상위 수준 `RunResult`에서 서브에이전트 메시지와 호스티드 오케스트레이션 레코드를 필터링합니다. SDK는 이러한 레코드를 로컬 함수로 실행하지 않습니다.
|
||||
|
||||
원문 스트리밍에서는 호스티드 출력 항목과 `response.inject.created` 확인을 포함한 베타 Responses 이벤트가 계속 노출됩니다. 어댑터는 함수 호출이 준비되면 하나의 활성 공급자 응답을 SDK에 표시되는 논리적 모델 턴으로 나누고, Runner가 출력을 생성한 후 같은 공급자 응답을 재개합니다. 원문 호스티드 항목이나 `ToolContext`에서 기여 주체를 검사하려면 `get_hosted_agent_metadata()`를 사용하세요.
|
||||
|
||||
#### SDK 오케스트레이션과의 관계
|
||||
|
||||
호스티드 멀티 에이전트는 SDK 핸드오프 및 Agents-as-tools와 별개입니다.
|
||||
|
||||
- 호스티드 멀티 에이전트는 OpenAI 서비스에서 서브에이전트를 생성합니다. 애플리케이션이 이러한 서브에이전트를 생성하거나 예약하지 않습니다.
|
||||
- SDK 핸드오프는 활성 로컬 SDK `Agent`를 변경합니다. 이 실험적 모델을 사용할 때는 모든 호스티드 에이전트가 같은 핸드오프 도구를 받아 소유권 충돌이 발생하므로 거부됩니다.
|
||||
- Agents-as-tools는 계속 사용할 수 있지만, 이를 사용하면 중첩된 클라이언트 측 및 서버 측 오케스트레이션이 생성됩니다. 추가 지연 시간, 비용, 도구 노출을 신중하게 평가하세요.
|
||||
|
||||
#### 현재 제한 사항
|
||||
|
||||
실험적 모델은 `reasoning.summary`, `max_tool_calls`, 호출자가 제공한 `multi_agent` 또는 `betas` 재정의를 거부합니다. 서비스가 각 호스티드 에이전트 컨텍스트를 독립적으로 자동 압축하므로 명시적인 `context_management.compact_threshold`를 사용할 수 있지만, Responses `/compact` 엔드포인트는 베타에서 지원되지 않습니다.
|
||||
|
||||
하나의 `OpenAIHostedMultiAgentModel` 인스턴스는 한 번에 최대 하나의 활성 호스티드 응답을 소유합니다. 로컬 함수 출력을 기다리는 동안 실행을 중단한 경우 `await model.close()`를 호출하여 WebSocket을 해제하세요. 다른 프로세스나 이벤트 루프에서 진행 중인 호스티드 응답을 복원하는 기능은 현재 지원되지 않습니다.
|
||||
|
||||
기본 Responses API 베타 동작은 [OpenAI 멀티 에이전트 가이드](https://developers.openai.com/api/docs/guides/tools-multi-agent)를 참조하세요. 비스트리밍 및 스트리밍 SDK 사용법은 [`examples/agent_patterns/hosted_multi_agent_beta.py`](https://github.com/openai/openai-agents-python/tree/main/examples/agent_patterns/hosted_multi_agent_beta.py)를 참조하세요.
|
||||
|
||||
## OpenAI 이외의 모델
|
||||
|
||||
OpenAI 이외의 공급자가 필요하면 SDK의 기본 제공 공급자 통합 지점부터 시작하세요. 많은 설정에서는 서드파티 어댑터를 추가하지 않아도 충분합니다. 각 패턴의 예제는 [examples/model_providers](https://github.com/openai/openai-agents-python/tree/main/examples/model_providers/)에 있습니다.
|
||||
|
||||
### OpenAI 이외의 공급자 통합 방법
|
||||
|
||||
| 접근 방식 | 사용 시점 | 범위 |
|
||||
| --- | --- | --- |
|
||||
| [`set_default_openai_client`][agents.set_default_openai_client] | 하나의 OpenAI 호환 엔드포인트가 대부분 또는 모든 에이전트의 기본값이어야 하는 경우 | 전역 기본값 |
|
||||
| [`ModelProvider`][agents.models.interface.ModelProvider] | 하나의 사용자 지정 공급자를 단일 실행에 적용해야 하는 경우 | 실행별 |
|
||||
| [`Agent.model`][agents.agent.Agent.model] | 에이전트마다 서로 다른 공급자 또는 구체적인 모델 객체가 필요한 경우 | 에이전트별 |
|
||||
| 서드파티 어댑터 | 기본 제공 경로에서 제공하지 않는 어댑터 관리형 공급자 지원 범위 또는 라우팅이 필요한 경우 | [서드파티 어댑터](#third-party-adapters) 참조 |
|
||||
|
||||
다음 기본 제공 경로를 사용하여 다른 LLM 공급자를 통합할 수 있습니다.
|
||||
|
||||
1. [`set_default_openai_client`][agents.set_default_openai_client]는 `AsyncOpenAI` 인스턴스를 LLM 클라이언트로 전역에서 사용하려는 경우 유용합니다. LLM 공급자에 OpenAI 호환 API 엔드포인트가 있고 `base_url` 및 `api_key`를 설정할 수 있는 경우에 사용합니다. 구성 가능한 예제는 [examples/model_providers/custom_example_global.py](https://github.com/openai/openai-agents-python/tree/main/examples/model_providers/custom_example_global.py)를 참조하세요.
|
||||
2. [`ModelProvider`][agents.models.interface.ModelProvider]는 `Runner.run` 수준에 적용됩니다. 이를 통해 "이 실행의 모든 에이전트에 사용자 지정 모델 공급자를 사용"하도록 지정할 수 있습니다. 구성 가능한 예제는 [examples/model_providers/custom_example_provider.py](https://github.com/openai/openai-agents-python/tree/main/examples/model_providers/custom_example_provider.py)를 참조하세요.
|
||||
3. [`Agent.model`][agents.agent.Agent.model]을 사용하면 특정 Agent 인스턴스에 모델을 지정할 수 있습니다. 이를 통해 에이전트별로 서로 다른 공급자를 혼합하여 사용할 수 있습니다. 구성 가능한 예제는 [examples/model_providers/custom_example_agent.py](https://github.com/openai/openai-agents-python/tree/main/examples/model_providers/custom_example_agent.py)를 참조하세요.
|
||||
|
||||
`platform.openai.com`의 API 키가 없는 경우 `set_tracing_disabled()`를 통해 트레이싱을 비활성화하거나 [다른 트레이싱 프로세서](../tracing.md)를 설정하는 것을 권장합니다.
|
||||
|
||||
``` python
|
||||
from agents import Agent, AsyncOpenAI, OpenAIChatCompletionsModel, set_tracing_disabled
|
||||
|
||||
set_tracing_disabled(disabled=True)
|
||||
|
||||
client = AsyncOpenAI(api_key="Api_Key", base_url="Base URL of Provider")
|
||||
model = OpenAIChatCompletionsModel(model="Model_Name", openai_client=client)
|
||||
|
||||
agent= Agent(name="Helping Agent", instructions="You are a Helping Agent", model=model)
|
||||
```
|
||||
|
||||
!!! note
|
||||
|
||||
이 예제에서는 아직 많은 LLM 공급자가 Responses API를 지원하지 않으므로 Chat Completions API/모델을 사용합니다. LLM 공급자가 Responses API를 지원하는 경우 Responses 사용을 권장합니다.
|
||||
|
||||
## 하나의 워크플로에서 모델 혼합
|
||||
|
||||
단일 워크플로에서 에이전트마다 서로 다른 모델을 사용할 수 있습니다. 예를 들어 분류에는 더 작고 빠른 모델을 사용하고, 복잡한 작업에는 더 크고 성능이 뛰어난 모델을 사용할 수 있습니다. [`Agent`][agents.Agent]를 구성할 때 다음 방법 중 하나로 특정 모델을 선택할 수 있습니다.
|
||||
|
||||
1. 모델 이름 전달
|
||||
2. 임의의 모델 이름과 해당 이름을 Model 인스턴스에 매핑할 수 있는 [`ModelProvider`][agents.models.interface.ModelProvider] 전달
|
||||
3. [`Model`][agents.models.interface.Model] 구현 직접 제공
|
||||
|
||||
!!! note
|
||||
|
||||
SDK는 [`OpenAIResponsesModel`][agents.models.openai_responses.OpenAIResponsesModel]과 [`OpenAIChatCompletionsModel`][agents.models.openai_chatcompletions.OpenAIChatCompletionsModel] 형식을 모두 지원하지만, 두 형식에서 지원하는 기능과 도구 집합이 다르므로 워크플로마다 하나의 모델 형식을 사용하는 것을 권장합니다. 워크플로에서 여러 모델 형식을 혼합해야 한다면 사용하는 모든 기능을 양쪽에서 모두 지원하는지 확인하세요.
|
||||
|
||||
```python
|
||||
from agents import Agent, Runner, AsyncOpenAI, OpenAIChatCompletionsModel
|
||||
import asyncio
|
||||
|
||||
spanish_agent = Agent(
|
||||
name="Spanish agent",
|
||||
instructions="You only speak Spanish.",
|
||||
model="gpt-5-mini", # (1)!
|
||||
)
|
||||
|
||||
english_agent = Agent(
|
||||
name="English agent",
|
||||
instructions="You only speak English",
|
||||
model=OpenAIChatCompletionsModel( # (2)!
|
||||
model="gpt-5-nano",
|
||||
openai_client=AsyncOpenAI()
|
||||
),
|
||||
)
|
||||
|
||||
triage_agent = Agent(
|
||||
name="Triage agent",
|
||||
instructions="Handoff to the appropriate agent based on the language of the request.",
|
||||
handoffs=[spanish_agent, english_agent],
|
||||
model="gpt-5.6-sol",
|
||||
)
|
||||
|
||||
async def main():
|
||||
result = await Runner.run(triage_agent, input="Hola, ¿cómo estás?")
|
||||
print(result.final_output)
|
||||
```
|
||||
|
||||
1. OpenAI 모델의 이름을 직접 설정합니다.
|
||||
2. [`Model`][agents.models.interface.Model] 구현을 제공합니다.
|
||||
|
||||
에이전트에 사용하는 모델을 추가로 구성하려면 temperature와 같은 선택적 모델 구성 매개변수를 제공하는 [`ModelSettings`][agents.models.interface.ModelSettings]를 전달할 수 있습니다.
|
||||
|
||||
```python
|
||||
from agents import Agent, ModelSettings
|
||||
|
||||
english_agent = Agent(
|
||||
name="English agent",
|
||||
instructions="You only speak English",
|
||||
model="gpt-4.1",
|
||||
model_settings=ModelSettings(temperature=0.1),
|
||||
)
|
||||
```
|
||||
|
||||
## 고급 OpenAI Responses 설정
|
||||
|
||||
OpenAI Responses 경로를 사용하면서 더 세밀한 제어가 필요한 경우 `ModelSettings`부터 시작하세요.
|
||||
|
||||
### 일반적인 고급 `ModelSettings` 옵션
|
||||
|
||||
OpenAI Responses API를 사용할 때 여러 요청 필드는 이미 직접 대응하는 `ModelSettings` 필드를 제공하므로 `extra_args`를 사용할 필요가 없습니다.
|
||||
|
||||
- `parallel_tool_calls`: 같은 턴에서 여러 도구 호출을 허용하거나 금지합니다.
|
||||
- `truncation`: 컨텍스트가 넘칠 때 실패하는 대신 Responses API가 가장 오래된 대화 항목을 제거하도록 `"auto"`를 설정합니다.
|
||||
- `store`: 생성된 응답을 나중에 가져올 수 있도록 서버 측에 저장할지 제어합니다. 이는 응답 ID에 의존하는 후속 워크플로와 `store=False`일 때 로컬 입력으로 대체해야 할 수 있는 세션 압축 흐름에 중요합니다.
|
||||
- `context_management`: `compact_threshold`를 사용하는 Responses 압축과 같은 서버 측 컨텍스트 처리를 구성합니다.
|
||||
- `prompt_cache_retention`: 이전 모델 계열의 확장된 보존 기간을 구성합니다. 예를 들어
|
||||
`"24h"`를 사용합니다.
|
||||
- `prompt_cache_options`: 암시적 또는 명시적 프롬프트 캐싱을 선택하고, GPT-5.6에서는 `"30m"` 캐시 TTL을 구성합니다.
|
||||
- `response_include`: `web_search_call.action.sources`, `file_search_call.results`, `reasoning.encrypted_content`와 같은 더 풍부한 응답 페이로드를 요청합니다.
|
||||
- `top_logprobs`: 출력 텍스트의 상위 토큰 logprob를 요청합니다. SDK는 `message.output_text.logprobs`도 자동으로 추가합니다.
|
||||
- `retry`: 모델 호출에 Runner 관리형 재시도 설정을 선택적으로 활성화합니다. [Runner 관리형 재시도](#runner-managed-retries)를 참조하세요.
|
||||
|
||||
```python
|
||||
from agents import Agent, ModelSettings
|
||||
|
||||
research_agent = Agent(
|
||||
name="Research agent",
|
||||
model="gpt-5.6-sol",
|
||||
model_settings=ModelSettings(
|
||||
parallel_tool_calls=False,
|
||||
truncation="auto",
|
||||
store=True,
|
||||
context_management=[{"type": "compaction", "compact_threshold": 200000}],
|
||||
prompt_cache_options={"mode": "explicit", "ttl": "30m"},
|
||||
response_include=["web_search_call.action.sources"],
|
||||
top_logprobs=5,
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
명시적 프롬프트 캐싱을 사용할 때는 재사용 가능한 접두사가 끝나는 콘텐츠 부분에 중단점을 추가하세요. 같은 `ModelSettings.prompt_cache_options` 필드가 Responses 및 Chat Completions 요청에 그대로 전달되며, Chat Completions 변환기는 텍스트, 이미지, 오디오, 파일 콘텐츠 부분의 중단점을 보존합니다.
|
||||
|
||||
```python
|
||||
from agents import Runner
|
||||
|
||||
result = await Runner.run(
|
||||
research_agent,
|
||||
[
|
||||
{
|
||||
"role": "user",
|
||||
"content": [
|
||||
{
|
||||
"type": "input_text",
|
||||
"text": "Reusable background material...",
|
||||
"prompt_cache_breakpoint": {"mode": "explicit"},
|
||||
},
|
||||
{
|
||||
"type": "input_text",
|
||||
"text": "Analyze the latest question.",
|
||||
},
|
||||
],
|
||||
}
|
||||
],
|
||||
)
|
||||
```
|
||||
|
||||
`prompt_cache_retention`은 기존
|
||||
보존 제어를 사용하는 이전 모델 계열에서 계속 사용할 수 있습니다. 직접적인 `ModelSettings` 필드와 같은 키를
|
||||
`extra_args`에서 함께 사용하지 마세요.
|
||||
|
||||
`store=False`를 설정하면 Responses API는 나중에 서버 측에서 가져올 수 있도록 해당 응답을 보관하지 않습니다. 이는 상태 비저장 또는 데이터 무보존 형태의 흐름에 유용하지만, 응답 ID를 재사용하는 기능은 대신 로컬에서 관리되는 상태에 의존해야 합니다. 예를 들어 마지막 응답이 저장되지 않은 경우 [`OpenAIResponsesCompactionSession`][agents.memory.openai_responses_compaction_session.OpenAIResponsesCompactionSession]은 기본 `"auto"` 압축 경로를 입력 기반 압축으로 전환합니다. [세션 가이드](../sessions/index.md#openai-responses-compaction-sessions)를 참조하세요.
|
||||
|
||||
서버 측 압축은 [`OpenAIResponsesCompactionSession`][agents.memory.openai_responses_compaction_session.OpenAIResponsesCompactionSession]과 다릅니다. `context_management=[{"type": "compaction", "compact_threshold": ...}]`는 각 Responses API 요청과 함께 전송되며, 렌더링된 컨텍스트가 임계값을 넘으면 API가 응답의 일부로 압축 항목을 생성할 수 있습니다. `OpenAIResponsesCompactionSession`은 턴 사이에 독립 실행형 `responses.compact` 엔드포인트를 호출하고 로컬 세션 기록을 다시 작성합니다.
|
||||
|
||||
### `extra_args` 전달
|
||||
|
||||
SDK가 아직 최상위 수준에서 직접 노출하지 않는 공급자별 또는 최신 요청 필드가 필요할 때 `extra_args`를 사용하세요.
|
||||
|
||||
또한 OpenAI의 Responses API를 사용할 때는 [몇 가지 다른 선택적 매개변수](https://platform.openai.com/docs/api-reference/responses/create)(예: `user`, `service_tier` 등)가 있습니다. 최상위 수준에서 사용할 수 없는 경우 `extra_args`를 사용하여 전달할 수 있습니다. 같은 요청 필드를 직접적인 `ModelSettings` 필드를 통해 함께 설정하지 마세요.
|
||||
|
||||
```python
|
||||
from agents import Agent, ModelSettings
|
||||
|
||||
english_agent = Agent(
|
||||
name="English agent",
|
||||
instructions="You only speak English",
|
||||
model="gpt-4.1",
|
||||
model_settings=ModelSettings(
|
||||
temperature=0.1,
|
||||
extra_args={"service_tier": "flex", "user": "user_12345"},
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
## Runner 관리형 재시도
|
||||
|
||||
재시도는 런타임 전용이며 선택적으로 활성화해야 합니다. `ModelSettings(retry=...)`를 설정하고 재시도 정책에서 재시도를 선택하지 않는 한 SDK는 일반적인 모델 요청을 재시도하지 않습니다.
|
||||
|
||||
```python
|
||||
from agents import Agent, ModelRetrySettings, ModelSettings, retry_policies
|
||||
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
model="gpt-5.6-sol",
|
||||
model_settings=ModelSettings(
|
||||
retry=ModelRetrySettings(
|
||||
max_retries=4,
|
||||
backoff={
|
||||
"initial_delay": 0.5,
|
||||
"max_delay": 5.0,
|
||||
"multiplier": 2.0,
|
||||
"jitter": True,
|
||||
},
|
||||
policy=retry_policies.any(
|
||||
retry_policies.provider_suggested(),
|
||||
retry_policies.retry_after(),
|
||||
retry_policies.network_error(),
|
||||
retry_policies.http_status([408, 409, 429, 500, 502, 503, 504]),
|
||||
),
|
||||
)
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
`ModelRetrySettings`에는 세 개의 필드가 있습니다.
|
||||
|
||||
<div class="field-table" markdown="1">
|
||||
|
||||
| 필드 | 유형 | 참고 사항 |
|
||||
| --- | --- | --- |
|
||||
| `max_retries` | `int | None` | 최초 요청 이후 허용되는 재시도 횟수 |
|
||||
| `backoff` | `ModelRetryBackoffSettings | dict | None` | 정책이 명시적인 지연 시간을 반환하지 않고 재시도할 때 사용하는 기본 지연 전략입니다. `backoff.max_delay`는 계산된 백오프 지연에만 상한을 적용합니다. 정책이 반환한 명시적 지연이나 retry-after 힌트에는 상한을 적용하지 않습니다. |
|
||||
| `policy` | `RetryPolicy | None` | 재시도 여부를 결정하는 콜백입니다. 이 필드는 런타임 전용이며 직렬화되지 않습니다. |
|
||||
|
||||
</div>
|
||||
|
||||
재시도 정책은 다음 항목이 포함된 [`RetryPolicyContext`][agents.retry.RetryPolicyContext]를 받습니다.
|
||||
|
||||
- `attempt` 및 `max_retries`: 시도 횟수를 고려해 결정할 수 있습니다.
|
||||
- `stream`: 스트리밍 및 비스트리밍 동작을 분기할 수 있습니다.
|
||||
- `error`: 원문 오류를 검사할 수 있습니다.
|
||||
- `normalized`: `status_code`, `retry_after`, `error_code`, `is_network_error`, `is_timeout`, `is_abort`와 같은 정규화된 정보입니다.
|
||||
- `provider_advice`: 기본 모델 어댑터가 재시도 지침을 제공할 수 있을 때 사용됩니다.
|
||||
|
||||
정책은 다음 중 하나를 반환할 수 있습니다.
|
||||
|
||||
- 간단한 재시도 결정을 위한 `True` / `False`
|
||||
- 지연 시간을 재정의하거나 진단 사유를 첨부하려는 경우 [`RetryDecision`][agents.retry.RetryDecision]
|
||||
|
||||
SDK는 `retry_policies`에 미리 준비된 헬퍼를 제공합니다.
|
||||
|
||||
| 헬퍼 | 동작 |
|
||||
| --- | --- |
|
||||
| `retry_policies.never()` | 항상 재시도하지 않습니다. |
|
||||
| `retry_policies.provider_suggested()` | 가능한 경우 공급자의 재시도 지침을 따릅니다. |
|
||||
| `retry_policies.network_error()` | 일시적인 전송 및 제한 시간 실패와 일치합니다. |
|
||||
| `retry_policies.http_status([...])` | 선택한 HTTP 상태 코드와 일치합니다. |
|
||||
| `retry_policies.retry_after()` | retry-after 힌트가 있을 때만 해당 지연 시간을 사용하여 재시도합니다. 이 헬퍼는 retry-after 값을 명시적 정책 지연으로 처리하므로 `backoff.max_delay`의 상한이 적용되지 않습니다. |
|
||||
| `retry_policies.any(...)` | 중첩된 정책 중 하나라도 재시도를 선택하면 재시도합니다. |
|
||||
| `retry_policies.all(...)` | 중첩된 모든 정책이 재시도를 선택할 때만 재시도합니다. |
|
||||
|
||||
정책을 조합할 때 `provider_suggested()`는 공급자가 구분할 수 있는 경우 공급자의 거부와 재생 안전성 승인을 보존하므로 가장 안전한 첫 번째 기본 구성 요소입니다.
|
||||
|
||||
##### 안전 경계
|
||||
|
||||
일부 실패는 자동으로 재시도되지 않습니다.
|
||||
|
||||
- 중단 오류
|
||||
- 공급자 지침에서 재생이 안전하지 않다고 표시한 요청
|
||||
- 이미 출력이 시작되어 재생이 안전하지 않은 스트리밍 실행
|
||||
|
||||
`previous_response_id` 또는 `conversation_id`를 사용하는 상태 저장 후속 요청도 더 보수적으로 처리됩니다. 이러한 요청에서는 `network_error()` 또는 `http_status([500])`와 같은 비공급자 조건만으로 충분하지 않습니다. 재시도 정책에는 일반적으로 `retry_policies.provider_suggested()`를 통한 공급자의 재생 안전 승인이 포함되어야 합니다.
|
||||
|
||||
##### Runner와 에이전트 병합 동작
|
||||
|
||||
`retry`는 Runner 수준과 에이전트 수준의 `ModelSettings` 사이에서 깊은 병합됩니다.
|
||||
|
||||
- 에이전트는 `retry.max_retries`만 재정의하면서 Runner의 `policy`를 상속할 수 있습니다.
|
||||
- 에이전트는 `retry.backoff`의 일부만 재정의하고 Runner의 다른 백오프 필드를 유지할 수 있습니다.
|
||||
- `policy`는 런타임 전용이므로 직렬화된 `ModelSettings`는 `max_retries`와 `backoff`를 유지하지만 콜백 자체는 생략합니다.
|
||||
|
||||
더 자세한 예제는 [`examples/basic/retry.py`](https://github.com/openai/openai-agents-python/tree/main/examples/basic/retry.py)와 [어댑터 기반 재시도 예제](https://github.com/openai/openai-agents-python/tree/main/examples/basic/retry_litellm.py)를 참조하세요.
|
||||
|
||||
## OpenAI 이외의 공급자 문제 해결
|
||||
|
||||
### 트레이싱 클라이언트 오류 401
|
||||
|
||||
트레이싱 관련 오류가 발생하는 이유는 트레이스가 OpenAI 서버에 업로드되지만 OpenAI API 키가 없기 때문입니다. 이를 해결하는 세 가지 방법이 있습니다.
|
||||
|
||||
1. 트레이싱을 완전히 비활성화합니다: [`set_tracing_disabled(True)`][agents.set_tracing_disabled]
|
||||
2. 트레이싱용 OpenAI 키를 설정합니다: [`set_tracing_export_api_key(...)`][agents.set_tracing_export_api_key]. 이 API 키는 트레이스 업로드에만 사용되며 [platform.openai.com](https://platform.openai.com/)에서 발급한 키여야 합니다.
|
||||
3. OpenAI 이외의 트레이스 프로세서를 사용합니다. [트레이싱 문서](../tracing.md#custom-tracing-processors)를 참조하세요.
|
||||
|
||||
### Responses API 지원
|
||||
|
||||
SDK는 기본적으로 Responses API를 사용하지만, 아직 많은 다른 LLM 공급자가 이를 지원하지 않습니다. 그 결과 404 또는 유사한 문제가 발생할 수 있습니다. 이를 해결하는 두 가지 방법이 있습니다.
|
||||
|
||||
1. [`set_default_openai_api("chat_completions")`][agents.set_default_openai_api]를 호출합니다. 환경 변수를 통해 `OPENAI_API_KEY` 및 `OPENAI_BASE_URL`을 설정하는 경우 사용할 수 있습니다.
|
||||
2. [`OpenAIChatCompletionsModel`][agents.models.openai_chatcompletions.OpenAIChatCompletionsModel]을 사용합니다. 예제는 [여기](https://github.com/openai/openai-agents-python/tree/main/examples/model_providers/)에서 확인할 수 있습니다.
|
||||
|
||||
### Chat Completions 호환성 옵션
|
||||
|
||||
Chat Completions를 통해 라우팅하면 SDK는 `previous_response_id`, `conversation_id`, 프롬프트 또는 텍스트 전용이 아닌 도구 출력처럼 Chat Completions에서 전송할 수 없는 Responses 전용 필드를 자동으로 제거하여 호환성을 유지합니다. 개발 중 이러한 불일치가 즉시 실패하도록 하려면 OpenAI 공급자에서 엄격한 기능 검증을 활성화하세요.
|
||||
|
||||
```python
|
||||
from agents import Agent, OpenAIProvider, RunConfig, Runner
|
||||
|
||||
provider = OpenAIProvider(
|
||||
use_responses=False,
|
||||
strict_feature_validation=True,
|
||||
)
|
||||
|
||||
agent = Agent(name="Assistant")
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
"Hello",
|
||||
run_config=RunConfig(model_provider=provider),
|
||||
)
|
||||
```
|
||||
|
||||
[`MultiProvider`][agents.MultiProvider]를 사용하는 경우 대신 `openai_strict_feature_validation=True`를 전달하세요.
|
||||
|
||||
일부 OpenAI 호환 Chat Completions 공급자는 증분 SDK 처리에 충분히 안정적이지 않은 청크로 도구 호출 델타를 스트리밍합니다. 이 경우 스트리밍 도구 호출 버퍼링을 활성화하여 공급자 스트림이 끝난 후에만 SDK가 도구 호출을 생성하도록 하세요.
|
||||
|
||||
```python
|
||||
from agents import OpenAIProvider
|
||||
|
||||
provider = OpenAIProvider(
|
||||
use_responses=False,
|
||||
buffer_streamed_tool_calls=True,
|
||||
)
|
||||
```
|
||||
|
||||
[`MultiProvider`][agents.MultiProvider]에서는 `openai_buffer_streamed_tool_calls=True`를 사용하세요.
|
||||
|
||||
### structured outputs 지원
|
||||
|
||||
일부 모델 공급자는 [structured outputs](https://platform.openai.com/docs/guides/structured-outputs)를 지원하지 않습니다. 이 경우 다음과 유사한 오류가 발생하기도 합니다.
|
||||
|
||||
```
|
||||
|
||||
BadRequestError: Error code: 400 - {'error': {'message': "'response_format.type' : value is not one of the allowed values ['text','json_object']", 'type': 'invalid_request_error'}}
|
||||
|
||||
```
|
||||
|
||||
이는 일부 모델 공급자의 한계입니다. JSON 출력은 지원하지만 출력에 사용할 `json_schema`를 지정할 수는 없습니다. 이 문제를 해결하기 위해 작업 중이지만, JSON 스키마 출력을 지원하는 공급자를 사용할 것을 권장합니다. 그렇지 않으면 잘못된 JSON으로 인해 앱이 자주 중단될 수 있습니다.
|
||||
|
||||
## 공급자 간 모델 혼합
|
||||
|
||||
모델 공급자 간의 기능 차이를 알고 있어야 하며, 그렇지 않으면 오류가 발생할 수 있습니다. 예를 들어 OpenAI는 structured outputs, 멀티모달 입력, 호스티드 파일 검색 및 웹 검색을 지원하지만 다른 많은 공급자는 이러한 기능을 지원하지 않습니다. 다음 제한 사항에 유의하세요.
|
||||
|
||||
- 이해하지 못하는 공급자에게 지원되지 않는 `tools`를 전송하지 마세요
|
||||
- 텍스트 전용 모델을 호출하기 전에 멀티모달 입력을 필터링하세요
|
||||
- 구조화된 JSON 출력을 지원하지 않는 공급자는 때때로 잘못된 JSON을 생성할 수 있다는 점에 유의하세요.
|
||||
|
||||
## 서드파티 어댑터
|
||||
|
||||
SDK의 기본 제공 공급자 통합 지점만으로 충분하지 않을 때만 서드파티 어댑터를 사용하세요. 이 SDK에서 OpenAI 모델만 사용하는 경우 Any-LLM이나 LiteLLM 대신 기본 제공 [`OpenAIResponsesModel`][agents.models.openai_responses.OpenAIResponsesModel] 경로를 사용하세요. 서드파티 어댑터는 OpenAI 모델과 OpenAI 이외의 공급자를 결합해야 하거나 기본 제공 경로에서 제공하지 않는 어댑터 관리형 공급자 지원 범위 또는 라우팅이 필요한 경우에 사용합니다. 어댑터는 SDK와 업스트림 모델 공급자 사이에 또 하나의 호환성 계층을 추가하므로 기능 지원과 요청 의미 체계는 공급자에 따라 달라질 수 있습니다. SDK에는 현재 Any-LLM과 LiteLLM이 최선형 베타 어댑터 통합으로 포함되어 있습니다.
|
||||
|
||||
### Any-LLM
|
||||
|
||||
Any-LLM 관리형 공급자 지원 범위 또는 라우팅이 필요한 경우를 위해 Any-LLM 지원이 최선형 베타로 제공됩니다.
|
||||
|
||||
업스트림 공급자 경로에 따라 Any-LLM은 Responses API, Chat Completions 호환 API 또는 공급자별 호환성 계층을 사용할 수 있습니다.
|
||||
|
||||
Any-LLM이 필요하면 `openai-agents[any-llm]`을 설치한 다음 [`examples/model_providers/any_llm_auto.py`](https://github.com/openai/openai-agents-python/tree/main/examples/model_providers/any_llm_auto.py) 또는 [`examples/model_providers/any_llm_provider.py`](https://github.com/openai/openai-agents-python/tree/main/examples/model_providers/any_llm_provider.py)부터 시작하세요. [`MultiProvider`][agents.MultiProvider]와 함께 `any-llm/...` 모델 이름을 사용하거나, `AnyLLMModel`을 직접 인스턴스화하거나, 실행 범위에서 `AnyLLMProvider`를 사용할 수 있습니다. 모델 인터페이스를 명시적으로 고정해야 하는 경우 `AnyLLMModel`을 생성할 때 `api="responses"` 또는 `api="chat_completions"`를 전달하세요.
|
||||
|
||||
Any-LLM은 서드파티 어댑터 계층이므로 공급자 종속성과 기능 격차는 SDK가 아닌 Any-LLM 업스트림에서 정의됩니다. 업스트림 공급자가 사용량 지표를 반환하면 자동으로 전파되지만, 스트리밍 Chat Completions 백엔드에서 사용량 청크를 생성하려면 `ModelSettings(include_usage=True)`가 필요할 수 있습니다. structured outputs, 도구 호출, 사용량 보고 또는 Responses 전용 동작에 의존한다면 배포할 정확한 공급자 백엔드를 검증하세요.
|
||||
|
||||
### LiteLLM
|
||||
|
||||
LiteLLM별 공급자 지원 범위 또는 라우팅이 필요한 경우를 위해 LiteLLM 지원이 최선형 베타로 제공됩니다.
|
||||
|
||||
LiteLLM이 필요하면 `openai-agents[litellm]`을 설치한 다음 [`examples/model_providers/litellm_auto.py`](https://github.com/openai/openai-agents-python/tree/main/examples/model_providers/litellm_auto.py) 또는 [`examples/model_providers/litellm_provider.py`](https://github.com/openai/openai-agents-python/tree/main/examples/model_providers/litellm_provider.py)부터 시작하세요. `litellm/...` 모델 이름을 사용하거나 [`LitellmModel`][agents.extensions.models.litellm_model.LitellmModel]을 직접 인스턴스화할 수 있습니다.
|
||||
|
||||
일부 LiteLLM 기반 공급자는 기본적으로 SDK 사용량 지표를 채우지 않습니다. 사용량 보고가 필요하면 `ModelSettings(include_usage=True)`를 전달하세요. structured outputs, 도구 호출, 사용량 보고 또는 어댑터별 라우팅 동작에 의존한다면 배포할 정확한 공급자 백엔드를 검증하세요.
|
||||
@@ -0,0 +1,13 @@
|
||||
---
|
||||
search:
|
||||
exclude: true
|
||||
---
|
||||
# LiteLLM
|
||||
|
||||
<script>
|
||||
window.location.replace("../#third-party-adapters");
|
||||
</script>
|
||||
|
||||
이 페이지는 [Models의 서드파티 어댑터 섹션](index.md#third-party-adapters)으로 이동되었습니다.
|
||||
|
||||
자동으로 리디렉션되지 않으면 위 링크를 사용하세요.
|
||||
@@ -0,0 +1,64 @@
|
||||
---
|
||||
search:
|
||||
exclude: true
|
||||
---
|
||||
# 에이전트 오케스트레이션
|
||||
|
||||
오케스트레이션은 앱에서 에이전트가 흐르는 방식을 의미합니다. 어떤 에이전트가 어떤 순서로 실행되며, 다음에 무엇이 일어날지 어떻게 결정할까요? 에이전트를 오케스트레이션하는 주요 방법은 두 가지입니다.
|
||||
|
||||
1. LLM이 결정을 내리도록 허용: LLM의 지능을 사용해 계획하고, 추론하고, 이를 바탕으로 어떤 단계를 수행할지 결정합니다.
|
||||
2. 코드를 통한 오케스트레이션: 코드로 에이전트의 흐름을 결정합니다.
|
||||
|
||||
이러한 패턴은 함께 조합해 사용할 수 있습니다. 각 방식에는 아래에 설명된 고유한 장단점이 있습니다.
|
||||
|
||||
## LLM을 통한 오케스트레이션
|
||||
|
||||
에이전트는 instructions, tools 및 핸드오프를 갖춘 LLM입니다. 즉, 개방형 작업이 주어지면 LLM은 tools를 사용해 작업을 수행하고 데이터를 얻으며, 핸드오프를 사용해 하위 에이전트에게 작업을 위임하면서, 작업을 어떻게 처리할지 자율적으로 계획할 수 있습니다. 예를 들어 연구 에이전트에는 다음과 같은 도구를 장착할 수 있습니다.
|
||||
|
||||
- 온라인에서 정보를 찾기 위한 웹 검색
|
||||
- 독점 데이터와 연결을 검색하기 위한 파일 검색 및 검색
|
||||
- 컴퓨터에서 작업을 수행하기 위한 컴퓨터 사용
|
||||
- 데이터 분석을 위한 코드 실행
|
||||
- 기획, 보고서 작성 등에 뛰어난 전문 에이전트로의 핸드오프
|
||||
|
||||
### 핵심 SDK 패턴
|
||||
|
||||
Python SDK에서는 두 가지 오케스트레이션 패턴이 가장 자주 사용됩니다.
|
||||
|
||||
| 패턴 | 작동 방식 | 가장 적합한 경우 |
|
||||
| --- | --- | --- |
|
||||
| Agents as tools | 관리자 에이전트가 대화의 제어권을 유지하고 `Agent.as_tool()`을 통해 전문 에이전트를 호출합니다. | 하나의 에이전트가 최종 답변을 담당하거나, 여러 전문가의 출력을 결합하거나, 공유 가드레일을 한곳에서 적용하도록 하고 싶을 때 |
|
||||
| 핸드오프 | 트리아지 에이전트가 대화를 전문가에게 라우팅하고, 해당 전문가가 나머지 턴 동안 활성 에이전트가 됩니다. | 전문가가 직접 응답하거나, 프롬프트를 집중된 상태로 유지하거나, 관리자가 결과를 설명하지 않고 instructions를 전환하도록 하고 싶을 때 |
|
||||
|
||||
전문가가 제한된 하위 작업을 도와야 하지만 사용자와 직접 마주하는 대화를 인수해서는 안 되는 경우 **agents as tools**를 사용합니다. 라우팅 자체가 워크플로의 일부이고 선택된 전문가가 상호작용의 다음 부분을 담당하도록 하고 싶을 때는 **핸드오프**를 사용합니다.
|
||||
|
||||
두 가지를 조합할 수도 있습니다. 트리아지 에이전트가 전문가에게 핸드오프할 수 있으며, 해당 전문가는 여전히 좁은 범위의 하위 작업을 위해 다른 에이전트를 도구로 호출할 수 있습니다.
|
||||
|
||||
이 패턴은 작업이 개방형이고 LLM의 지능에 의존하고자 할 때 유용합니다. 여기서 가장 중요한 전략은 다음과 같습니다.
|
||||
|
||||
1. 좋은 프롬프트에 투자합니다. 어떤 도구를 사용할 수 있는지, 어떻게 사용해야 하는지, 어떤 매개변수 범위 내에서 작동해야 하는지 명확히 합니다.
|
||||
2. 앱을 모니터링하고 반복적으로 개선합니다. 문제가 발생하는 지점을 파악하고 프롬프트를 반복 개선합니다.
|
||||
3. 에이전트가 스스로 성찰하고 개선하도록 허용합니다. 예를 들어 루프 안에서 실행하고 스스로 비평하게 하거나, 오류 메시지를 제공하고 개선하게 합니다.
|
||||
4. 무엇이든 잘하도록 기대되는 범용 에이전트보다, 하나의 작업에 탁월한 전문 에이전트를 둡니다.
|
||||
5. [평가](https://platform.openai.com/docs/guides/evals)에 투자합니다. 이를 통해 에이전트를 훈련해 작업 수행 능력을 개선하고 향상시킬 수 있습니다.
|
||||
|
||||
이러한 오케스트레이션 방식의 핵심 SDK 기본 구성 요소를 알고 싶다면 [도구](tools.md), [핸드오프](handoffs.md), [에이전트 실행](running_agents.md)부터 시작하세요.
|
||||
|
||||
## 코드를 통한 오케스트레이션
|
||||
|
||||
LLM을 통한 오케스트레이션은 강력하지만, 코드를 통한 오케스트레이션은 속도, 비용, 성능 측면에서 작업을 더 결정적이고 예측 가능하게 만듭니다. 여기서 흔히 사용되는 패턴은 다음과 같습니다.
|
||||
|
||||
- [structured outputs](https://platform.openai.com/docs/guides/structured-outputs)를 사용해 코드로 검사할 수 있는 적절한 형식의 데이터를 생성합니다. 예를 들어 에이전트에게 작업을 몇 가지 카테고리로 분류하게 한 다음, 해당 카테고리를 바탕으로 다음 에이전트를 선택할 수 있습니다.
|
||||
- 하나의 에이전트 출력을 다음 에이전트의 입력으로 변환해 여러 에이전트를 체이닝합니다. 블로그 게시물 작성 같은 작업을 연구하기, 개요 작성하기, 블로그 게시물 작성하기, 비평하기, 개선하기와 같은 일련의 단계로 분해할 수 있습니다.
|
||||
- 평가하고 피드백을 제공하는 에이전트와 함께, 작업을 수행하는 에이전트를 `while` 루프에서 실행하여 평가자가 출력이 특정 기준을 통과했다고 말할 때까지 반복합니다.
|
||||
- 여러 에이전트를 병렬로 실행합니다. 예를 들어 `asyncio.gather` 같은 Python 기본 구성 요소를 사용할 수 있습니다. 서로 의존하지 않는 여러 작업이 있을 때 속도 측면에서 유용합니다.
|
||||
|
||||
[`examples/agent_patterns`](https://github.com/openai/openai-agents-python/tree/main/examples/agent_patterns)에 여러 코드 예제가 있습니다.
|
||||
|
||||
## 관련 가이드
|
||||
|
||||
- 구성 패턴과 에이전트 설정은 [에이전트](agents.md)를 참조하세요.
|
||||
- `Agent.as_tool()` 및 관리자 스타일 오케스트레이션은 [도구](tools.md#agents-as-tools)를 참조하세요.
|
||||
- 전문 에이전트 간 위임은 [핸드오프](handoffs.md)를 참조하세요.
|
||||
- 실행별 오케스트레이션 제어와 대화 상태는 [에이전트 실행](running_agents.md)을 참조하세요.
|
||||
- 최소한의 엔드투엔드 핸드오프 예제는 [빠른 시작](quickstart.md)을 참조하세요.
|
||||
@@ -0,0 +1,225 @@
|
||||
---
|
||||
search:
|
||||
exclude: true
|
||||
---
|
||||
# 빠른 시작
|
||||
|
||||
## 프로젝트 및 가상 환경 생성
|
||||
|
||||
이 작업은 한 번만 수행하면 됩니다.
|
||||
|
||||
```bash
|
||||
mkdir my_project
|
||||
cd my_project
|
||||
python -m venv .venv
|
||||
```
|
||||
|
||||
### 가상 환경 활성화
|
||||
|
||||
새 터미널 세션을 시작할 때마다 이 작업을 수행하세요.
|
||||
|
||||
macOS 또는 Linux:
|
||||
|
||||
```bash
|
||||
source .venv/bin/activate
|
||||
```
|
||||
|
||||
Windows:
|
||||
|
||||
```cmd
|
||||
.venv\Scripts\activate
|
||||
```
|
||||
|
||||
### Agents SDK 설치
|
||||
|
||||
```bash
|
||||
pip install openai-agents # or `uv add openai-agents`, etc
|
||||
```
|
||||
|
||||
### OpenAI API 키 설정
|
||||
|
||||
API 키가 없다면 [이 지침](https://platform.openai.com/docs/quickstart#create-and-export-an-api-key)에 따라 OpenAI API 키를 생성하세요.
|
||||
|
||||
이 명령은 현재 터미널 세션에 키를 설정합니다.
|
||||
|
||||
macOS 또는 Linux:
|
||||
|
||||
```bash
|
||||
export OPENAI_API_KEY=sk-...
|
||||
```
|
||||
|
||||
Windows PowerShell:
|
||||
|
||||
```powershell
|
||||
$env:OPENAI_API_KEY = "sk-..."
|
||||
```
|
||||
|
||||
Windows Command Prompt:
|
||||
|
||||
```cmd
|
||||
set "OPENAI_API_KEY=sk-..."
|
||||
```
|
||||
|
||||
## 첫 에이전트 생성
|
||||
|
||||
에이전트는 instructions, 이름, 특정 모델과 같은 선택적 구성으로 정의됩니다.
|
||||
|
||||
```python
|
||||
from agents import Agent
|
||||
|
||||
agent = Agent(
|
||||
name="History Tutor",
|
||||
instructions="You answer history questions clearly and concisely.",
|
||||
)
|
||||
```
|
||||
|
||||
## 첫 에이전트 실행
|
||||
|
||||
[`Runner`][agents.run.Runner]를 사용해 에이전트를 실행하고 [`RunResult`][agents.result.RunResult]를 반환받습니다.
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
from agents import Agent, Runner
|
||||
|
||||
agent = Agent(
|
||||
name="History Tutor",
|
||||
instructions="You answer history questions clearly and concisely.",
|
||||
)
|
||||
|
||||
async def main():
|
||||
result = await Runner.run(agent, "When did the Roman Empire fall?")
|
||||
print(result.final_output)
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
두 번째 턴에서는 `result.to_input_list()`를 다시 `Runner.run(...)`에 전달하거나, [세션](sessions/index.md)을 연결하거나, `conversation_id` / `previous_response_id`를 사용해 OpenAI 서버 관리 상태를 재사용할 수 있습니다. [에이전트 실행](running_agents.md) 가이드에서는 이러한 접근 방식을 비교합니다.
|
||||
|
||||
다음 경험칙을 사용하세요.
|
||||
|
||||
| 원하는 경우... | 시작점... |
|
||||
| --- | --- |
|
||||
| 전체 수동 제어와 제공자에 독립적인 기록 | `result.to_input_list()` |
|
||||
| SDK가 기록을 로드하고 저장하도록 함 | [`session=...`](sessions/index.md) |
|
||||
| OpenAI가 관리하는 서버 측 이어가기 | `previous_response_id` 또는 `conversation_id` |
|
||||
|
||||
절충점과 정확한 동작은 [에이전트 실행](running_agents.md#choose-a-memory-strategy)을 참고하세요.
|
||||
|
||||
작업이 주로 프롬프트, 도구, 대화 상태 안에서 이루어진다면 일반 `Agent`와 `Runner`를 사용하세요. 에이전트가 격리된 워크스페이스에서 실제 파일을 검사하거나 수정해야 한다면 [샌드박스 에이전트 빠른 시작](sandbox_agents.md)으로 이동하세요.
|
||||
|
||||
## 에이전트에 도구 제공
|
||||
|
||||
에이전트에 정보를 조회하거나 작업을 수행할 수 있는 도구를 제공할 수 있습니다.
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
from agents import Agent, Runner, function_tool
|
||||
|
||||
|
||||
@function_tool
|
||||
def history_fun_fact() -> str:
|
||||
"""Return a short history fact."""
|
||||
return "Sharks are older than trees."
|
||||
|
||||
|
||||
agent = Agent(
|
||||
name="History Tutor",
|
||||
instructions="Answer history questions clearly. Use history_fun_fact when it helps.",
|
||||
tools=[history_fun_fact],
|
||||
)
|
||||
|
||||
|
||||
async def main():
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
"Tell me something surprising about ancient life on Earth.",
|
||||
)
|
||||
print(result.final_output)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
## 에이전트 몇 개 더 추가
|
||||
|
||||
멀티 에이전트 패턴을 선택하기 전에, 최종 답변을 누가 담당할지 결정하세요.
|
||||
|
||||
- **핸드오프**: 해당 턴의 해당 부분에 대해 전문가가 대화를 이어받습니다.
|
||||
- **Agents as tools**: 오케스트레이터가 제어를 유지하며 전문가를 도구로 호출합니다.
|
||||
|
||||
이 빠른 시작에서는 첫 예제로 가장 짧기 때문에 **핸드오프**를 계속 사용합니다. 매니저 스타일 패턴은 [에이전트 오케스트레이션](multi_agent.md) 및 [도구: agents as tools](tools.md#agents-as-tools)를 참고하세요.
|
||||
|
||||
추가 에이전트도 같은 방식으로 정의할 수 있습니다. `handoff_description`은 라우팅 에이전트가 언제 위임할지에 대한 추가 컨텍스트를 제공합니다.
|
||||
|
||||
```python
|
||||
from agents import Agent
|
||||
|
||||
history_tutor_agent = Agent(
|
||||
name="History Tutor",
|
||||
handoff_description="Specialist agent for historical questions",
|
||||
instructions="You answer history questions clearly and concisely.",
|
||||
)
|
||||
|
||||
math_tutor_agent = Agent(
|
||||
name="Math Tutor",
|
||||
handoff_description="Specialist agent for math questions",
|
||||
instructions="You explain math step by step and include worked examples.",
|
||||
)
|
||||
```
|
||||
|
||||
## 핸드오프 정의
|
||||
|
||||
에이전트에서는 작업을 해결하는 동안 선택할 수 있는 발신 핸드오프 옵션 목록을 정의할 수 있습니다.
|
||||
|
||||
```python
|
||||
triage_agent = Agent(
|
||||
name="Triage Agent",
|
||||
instructions="Route each homework question to the right specialist.",
|
||||
handoffs=[history_tutor_agent, math_tutor_agent],
|
||||
)
|
||||
```
|
||||
|
||||
## 에이전트 오케스트레이션 실행
|
||||
|
||||
러너는 개별 에이전트 실행, 모든 핸드오프, 모든 도구 호출을 처리합니다.
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
from agents import Runner
|
||||
|
||||
|
||||
async def main():
|
||||
result = await Runner.run(
|
||||
triage_agent,
|
||||
"Who was the first president of the United States?",
|
||||
)
|
||||
print(result.final_output)
|
||||
print(f"Answered by: {result.last_agent.name}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
## 참조 예제
|
||||
|
||||
저장소에는 동일한 핵심 패턴에 대한 전체 스크립트가 포함되어 있습니다.
|
||||
|
||||
- [`examples/basic/hello_world.py`](https://github.com/openai/openai-agents-python/tree/main/examples/basic/hello_world.py): 첫 실행
|
||||
- [`examples/basic/tools.py`](https://github.com/openai/openai-agents-python/tree/main/examples/basic/tools.py): 함수 도구
|
||||
- [`examples/agent_patterns/routing.py`](https://github.com/openai/openai-agents-python/tree/main/examples/agent_patterns/routing.py): 멀티 에이전트 라우팅
|
||||
|
||||
## 트레이스 보기
|
||||
|
||||
에이전트 실행 중 발생한 일을 검토하려면 [OpenAI Dashboard의 Trace viewer](https://platform.openai.com/traces)로 이동해 에이전트 실행 트레이스를 확인하세요.
|
||||
|
||||
## 다음 단계
|
||||
|
||||
더 복잡한 에이전트형 흐름을 구축하는 방법을 알아보세요.
|
||||
|
||||
- [에이전트](agents.md) 구성 방법 알아보기
|
||||
- [에이전트 실행](running_agents.md) 및 [세션](sessions/index.md) 알아보기
|
||||
- 실제 워크스페이스 안에서 작업이 이루어져야 하는 경우 [샌드박스 에이전트](sandbox_agents.md) 알아보기
|
||||
- [도구](tools.md), [가드레일](guardrails.md), [모델](models/index.md) 알아보기
|
||||
@@ -0,0 +1,348 @@
|
||||
---
|
||||
search:
|
||||
exclude: true
|
||||
---
|
||||
# 실시간 에이전트 가이드
|
||||
|
||||
이 가이드는 OpenAI Agents SDK의 실시간 계층이 OpenAI Realtime API에 어떻게 매핑되는지, 그리고 Python SDK가 그 위에 어떤 추가 동작을 더하는지 설명합니다.
|
||||
|
||||
!!! note "먼저 읽기"
|
||||
|
||||
기본 Python 경로를 원한다면 먼저 [빠른 시작](quickstart.md)을 읽어 보세요. 앱에서 서버 측 WebSocket 또는 SIP 중 무엇을 사용해야 할지 결정하는 중이라면 [실시간 전송](transport.md)을 읽어 보세요. 브라우저 WebRTC 전송은 Python SDK의 일부가 아닙니다.
|
||||
|
||||
## 개요
|
||||
|
||||
실시간 에이전트는 Realtime API에 장기 연결을 열어 두어 모델이 텍스트와 오디오를 점진적으로 처리하고, 오디오 출력을 스트리밍하고, 도구를 호출하며, 매 턴마다 새 요청을 다시 시작하지 않고도 인터럽션(중단 처리)을 처리할 수 있게 합니다.
|
||||
|
||||
주요 SDK 컴포넌트는 다음과 같습니다.
|
||||
|
||||
- **RealtimeAgent**: 하나의 실시간 전문가를 위한 지침, 도구, 출력 가드레일 및 핸드오프
|
||||
- **RealtimeRunner**: 시작 에이전트를 실시간 전송에 연결하는 세션 팩토리
|
||||
- **RealtimeSession**: 입력을 보내고, 이벤트를 수신하고, 히스토리를 추적하고, 도구를 실행하는 라이브 세션
|
||||
- **RealtimeModel**: 전송 추상화. 기본값은 OpenAI의 서버 측 WebSocket 구현입니다.
|
||||
|
||||
## 세션 수명 주기
|
||||
|
||||
일반적인 실시간 세션은 다음과 같습니다.
|
||||
|
||||
1. 하나 이상의 `RealtimeAgent`를 생성합니다.
|
||||
2. 시작 에이전트로 `RealtimeRunner`를 생성합니다.
|
||||
3. `RealtimeSession`을 얻으려면 `await runner.run()`을 호출합니다.
|
||||
4. `async with session:` 또는 `await session.enter()`로 세션에 진입합니다.
|
||||
5. `send_message()` 또는 `send_audio()`로 사용자 입력을 보냅니다.
|
||||
6. 대화가 끝날 때까지 세션 이벤트를 반복 처리합니다.
|
||||
|
||||
텍스트 전용 실행과 달리 `runner.run()`은 최종 결과를 즉시 생성하지 않습니다. 대신 로컬 히스토리, 백그라운드 도구 실행, 가드레일 상태, 활성 에이전트 구성을 전송 계층과 동기화하는 라이브 세션 객체를 반환합니다.
|
||||
|
||||
기본적으로 `RealtimeRunner`는 `OpenAIRealtimeWebSocketModel`을 사용하므로 기본 Python 경로는 Realtime API에 대한 서버 측 WebSocket 연결입니다. 다른 `RealtimeModel`을 전달해도 동일한 세션 수명 주기와 에이전트 기능이 적용되며, 연결 방식만 달라질 수 있습니다.
|
||||
|
||||
## 에이전트 및 세션 구성
|
||||
|
||||
`RealtimeAgent`는 의도적으로 일반 `Agent` 타입보다 범위가 좁습니다.
|
||||
|
||||
- 모델 선택은 에이전트별이 아니라 세션 수준에서 구성됩니다.
|
||||
- Structured outputs는 지원되지 않습니다.
|
||||
- 음성은 구성할 수 있지만, 세션이 이미 음성 오디오를 생성한 후에는 변경할 수 없습니다.
|
||||
- 지침, 함수 도구, 핸드오프, 훅, 출력 가드레일은 모두 계속 작동합니다.
|
||||
|
||||
`RealtimeSessionModelSettings`는 새로운 중첩형 `audio` 구성과 이전의 평면형 별칭을 모두 지원합니다. 새 코드에는 중첩 구조를 권장하며, 새 실시간 에이전트는 `gpt-realtime-2.1`로 시작하세요.
|
||||
|
||||
```python
|
||||
runner = RealtimeRunner(
|
||||
starting_agent=agent,
|
||||
config={
|
||||
"model_settings": {
|
||||
"model_name": "gpt-realtime-2.1",
|
||||
"audio": {
|
||||
"input": {
|
||||
"format": "pcm16",
|
||||
"transcription": {"model": "gpt-4o-mini-transcribe"},
|
||||
"turn_detection": {"type": "semantic_vad", "interrupt_response": True},
|
||||
},
|
||||
"output": {"format": "pcm16", "voice": "ash"},
|
||||
},
|
||||
"tool_choice": "auto",
|
||||
}
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||
유용한 세션 수준 설정은 다음과 같습니다.
|
||||
|
||||
- `audio.input.format`, `audio.output.format`
|
||||
- `audio.input.transcription`
|
||||
- `audio.input.noise_reduction`
|
||||
- `audio.input.turn_detection`
|
||||
- `audio.output.voice`, `audio.output.speed`
|
||||
- `output_modalities`
|
||||
- `tool_choice`
|
||||
- `prompt`
|
||||
- `tracing`
|
||||
|
||||
`RealtimeRunner(config=...)`에서 유용한 실행 수준 설정은 다음과 같습니다.
|
||||
|
||||
- `async_tool_calls`
|
||||
- `output_guardrails`
|
||||
- `guardrails_settings.debounce_text_length`
|
||||
- `tool_error_formatter`
|
||||
- `tracing_disabled`
|
||||
|
||||
타입이 지정된 전체 API 범위는 [`RealtimeRunConfig`][agents.realtime.config.RealtimeRunConfig] 및 [`RealtimeSessionModelSettings`][agents.realtime.config.RealtimeSessionModelSettings]를 참고하세요.
|
||||
|
||||
## 입력과 출력
|
||||
|
||||
### 텍스트 및 구조화된 사용자 메시지
|
||||
|
||||
일반 텍스트 또는 구조화된 실시간 메시지에는 [`session.send_message()`][agents.realtime.session.RealtimeSession.send_message]를 사용하세요.
|
||||
|
||||
```python
|
||||
from agents.realtime import RealtimeUserInputMessage
|
||||
|
||||
await session.send_message("Summarize what we discussed so far.")
|
||||
|
||||
message: RealtimeUserInputMessage = {
|
||||
"type": "message",
|
||||
"role": "user",
|
||||
"content": [
|
||||
{"type": "input_text", "text": "Describe this image."},
|
||||
{"type": "input_image", "image_url": image_data_url, "detail": "high"},
|
||||
],
|
||||
}
|
||||
await session.send_message(message)
|
||||
```
|
||||
|
||||
구조화된 메시지는 실시간 대화에 이미지 입력을 포함하는 주요 방법입니다. [`examples/realtime/app/server.py`](https://github.com/openai/openai-agents-python/tree/main/examples/realtime/app/server.py)의 예제 웹 데모는 이러한 방식으로 `input_image` 메시지를 전달합니다.
|
||||
|
||||
### 오디오 입력
|
||||
|
||||
원문 오디오 바이트를 스트리밍하려면 [`session.send_audio()`][agents.realtime.session.RealtimeSession.send_audio]를 사용하세요.
|
||||
|
||||
```python
|
||||
await session.send_audio(audio_bytes)
|
||||
```
|
||||
|
||||
서버 측 턴 감지가 비활성화된 경우 턴 경계를 표시할 책임은 사용자에게 있습니다. 고수준 편의 메서드는 다음과 같습니다.
|
||||
|
||||
```python
|
||||
await session.send_audio(audio_bytes, commit=True)
|
||||
```
|
||||
|
||||
더 낮은 수준의 제어가 필요하다면 하위 모델 전송을 통해 `input_audio_buffer.commit` 같은 원문 클라이언트 이벤트를 보낼 수도 있습니다.
|
||||
|
||||
### 수동 응답 제어
|
||||
|
||||
`session.send_message()`는 고수준 경로를 사용해 사용자 입력을 보내고 응답을 시작합니다. 원문 오디오 버퍼링은 모든 구성에서 자동으로 동일하게 작동하지는 않습니다.
|
||||
|
||||
Realtime API 수준에서 수동 턴 제어란 원문 `session.update`로 `turn_detection`을 지운 다음, `input_audio_buffer.commit` 및 `response.create`를 직접 보내는 것을 의미합니다.
|
||||
|
||||
턴을 수동으로 관리하는 경우 모델 전송을 통해 원문 클라이언트 이벤트를 보낼 수 있습니다.
|
||||
|
||||
```python
|
||||
from agents.realtime.model_inputs import RealtimeModelSendRawMessage
|
||||
|
||||
await session.model.send_event(
|
||||
RealtimeModelSendRawMessage(
|
||||
message={
|
||||
"type": "response.create",
|
||||
}
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
이 패턴은 다음과 같은 경우에 유용합니다.
|
||||
|
||||
- `turn_detection`이 비활성화되어 있고 모델이 언제 응답해야 하는지 직접 결정하려는 경우
|
||||
- 응답을 트리거하기 전에 사용자 입력을 검사하거나 게이트 처리하려는 경우
|
||||
- 대역 외 응답을 위한 사용자 지정 프롬프트가 필요한 경우
|
||||
|
||||
[`examples/realtime/twilio_sip/server.py`](https://github.com/openai/openai-agents-python/tree/main/examples/realtime/twilio_sip/server.py)의 SIP 예제는 시작 인사를 강제로 생성하기 위해 원문 `response.create`를 사용합니다.
|
||||
|
||||
## 이벤트, 히스토리 및 인터럽션(중단 처리)
|
||||
|
||||
`RealtimeSession`은 고수준 SDK 이벤트를 내보내면서, 필요할 때는 원문 모델 이벤트도 계속 전달합니다.
|
||||
|
||||
중요한 세션 이벤트는 다음과 같습니다.
|
||||
|
||||
- `audio`, `audio_end`, `audio_interrupted`
|
||||
- `agent_start`, `agent_end`
|
||||
- `tool_start`, `tool_end`, `tool_approval_required`
|
||||
- `handoff`
|
||||
- `history_added`, `history_updated`
|
||||
- `guardrail_tripped`
|
||||
- `input_audio_timeout_triggered`
|
||||
- `error`
|
||||
- `raw_model_event`
|
||||
|
||||
UI 상태에 가장 유용한 이벤트는 보통 `history_added`와 `history_updated`입니다. 이 이벤트는 사용자 메시지, 어시스턴트 메시지, 도구 호출을 포함한 세션의 로컬 히스토리를 `RealtimeItem` 객체로 노출합니다.
|
||||
|
||||
### 인터럽션(중단 처리) 및 재생 추적
|
||||
|
||||
사용자가 어시스턴트를 중단하면 세션은 `audio_interrupted`를 내보내고, 서버 측 대화가 사용자가 실제로 들은 내용과 일치하도록 히스토리를 업데이트합니다.
|
||||
|
||||
지연 시간이 낮은 로컬 재생에서는 기본 재생 추적기로 충분한 경우가 많습니다. 원격 또는 지연 재생 시나리오, 특히 전화 통신에서는 [`RealtimePlaybackTracker`][agents.realtime.model.RealtimePlaybackTracker]를 사용하세요. 그러면 생성된 모든 오디오가 이미 들렸다고 가정하는 대신 실제 재생 진행률을 기준으로 인터럽션(중단 처리) 시 잘라내기가 이루어집니다.
|
||||
|
||||
[`examples/realtime/twilio/twilio_handler.py`](https://github.com/openai/openai-agents-python/tree/main/examples/realtime/twilio/twilio_handler.py)의 Twilio 예제가 이 패턴을 보여줍니다.
|
||||
|
||||
## 도구, 승인, 핸드오프 및 가드레일
|
||||
|
||||
### 함수 도구
|
||||
|
||||
실시간 에이전트는 라이브 대화 중 함수 도구를 지원합니다.
|
||||
|
||||
```python
|
||||
from agents import function_tool
|
||||
|
||||
|
||||
@function_tool
|
||||
def get_weather(city: str) -> str:
|
||||
"""Get current weather for a city."""
|
||||
return f"The weather in {city} is sunny, 72F."
|
||||
|
||||
|
||||
agent = RealtimeAgent(
|
||||
name="Assistant",
|
||||
instructions="You can answer weather questions.",
|
||||
tools=[get_weather],
|
||||
)
|
||||
```
|
||||
|
||||
### 도구 승인
|
||||
|
||||
함수 도구는 실행 전에 사람의 승인을 요구할 수 있습니다. 이 경우 세션은 `tool_approval_required`를 내보내고, `approve_tool_call()` 또는 `reject_tool_call()`을 호출할 때까지 도구 실행을 일시 중지합니다.
|
||||
|
||||
도구에도 입력 가드레일이 있으면 해당 가드레일은 승인 후 실행 직전에 실행됩니다. 승인 이벤트가 발생하기 전에 이를 실행하려면 `RealtimeRunner(..., config={"tool_execution": {"pre_approval_tool_input_guardrails": True}})`로 runner를 생성하세요. 이 사전 승인 검사를 통과한 호출도 실행 전 승인 후에 다시 검사됩니다.
|
||||
|
||||
```python
|
||||
async for event in session:
|
||||
if event.type == "tool_approval_required":
|
||||
await session.approve_tool_call(event.call_id)
|
||||
```
|
||||
|
||||
구체적인 서버 측 승인 루프는 [`examples/realtime/app/server.py`](https://github.com/openai/openai-agents-python/tree/main/examples/realtime/app/server.py)를 참고하세요. 휴먼인더루프 (HITL) 문서도 [휴먼인더루프 (HITL)](../human_in_the_loop.md)에서 이 흐름을 다시 참조합니다.
|
||||
|
||||
### 핸드오프
|
||||
|
||||
실시간 핸드오프를 사용하면 한 에이전트가 라이브 대화를 다른 전문가에게 넘길 수 있습니다.
|
||||
|
||||
```python
|
||||
from agents.realtime import RealtimeAgent, realtime_handoff
|
||||
|
||||
billing_agent = RealtimeAgent(
|
||||
name="Billing Support",
|
||||
instructions="You specialize in billing issues.",
|
||||
)
|
||||
|
||||
main_agent = RealtimeAgent(
|
||||
name="Customer Service",
|
||||
instructions="Triage the request and hand off when needed.",
|
||||
handoffs=[
|
||||
realtime_handoff(
|
||||
billing_agent,
|
||||
tool_description_override="Transfer to billing support",
|
||||
)
|
||||
],
|
||||
)
|
||||
```
|
||||
|
||||
단독으로 지정된 `RealtimeAgent` 핸드오프는 자동으로 래핑되며, `realtime_handoff(...)`를 사용하면 이름, 설명, 검증, 콜백 및 가용성을 사용자 지정할 수 있습니다. 실시간 핸드오프는 일반 핸드오프 `input_filter`를 지원하지 않습니다.
|
||||
|
||||
### 가드레일
|
||||
|
||||
실시간 에이전트는 에이전트 응답에 대한 출력 가드레일과 함수 도구 호출에 대한 입력 가드레일을 지원합니다. 출력 가드레일은 모든 부분 토큰마다 실행되는 대신 디바운스된 transcript 누적에 대해 실행되며, 예외를 발생시키는 대신 `guardrail_tripped`를 내보냅니다.
|
||||
|
||||
```python
|
||||
from agents.guardrail import GuardrailFunctionOutput, OutputGuardrail
|
||||
|
||||
|
||||
def sensitive_data_check(context, agent, output):
|
||||
return GuardrailFunctionOutput(
|
||||
tripwire_triggered="password" in output,
|
||||
output_info=None,
|
||||
)
|
||||
|
||||
|
||||
agent = RealtimeAgent(
|
||||
name="Assistant",
|
||||
instructions="...",
|
||||
output_guardrails=[OutputGuardrail(guardrail_function=sensitive_data_check)],
|
||||
)
|
||||
```
|
||||
|
||||
실시간 출력 가드레일이 트립되면 세션은 활성 응답을 중단하고, `response.cancel`을 강제하며, `guardrail_tripped`를 내보내고, 트리거된 가드레일의 이름을 담은 후속 사용자 메시지를 보내 모델이 대체 응답을 생성할 수 있게 합니다. 가드레일은 디바운스된 transcript 텍스트에 대해 실행되고 트립와이어가 작동할 때 일부 오디오가 이미 버퍼링되어 있을 수 있으므로, 오디오 플레이어는 여전히 `audio_interrupted`를 수신하고 로컬 재생을 즉시 중지해야 합니다.
|
||||
|
||||
## SIP 및 전화 통신
|
||||
|
||||
Python SDK에는 [`OpenAIRealtimeSIPModel`][agents.realtime.openai_realtime.OpenAIRealtimeSIPModel]을 통한 일급 SIP 연결 흐름이 포함되어 있습니다.
|
||||
|
||||
Realtime Calls API를 통해 통화가 들어오고, 생성된 `call_id`에 에이전트 세션을 연결하려는 경우 이를 사용하세요.
|
||||
|
||||
```python
|
||||
from agents.realtime import RealtimeRunner
|
||||
from agents.realtime.openai_realtime import OpenAIRealtimeSIPModel
|
||||
|
||||
runner = RealtimeRunner(starting_agent=agent, model=OpenAIRealtimeSIPModel())
|
||||
|
||||
async with await runner.run(
|
||||
model_config={
|
||||
"call_id": call_id_from_webhook,
|
||||
}
|
||||
) as session:
|
||||
async for event in session:
|
||||
...
|
||||
```
|
||||
|
||||
통화를 먼저 수락해야 하고 수락 페이로드가 에이전트에서 파생된 세션 구성과 일치하길 원한다면 `OpenAIRealtimeSIPModel.build_initial_session_payload(...)`를 사용하세요. 전체 흐름은 [`examples/realtime/twilio_sip/server.py`](https://github.com/openai/openai-agents-python/tree/main/examples/realtime/twilio_sip/server.py)에 나와 있습니다.
|
||||
|
||||
## 저수준 접근 및 사용자 지정 엔드포인트
|
||||
|
||||
`session.model`을 통해 하위 전송 객체에 접근할 수 있습니다.
|
||||
|
||||
다음이 필요할 때 사용하세요.
|
||||
|
||||
- `session.model.add_listener(...)`를 통한 사용자 지정 리스너
|
||||
- `response.create` 또는 `session.update` 같은 원문 클라이언트 이벤트
|
||||
- `model_config`를 통한 사용자 지정 `url`, `headers` 또는 `api_key` 처리
|
||||
- 기존 실시간 호출에 `call_id` 연결
|
||||
|
||||
`RealtimeModelConfig`는 다음을 지원합니다.
|
||||
|
||||
- `api_key`
|
||||
- `url`
|
||||
- `headers`
|
||||
- `initial_model_settings`
|
||||
- `playback_tracker`
|
||||
- `call_id`
|
||||
|
||||
이 저장소에 포함되어 제공되는 `call_id` 예제는 SIP입니다. 더 넓은 범위의 Realtime API도 일부 서버 측 제어 흐름에 `call_id`를 사용하지만, 여기에는 Python 예제로 패키징되어 있지 않습니다.
|
||||
|
||||
Azure OpenAI에 연결할 때는 GA Realtime 엔드포인트 URL과 명시적 헤더를 전달하세요. 예를 들면 다음과 같습니다.
|
||||
|
||||
```python
|
||||
session = await runner.run(
|
||||
model_config={
|
||||
"url": "wss://<your-resource>.openai.azure.com/openai/v1/realtime?model=<deployment-name>",
|
||||
"headers": {"api-key": "<your-azure-api-key>"},
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
토큰 기반 인증에는 `headers`에 bearer 토큰을 사용하세요.
|
||||
|
||||
```python
|
||||
session = await runner.run(
|
||||
model_config={
|
||||
"url": "wss://<your-resource>.openai.azure.com/openai/v1/realtime?model=<deployment-name>",
|
||||
"headers": {"authorization": f"Bearer {token}"},
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
`headers`를 전달하면 SDK가 `Authorization`을 자동으로 추가하지 않습니다. 실시간 에이전트에서는 기존 beta 경로(`/openai/realtime?api-version=...`)를 사용하지 마세요.
|
||||
|
||||
## 추가 자료
|
||||
|
||||
- [실시간 전송](transport.md)
|
||||
- [빠른 시작](quickstart.md)
|
||||
- [OpenAI Realtime 대화](https://developers.openai.com/api/docs/guides/realtime-conversations/)
|
||||
- [OpenAI Realtime 서버 측 제어](https://developers.openai.com/api/docs/guides/realtime-server-controls/)
|
||||
- [`examples/realtime`](https://github.com/openai/openai-agents-python/tree/main/examples/realtime)
|
||||
@@ -0,0 +1,158 @@
|
||||
---
|
||||
search:
|
||||
exclude: true
|
||||
---
|
||||
# 빠른 시작
|
||||
|
||||
Python SDK의 실시간 에이전트는 WebSocket 전송 기반의 OpenAI Realtime API 위에 구축된 서버 측 저지연 에이전트입니다.
|
||||
|
||||
!!! note "Python SDK 범위"
|
||||
|
||||
Python SDK는 브라우저 WebRTC 전송을 제공하지 **않습니다**. 이 페이지에서는 서버 측 WebSocket을 통한 Python 관리 실시간 세션만 다룹니다. 서버 측 오케스트레이션, 도구, 승인, 전화 통신 통합에는 이 SDK를 사용하세요. [Realtime 전송](transport.md)도 참조하세요.
|
||||
|
||||
## 전제 조건
|
||||
|
||||
- Python 3.10 이상
|
||||
- OpenAI API 키
|
||||
- OpenAI Agents SDK에 대한 기본적인 이해
|
||||
|
||||
## 설치
|
||||
|
||||
아직 설치하지 않았다면 OpenAI Agents SDK를 설치하세요:
|
||||
|
||||
```bash
|
||||
pip install openai-agents
|
||||
```
|
||||
|
||||
## 서버 측 실시간 세션 생성
|
||||
|
||||
### 1. 실시간 구성 요소 가져오기
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
|
||||
from agents.realtime import RealtimeAgent, RealtimeRunner
|
||||
```
|
||||
|
||||
### 2. 시작 에이전트 정의
|
||||
|
||||
```python
|
||||
agent = RealtimeAgent(
|
||||
name="Assistant",
|
||||
instructions="You are a helpful voice assistant. Keep responses short and conversational.",
|
||||
)
|
||||
```
|
||||
|
||||
### 3. 러너 구성
|
||||
|
||||
새 코드에서는 중첩된 `audio.input` / `audio.output` 세션 설정 구조를 사용하는 것을 권장합니다. 새 실시간 에이전트에는 `gpt-realtime-2.1`로 시작하세요.
|
||||
|
||||
```python
|
||||
runner = RealtimeRunner(
|
||||
starting_agent=agent,
|
||||
config={
|
||||
"model_settings": {
|
||||
"model_name": "gpt-realtime-2.1",
|
||||
"audio": {
|
||||
"input": {
|
||||
"format": "pcm16",
|
||||
"transcription": {"model": "gpt-4o-mini-transcribe"},
|
||||
"turn_detection": {
|
||||
"type": "semantic_vad",
|
||||
"interrupt_response": True,
|
||||
},
|
||||
},
|
||||
"output": {
|
||||
"format": "pcm16",
|
||||
"voice": "ash",
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||
### 4. 세션 시작 및 입력 전송
|
||||
|
||||
`runner.run()`은 `RealtimeSession`을 반환합니다. 세션 컨텍스트에 들어가면 연결이 열립니다.
|
||||
|
||||
```python
|
||||
async def main() -> None:
|
||||
session = await runner.run()
|
||||
|
||||
async with session:
|
||||
await session.send_message("Say hello in one short sentence.")
|
||||
|
||||
async for event in session:
|
||||
if event.type == "audio":
|
||||
# Forward or play event.audio.data.
|
||||
pass
|
||||
elif event.type == "history_added":
|
||||
print(event.item)
|
||||
elif event.type == "agent_end":
|
||||
# One assistant turn finished.
|
||||
break
|
||||
elif event.type == "error":
|
||||
print(f"Error: {event.error}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
`session.send_message()`는 일반 문자열 또는 구조화된 실시간 메시지를 받습니다. 원문 오디오 청크에는 [`session.send_audio()`][agents.realtime.session.RealtimeSession.send_audio]를 사용하세요.
|
||||
|
||||
## 이 빠른 시작에 포함되지 않는 내용
|
||||
|
||||
- 마이크 캡처 및 스피커 재생 코드. [`examples/realtime`](https://github.com/openai/openai-agents-python/tree/main/examples/realtime)의 실시간 코드 예제를 참조하세요.
|
||||
- SIP / 전화 통신 연결 흐름. [Realtime 전송](transport.md) 및 [SIP 섹션](guide.md#sip-and-telephony)을 참조하세요.
|
||||
|
||||
## 주요 설정
|
||||
|
||||
기본 세션이 작동하면, 대부분의 사람들이 다음으로 찾는 설정은 다음과 같습니다:
|
||||
|
||||
- `model_name`
|
||||
- `audio.input.format`, `audio.output.format`
|
||||
- `audio.input.transcription`
|
||||
- `audio.input.noise_reduction`
|
||||
- 자동 턴 감지를 위한 `audio.input.turn_detection`
|
||||
- `audio.output.voice`
|
||||
- `tool_choice`, `prompt`, `tracing`
|
||||
- `async_tool_calls`, `tool_execution.pre_approval_tool_input_guardrails`, `guardrails_settings.debounce_text_length`, `tool_error_formatter`
|
||||
|
||||
`input_audio_format`, `output_audio_format`, `input_audio_transcription`, `turn_detection` 같은 이전의 플랫 별칭도 여전히 작동하지만, 새 코드에는 중첩된 `audio` 설정을 권장합니다.
|
||||
|
||||
수동 턴 제어에는 [실시간 에이전트 가이드](guide.md#manual-response-control)에 설명된 원문 `session.update` / `input_audio_buffer.commit` / `response.create` 흐름을 사용하세요.
|
||||
|
||||
전체 스키마는 [`RealtimeRunConfig`][agents.realtime.config.RealtimeRunConfig] 및 [`RealtimeSessionModelSettings`][agents.realtime.config.RealtimeSessionModelSettings]를 참조하세요.
|
||||
|
||||
## 연결 옵션
|
||||
|
||||
환경에서 API 키를 설정하세요:
|
||||
|
||||
```bash
|
||||
export OPENAI_API_KEY="your-api-key-here"
|
||||
```
|
||||
|
||||
또는 세션을 시작할 때 직접 전달하세요:
|
||||
|
||||
```python
|
||||
session = await runner.run(model_config={"api_key": "your-api-key"})
|
||||
```
|
||||
|
||||
`model_config`는 다음도 지원합니다:
|
||||
|
||||
- `url`: 사용자 지정 WebSocket 엔드포인트
|
||||
- `headers`: 사용자 지정 요청 헤더
|
||||
- `call_id`: 기존 실시간 호출에 연결합니다. 이 리포지토리에서 문서화된 연결 흐름은 SIP입니다.
|
||||
- `playback_tracker`: 사용자가 실제로 들은 오디오 양을 보고합니다
|
||||
|
||||
`headers`를 명시적으로 전달하면 SDK는 `Authorization` 헤더를 대신 삽입해 주지 **않습니다**.
|
||||
|
||||
Azure OpenAI에 연결할 때는 GA Realtime 엔드포인트 URL을 `model_config["url"]`에 전달하고 명시적인 헤더도 전달하세요. 실시간 에이전트에서는 레거시 베타 경로(`/openai/realtime?api-version=...`)를 피하세요. 자세한 내용은 [실시간 에이전트 가이드](guide.md#low-level-access-and-custom-endpoints)를 참조하세요.
|
||||
|
||||
## 다음 단계
|
||||
|
||||
- 서버 측 WebSocket과 SIP 중 선택하려면 [Realtime 전송](transport.md)을 읽어 보세요.
|
||||
- 수명 주기, 구조화된 입력, 승인, 핸드오프, 가드레일, 저수준 제어에 대해서는 [실시간 에이전트 가이드](guide.md)를 읽어 보세요.
|
||||
- [`examples/realtime`](https://github.com/openai/openai-agents-python/tree/main/examples/realtime)의 코드 예제를 살펴보세요.
|
||||
@@ -0,0 +1,76 @@
|
||||
---
|
||||
search:
|
||||
exclude: true
|
||||
---
|
||||
# 실시간 전송 방식
|
||||
|
||||
이 페이지를 사용하여 실시간 에이전트가 Python 애플리케이션에 어떻게 적합한지 결정하세요.
|
||||
|
||||
!!! note "Python SDK 경계"
|
||||
|
||||
Python SDK에는 브라우저 WebRTC 전송이 **포함되지 않습니다**. 이 페이지는 Python SDK 전송 선택지, 즉 서버 측 WebSocket 및 SIP 연결 플로우만 다룹니다. 브라우저 WebRTC는 별도의 플랫폼 주제이며, 공식 [WebRTC를 사용하는 Realtime API](https://developers.openai.com/api/docs/guides/realtime-webrtc/) 가이드에 문서화되어 있습니다.
|
||||
|
||||
## 결정 가이드
|
||||
|
||||
| 목표 | 시작 위치 | 이유 |
|
||||
| --- | --- | --- |
|
||||
| 서버가 관리하는 실시간 앱 빌드 | [빠른 시작](quickstart.md) | 기본 Python 경로는 `RealtimeRunner`가 관리하는 서버 측 WebSocket 세션입니다. |
|
||||
| 어떤 전송 방식과 배포 형태를 선택해야 하는지 이해 | 이 페이지 | 전송 방식이나 배포 형태를 확정하기 전에 이 페이지를 사용하세요. |
|
||||
| 에이전트를 전화 또는 SIP 통화에 연결 | [실시간 가이드](guide.md) 및 [`examples/realtime/twilio_sip`](https://github.com/openai/openai-agents-python/tree/main/examples/realtime/twilio_sip) | 이 저장소에는 `call_id`로 구동되는 SIP 연결 플로우가 포함되어 있습니다. |
|
||||
|
||||
## 기본 Python 경로인 서버 측 WebSocket
|
||||
|
||||
커스텀 `RealtimeModel`을 전달하지 않으면 `RealtimeRunner`는 `OpenAIRealtimeWebSocketModel`을 사용합니다.
|
||||
|
||||
즉, 표준 Python 토폴로지는 다음과 같습니다.
|
||||
|
||||
1. Python 서비스가 `RealtimeRunner`를 생성합니다.
|
||||
2. `await runner.run()`이 `RealtimeSession`을 반환합니다.
|
||||
3. 세션에 진입하여 텍스트, 구조화된 메시지 또는 오디오를 전송합니다.
|
||||
4. `RealtimeSessionEvent` 항목을 소비하고 오디오 또는 대본을 애플리케이션으로 전달합니다.
|
||||
|
||||
이 토폴로지는 핵심 데모 앱, CLI 예제, Twilio Media Streams 예제에서 사용됩니다.
|
||||
|
||||
- [`examples/realtime/app`](https://github.com/openai/openai-agents-python/tree/main/examples/realtime/app)
|
||||
- [`examples/realtime/cli`](https://github.com/openai/openai-agents-python/tree/main/examples/realtime/cli)
|
||||
- [`examples/realtime/twilio`](https://github.com/openai/openai-agents-python/tree/main/examples/realtime/twilio)
|
||||
|
||||
서버가 오디오 파이프라인, 도구 실행, 승인 플로우, 기록 처리를 담당할 때 이 경로를 사용하세요.
|
||||
|
||||
## 전화 통신 경로인 SIP 연결
|
||||
|
||||
이 저장소에 문서화된 전화 통신 플로우에서 Python SDK는 `call_id`를 통해 기존 실시간 통화에 연결합니다.
|
||||
|
||||
이 토폴로지는 다음과 같습니다.
|
||||
|
||||
1. OpenAI가 `realtime.call.incoming`과 같은 웹훅을 서비스로 보냅니다.
|
||||
2. 서비스가 Realtime Calls API를 통해 통화를 수락합니다.
|
||||
3. Python 서비스가 `RealtimeRunner(..., model=OpenAIRealtimeSIPModel())`을 시작합니다.
|
||||
4. 세션이 `model_config={"call_id": ...}`로 연결된 다음, 다른 실시간 세션과 마찬가지로 이벤트를 처리합니다.
|
||||
|
||||
이 토폴로지는 [`examples/realtime/twilio_sip`](https://github.com/openai/openai-agents-python/tree/main/examples/realtime/twilio_sip)에 나와 있습니다.
|
||||
|
||||
더 넓은 Realtime API도 일부 서버 측 제어 패턴에 `call_id`를 사용하지만, 이 저장소에서 제공되는 연결 예제는 SIP입니다.
|
||||
|
||||
## 이 SDK 범위 밖의 브라우저 WebRTC
|
||||
|
||||
앱의 주 클라이언트가 Realtime WebRTC를 사용하는 브라우저인 경우:
|
||||
|
||||
- 이 저장소의 Python SDK 문서 범위 밖으로 간주하세요.
|
||||
- 클라이언트 측 플로우와 이벤트 모델에는 공식 [WebRTC를 사용하는 Realtime API](https://developers.openai.com/api/docs/guides/realtime-webrtc/) 및 [실시간 대화](https://developers.openai.com/api/docs/guides/realtime-conversations/) 문서를 사용하세요.
|
||||
- 브라우저 WebRTC 클라이언트 위에 사이드밴드 서버 연결이 필요한 경우 공식 [실시간 서버 측 제어](https://developers.openai.com/api/docs/guides/realtime-server-controls/) 가이드를 사용하세요.
|
||||
- 이 저장소가 브라우저 측 `RTCPeerConnection` 추상화나 바로 사용할 수 있는 브라우저 WebRTC 샘플을 제공한다고 기대하지 마세요.
|
||||
|
||||
또한 이 저장소는 현재 브라우저 WebRTC와 Python 사이드밴드를 함께 사용하는 예제를 제공하지 않습니다.
|
||||
|
||||
## 커스텀 엔드포인트 및 연결 지점
|
||||
|
||||
[`RealtimeModelConfig`][agents.realtime.model.RealtimeModelConfig]의 전송 구성 인터페이스를 사용하면 기본 경로를 조정할 수 있습니다.
|
||||
|
||||
- `url`: WebSocket 엔드포인트 재정의
|
||||
- `headers`: Azure 인증 헤더와 같은 명시적 헤더 제공
|
||||
- `api_key`: API 키를 직접 또는 콜백을 통해 전달
|
||||
- `call_id`: 기존 실시간 통화에 연결. 이 저장소에서 문서화된 예제는 SIP입니다.
|
||||
- `playback_tracker`: 인터럽션(중단 처리)을 위해 실제 재생 진행 상황 보고
|
||||
|
||||
토폴로지를 선택한 후의 상세한 생명주기와 기능 범위는 [실시간 에이전트 가이드](guide.md)를 참조하세요.
|
||||
@@ -0,0 +1,186 @@
|
||||
---
|
||||
search:
|
||||
exclude: true
|
||||
---
|
||||
# 릴리스 프로세스/변경 로그
|
||||
|
||||
이 프로젝트는 `0.Y.Z` 형식을 사용하는, 약간 수정된 시맨틱 버저닝을 따릅니다. 맨 앞의 `0`은 SDK가 여전히 빠르게 발전하고 있음을 나타냅니다. 각 구성 요소는 다음과 같이 증가합니다.
|
||||
|
||||
## 마이너(`Y`) 버전
|
||||
|
||||
베타로 표시되지 않은 공개 인터페이스에 **호환성을 깨는 변경 사항**이 있는 경우 마이너 버전 `Y`를 증가시킵니다. 예를 들어 `0.0.x`에서 `0.1.x`로 변경할 때 호환성을 깨는 변경 사항이 포함될 수 있습니다.
|
||||
|
||||
호환성을 깨는 변경 사항을 원하지 않는다면 프로젝트에서 `0.0.x` 버전으로 고정하는 것이 좋습니다.
|
||||
|
||||
## 패치(`Z`) 버전
|
||||
|
||||
호환성을 깨지 않는 변경 사항에는 `Z`를 증가시킵니다.
|
||||
|
||||
- 버그 수정
|
||||
- 새로운 기능
|
||||
- 비공개 인터페이스 변경
|
||||
- 베타 기능 업데이트
|
||||
|
||||
## 호환성을 깨는 변경 사항 기록
|
||||
|
||||
### 0.18.0
|
||||
|
||||
이 마이너 릴리스에는 호환성을 깨는 변경 사항이 **없습니다**. 마이너 버전 증가는 실시간 에이전트의 기본 모델 업데이트만을 위한 것입니다.
|
||||
|
||||
주요 내용:
|
||||
|
||||
- 이제 실시간 에이전트는 `gpt-realtime-2.1`을 기본 모델로 사용하므로, 새로운 Realtime 설정에서는 별도 구성 없이 최신 권장 모델을 사용합니다.
|
||||
|
||||
### 0.17.0
|
||||
|
||||
이 버전에서는 소스 경로에 `Manifest.extra_path_grants`가 적용되지 않는 한, 샌드박스 로컬 소스 구체화 과정에서 `LocalFile.src`와 `LocalDir.src`가 구체화 `base_dir` 내부에 유지됩니다. 매니페스트가 적용될 때 `base_dir`은 SDK 프로세스의 현재 작업 디렉터리입니다. 상대 로컬 소스는 해당 디렉터리를 기준으로 해석되며, 절대 로컬 소스는 이미 해당 디렉터리 내부 또는 명시적으로 권한이 부여된 경로 아래에 있어야 합니다. 이를 통해 로컬 아티팩트 경계 문제가 해결되지만, 해당 기본 디렉터리 외부의 신뢰할 수 있는 호스트 파일이나 디렉터리를 의도적으로 샌드박스 작업 공간에 복사하는 애플리케이션에는 영향을 줄 수 있습니다.
|
||||
|
||||
마이그레이션하려면 매니페스트 수준에서 `SandboxPathGrant`를 사용해 신뢰할 수 있는 호스트 루트에 권한을 부여하세요. 샌드박스에서 해당 파일을 읽기만 하면 되는 경우에는 가급적 읽기 전용으로 설정하세요.
|
||||
|
||||
```python
|
||||
from pathlib import Path
|
||||
|
||||
from agents.sandbox import Manifest, SandboxPathGrant
|
||||
from agents.sandbox.entries import Dir, LocalDir
|
||||
|
||||
# This is an absolute host path outside the SDK process base_dir.
|
||||
TRUSTED_DOCS_ROOT = Path("/opt/my-app/docs")
|
||||
|
||||
manifest = Manifest(
|
||||
extra_path_grants=(
|
||||
# This host root is outside the SDK process base_dir, so the manifest must grant it.
|
||||
SandboxPathGrant(path=str(TRUSTED_DOCS_ROOT), read_only=True),
|
||||
),
|
||||
entries={
|
||||
# No grant is needed for local sources that stay under the SDK process base_dir.
|
||||
"fixtures": LocalDir(src=Path("fixtures"), description="Local test fixtures."),
|
||||
# This entry reads from the granted host root and copies it into the sandbox workspace.
|
||||
"docs": LocalDir(src=TRUSTED_DOCS_ROOT, description="Trusted local documents."),
|
||||
# Dir creates a sandbox workspace directory; it does not read from the host filesystem.
|
||||
"output": Dir(description="Generated artifacts."),
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||
`extra_path_grants`는 신뢰할 수 있는 애플리케이션 구성으로 취급하세요. 애플리케이션에서 해당 호스트 경로를 이미 승인한 경우가 아니라면 모델 출력이나 신뢰할 수 없는 다른 매니페스트 입력으로 권한 부여 항목을 채우지 마세요.
|
||||
|
||||
### 0.16.0
|
||||
|
||||
이 버전부터 SDK 기본 모델이 `gpt-4.1` 대신 `gpt-5.4-mini`로 변경되었습니다. 이는 모델을 명시적으로 설정하지 않은 에이전트와 실행에 영향을 줍니다. 새로운 기본 모델이 GPT-5 모델이므로, 암시적 기본 모델 설정에는 이제 `reasoning.effort="none"` 및 `verbosity="low"`와 같은 GPT-5 기본값이 포함됩니다.
|
||||
|
||||
이전 기본 모델 동작을 유지해야 한다면 에이전트 또는 실행 구성에서 모델을 명시적으로 설정하거나 `OPENAI_DEFAULT_MODEL` 환경 변수를 설정하세요.
|
||||
|
||||
```python
|
||||
agent = Agent(name="Assistant", model="gpt-4.1")
|
||||
```
|
||||
|
||||
주요 내용:
|
||||
|
||||
- 이제 `Runner.run`, `Runner.run_sync`, `Runner.run_streamed`에서 `max_turns=None`을 지정하여 턴 제한을 비활성화할 수 있습니다.
|
||||
- 이제 로컬, Docker 및 공급자 기반 샌드박스 구현 전체에서 샌드박스 작업 공간 하이드레이션이 절대 심볼릭 링크 대상을 포함하여 아카이브 루트 외부를 가리키는 심볼릭 링크가 있는 tar 아카이브를 거부합니다.
|
||||
|
||||
### 0.15.0
|
||||
|
||||
이 버전부터 모델 거부는 빈 텍스트 출력으로 처리되거나 structured outputs에서 실행 루프가 `MaxTurnsExceeded`에 도달할 때까지 재시도되도록 하는 대신, `ModelRefusalError`로 명시적으로 노출됩니다.
|
||||
|
||||
이는 이전에 거부만 포함된 모델 응답이 `final_output == ""` 상태로 완료될 것으로 예상한 코드에 영향을 줍니다. 예외를 발생시키지 않고 거부를 처리하려면 `model_refusal` 실행 오류 핸들러를 제공하세요.
|
||||
|
||||
```python
|
||||
result = Runner.run_sync(
|
||||
agent,
|
||||
input,
|
||||
error_handlers={"model_refusal": lambda data: data.error.refusal},
|
||||
)
|
||||
```
|
||||
|
||||
structured outputs 에이전트의 경우 핸들러는 에이전트의 출력 스키마와 일치하는 값을 반환할 수 있으며, SDK는 다른 실행 오류 핸들러의 최종 출력과 동일하게 이를 검증합니다.
|
||||
|
||||
### 0.14.0
|
||||
|
||||
이 마이너 릴리스에는 호환성을 깨는 변경 사항이 **없지만**, 주요 신규 베타 기능 영역인 샌드박스 에이전트와 더불어 로컬, 컨테이너 및 호스팅 환경 전반에서 이를 사용하는 데 필요한 런타임, 백엔드 및 문서 지원이 추가되었습니다.
|
||||
|
||||
주요 내용:
|
||||
|
||||
- `SandboxAgent`, `Manifest`, `SandboxRunConfig`를 중심으로 하는 새로운 베타 샌드박스 런타임 인터페이스가 추가되어, 에이전트가 파일, 디렉터리, Git 저장소, 마운트, 스냅샷 및 재개 지원을 갖춘 영구 격리 작업 공간 안에서 작업할 수 있습니다.
|
||||
- `UnixLocalSandboxClient`와 `DockerSandboxClient`를 통해 로컬 및 컨테이너 기반 개발용 샌드박스 실행 백엔드가 추가되었으며, 선택적 추가 패키지를 통해 Blaxel, Cloudflare, Daytona, E2B, Modal, Runloop, Vercel의 호스팅 공급자 통합도 추가되었습니다.
|
||||
- 향후 실행에서 이전 실행에서 얻은 교훈을 재사용할 수 있도록 샌드박스 메모리 지원이 추가되었습니다. 여기에는 점진적 공개, 멀티턴 그룹화, 구성 가능한 격리 경계 및 S3 기반 워크플로를 포함한 영구 메모리 코드 예제가 포함됩니다.
|
||||
- 로컬 및 합성 작업 공간 항목, S3/R2/GCS/Azure Blob Storage/S3 Files용 원격 스토리지 마운트, 이식 가능한 스냅샷, `RunState`, `SandboxSessionState` 또는 저장된 스냅샷을 통한 재개 흐름을 포함하여 더 광범위한 작업 공간 및 재개 모델이 추가되었습니다.
|
||||
- `examples/sandbox/` 아래에 기술을 활용한 코딩 작업, 핸드오프, 메모리, 공급자별 설정과 코드 검토, 데이터룸 QA, 웹사이트 복제 같은 엔드투엔드 워크플로를 다루는 다양한 샌드박스 코드 예제와 튜토리얼이 추가되었습니다.
|
||||
- 샌드박스를 인식하는 세션 준비, 기능 바인딩, 상태 직렬화, 통합 트레이싱, 프롬프트 캐시 키 기본값 및 더 안전한 민감 MCP 출력 마스킹을 통해 핵심 런타임과 트레이싱 스택이 확장되었습니다.
|
||||
|
||||
### 0.13.0
|
||||
|
||||
이 마이너 릴리스에는 호환성을 깨는 변경 사항이 **없지만**, 주목할 만한 Realtime 기본값 업데이트와 새로운 MCP 기능 및 런타임 안정성 수정 사항이 포함되어 있습니다.
|
||||
|
||||
주요 내용:
|
||||
|
||||
- 이제 기본 WebSocket Realtime 모델은 `gpt-realtime-1.5`이므로, 새로운 Realtime 에이전트 설정에서는 별도 구성 없이 더 새로운 모델을 사용합니다.
|
||||
- 이제 `MCPServer`에서 `list_resources()`, `list_resource_templates()`, `read_resource()`를 제공하며, `MCPServerStreamableHttp`에서는 `session_id`를 제공하므로 재연결이나 상태 비저장 워커 간에 스트리밍 가능 HTTP 세션을 재개할 수 있습니다.
|
||||
- 이제 Chat Completions 통합에서 `should_replay_reasoning_content`를 통해 추론 콘텐츠 재실행을 선택적으로 활성화할 수 있어 LiteLLM/DeepSeek 같은 어댑터에서 공급자별 추론/도구 호출 연속성이 향상됩니다.
|
||||
- `SQLAlchemySession`의 동시 첫 쓰기, 추론 제거 후 고립된 어시스턴트 메시지 ID가 포함된 압축 요청, MCP/추론 항목을 남겨 두는 `remove_all_tools()`, 함수 도구 배치 실행기의 경쟁 상태를 포함하여 여러 런타임 및 세션 경계 사례를 수정했습니다.
|
||||
|
||||
### 0.12.0
|
||||
|
||||
이 마이너 릴리스에는 호환성을 깨는 변경 사항이 **없습니다**. 주요 기능 추가 사항은 [릴리스 노트](https://github.com/openai/openai-agents-python/releases/tag/v0.12.0)를 확인하세요.
|
||||
|
||||
### 0.11.0
|
||||
|
||||
이 마이너 릴리스에는 호환성을 깨는 변경 사항이 **없습니다**. 주요 기능 추가 사항은 [릴리스 노트](https://github.com/openai/openai-agents-python/releases/tag/v0.11.0)를 확인하세요.
|
||||
|
||||
### 0.10.0
|
||||
|
||||
이 마이너 릴리스에는 호환성을 깨는 변경 사항이 **없지만**, OpenAI Responses 사용자를 위한 중요한 신규 기능 영역인 Responses API의 WebSocket 전송 지원이 포함되어 있습니다.
|
||||
|
||||
주요 내용:
|
||||
|
||||
- OpenAI Responses 모델에 대한 WebSocket 전송 지원이 추가되었습니다(선택적 활성화 방식이며 HTTP가 계속 기본 전송 방식입니다).
|
||||
- 멀티턴 실행에서 WebSocket을 지원하는 공유 공급자와 `RunConfig`를 재사용할 수 있도록 `responses_websocket_session()` 헬퍼 / `ResponsesWebSocketSession`이 추가되었습니다.
|
||||
- 스트리밍, 도구, 승인 및 후속 턴을 다루는 새로운 WebSocket 스트리밍 코드 예제(`examples/basic/stream_ws.py`)가 추가되었습니다.
|
||||
|
||||
### 0.9.0
|
||||
|
||||
이 버전부터 Python 3.9는 더 이상 지원되지 않습니다. 해당 메이저 버전은 3개월 전에 EOL에 도달했습니다. 더 새로운 런타임 버전으로 업그레이드하세요.
|
||||
|
||||
또한 `Agent#as_tool()` 메서드에서 반환되는 값의 타입 힌트가 `Tool`에서 `FunctionTool`로 좁혀졌습니다. 이 변경으로 일반적으로 호환성 문제가 발생하지는 않지만, 코드가 더 넓은 유니온 타입에 의존하는 경우 일부 조정이 필요할 수 있습니다.
|
||||
|
||||
### 0.8.0
|
||||
|
||||
이 버전에서는 두 가지 런타임 동작 변경으로 인해 마이그레이션 작업이 필요할 수 있습니다.
|
||||
|
||||
- **동기식** Python 호출 가능 객체를 래핑하는 함수 도구는 이제 이벤트 루프 스레드에서 실행되는 대신 `asyncio.to_thread(...)`를 통해 워커 스레드에서 실행됩니다. 도구 로직이 스레드 로컬 상태나 스레드 종속 리소스에 의존한다면 비동기 도구 구현으로 마이그레이션하거나 도구 코드에서 스레드 종속성을 명시하세요.
|
||||
- 이제 로컬 MCP 도구 실패 처리를 구성할 수 있으며, 기본 동작에서는 전체 실행을 실패시키는 대신 모델에 표시되는 오류 출력을 반환할 수 있습니다. 빠른 실패 동작에 의존한다면 `mcp_config={"failure_error_function": None}`을 설정하세요. 서버 수준의 `failure_error_function` 값은 에이전트 수준 설정을 재정의하므로, 명시적 핸들러가 있는 각 로컬 MCP 서버에 `failure_error_function=None`을 설정하세요.
|
||||
|
||||
### 0.7.0
|
||||
|
||||
이 버전에는 기존 애플리케이션에 영향을 줄 수 있는 몇 가지 동작 변경 사항이 있습니다.
|
||||
|
||||
- 이제 중첩 핸드오프 기록은 **선택적 활성화** 방식입니다(기본적으로 비활성화됨). v0.6.x의 기본 중첩 동작에 의존했다면 `RunConfig(nest_handoff_history=True)`를 명시적으로 설정하세요.
|
||||
- `gpt-5.1` / `gpt-5.2`의 기본 `reasoning.effort`가 SDK 기본값으로 구성되던 이전 기본값 `"low"`에서 `"none"`으로 변경되었습니다. 프롬프트나 품질/비용 특성이 `"low"`에 의존했다면 `model_settings`에서 명시적으로 설정하세요.
|
||||
|
||||
### 0.6.0
|
||||
|
||||
이 버전부터 기본 핸드오프 기록은 원문 사용자/어시스턴트 턴을 노출하는 대신 하나의 어시스턴트 메시지로 패키징되어 후속 에이전트에 간결하고 예측 가능한 요약을 제공합니다
|
||||
- 이제 기존의 단일 메시지 핸드오프 대화 내용은 기본적으로 `<CONVERSATION HISTORY>` 블록 앞에 "For context, here is the conversation so far between the user and the previous agent:"로 시작하므로 후속 에이전트가 명확하게 표시된 요약을 받습니다
|
||||
|
||||
### 0.5.0
|
||||
|
||||
이 버전에는 외부에 드러나는 호환성을 깨는 변경 사항이 없지만, 내부적으로 새로운 기능과 몇 가지 중요한 업데이트가 포함되어 있습니다.
|
||||
|
||||
- [SIP 프로토콜 연결](https://platform.openai.com/docs/guides/realtime-sip)을 처리할 수 있도록 `RealtimeRunner` 지원 추가
|
||||
- Python 3.14 호환성을 위해 `Runner#run_sync`의 내부 로직을 대폭 수정
|
||||
|
||||
### 0.4.0
|
||||
|
||||
이 버전부터 [openai](https://pypi.org/project/openai/) 패키지 v1.x 버전은 더 이상 지원되지 않습니다. 이 SDK와 함께 openai v2.x를 사용하세요.
|
||||
|
||||
### 0.3.0
|
||||
|
||||
이 버전에서는 Realtime API 지원이 gpt-realtime 모델과 해당 API 인터페이스(GA 버전)로 전환됩니다.
|
||||
|
||||
### 0.2.0
|
||||
|
||||
이 버전에서는 이전에 `Agent`를 인수로 받던 몇몇 위치에서 이제 `AgentBase`를 인수로 받습니다. MCP 서버의 `list_tools()` 호출이 그 예입니다. 이는 타입 지정만 변경된 것이며, 계속해서 `Agent` 객체를 받게 됩니다. 업데이트하려면 `Agent`를 `AgentBase`로 바꿔 타입 오류를 수정하면 됩니다.
|
||||
|
||||
### 0.1.0
|
||||
|
||||
이 버전에서 [`MCPServer.list_tools()`][agents.mcp.server.MCPServer]에는 `run_context`와 `agent`라는 두 개의 새로운 매개변수가 추가되었습니다. `MCPServer`를 상속하는 모든 클래스에 이 매개변수를 추가해야 합니다.
|
||||
@@ -0,0 +1,24 @@
|
||||
---
|
||||
search:
|
||||
exclude: true
|
||||
---
|
||||
# REPL 유틸리티
|
||||
|
||||
SDK는 터미널에서 직접 에이전트의 동작을 빠르게 대화형으로 테스트할 수 있도록 `run_demo_loop`를 제공합니다.
|
||||
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
from agents import Agent, run_demo_loop
|
||||
|
||||
async def main() -> None:
|
||||
agent = Agent(name="Assistant", instructions="You are a helpful assistant.")
|
||||
await run_demo_loop(agent)
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
`run_demo_loop`는 루프 안에서 사용자 입력을 요청하며, 턴 사이의 대화 기록을 유지합니다. 기본적으로 모델 출력이 생성되는 대로 스트리밍합니다. 위 예제를 실행하면 run_demo_loop가 대화형 채팅 세션을 시작합니다. 계속해서 입력을 요청하고, 턴 사이의 전체 대화 기록을 기억하며(따라서 에이전트는 지금까지 논의된 내용을 알 수 있습니다), 에이전트의 응답이 생성되는 즉시 실시간으로 자동 스트리밍해 제공합니다.
|
||||
|
||||
이 채팅 세션을 종료하려면 `quit` 또는 `exit`를 입력한 뒤 Enter를 누르거나 `Ctrl-D` 키보드 단축키를 사용하면 됩니다.
|
||||
@@ -0,0 +1,165 @@
|
||||
---
|
||||
search:
|
||||
exclude: true
|
||||
---
|
||||
# 결과
|
||||
|
||||
`Runner.run` 메서드를 호출하면 두 가지 결과 타입 중 하나를 받습니다.
|
||||
|
||||
- `Runner.run(...)` 또는 `Runner.run_sync(...)`의 [`RunResult`][agents.result.RunResult]
|
||||
- `Runner.run_streamed(...)`의 [`RunResultStreaming`][agents.result.RunResultStreaming]
|
||||
|
||||
둘 다 [`RunResultBase`][agents.result.RunResultBase]를 상속하며, `final_output`, `new_items`, `last_agent`, `raw_responses`, `to_state()`와 같은 공통 결과 접근 지점을 제공합니다.
|
||||
|
||||
`RunResultStreaming`은 [`stream_events()`][agents.result.RunResultStreaming.stream_events], [`current_agent`][agents.result.RunResultStreaming.current_agent], [`is_complete`][agents.result.RunResultStreaming.is_complete], [`cancel(...)`][agents.result.RunResultStreaming.cancel] 같은 스트리밍 전용 제어 기능을 추가합니다.
|
||||
|
||||
## 적절한 결과 접근 지점 선택
|
||||
|
||||
대부분의 애플리케이션에는 몇 가지 결과 속성이나 헬퍼만 필요합니다.
|
||||
|
||||
| 필요한 항목 | 사용 |
|
||||
| --- | --- |
|
||||
| 사용자에게 보여줄 최종 답변 | `final_output` |
|
||||
| 전체 로컬 대화 기록이 포함된, 재생 가능한 다음 턴 입력 목록 | `to_input_list()` |
|
||||
| 에이전트, 도구, 핸드오프, 승인 메타데이터가 포함된 풍부한 실행 항목 | `new_items` |
|
||||
| 일반적으로 다음 사용자 턴을 처리해야 하는 에이전트 | `last_agent` |
|
||||
| `previous_response_id`를 사용한 OpenAI Responses API 체이닝 | `last_response_id` |
|
||||
| 대기 중인 승인 및 재개 가능한 스냅샷 | `interruptions` 및 `to_state()` |
|
||||
| 현재 중첩 `Agent.as_tool()` 호출에 대한 메타데이터 | `agent_tool_invocation` |
|
||||
| 원문 모델 호출 또는 가드레일 진단 | `raw_responses` 및 가드레일 결과 배열 |
|
||||
|
||||
## 최종 출력
|
||||
|
||||
[`final_output`][agents.result.RunResultBase.final_output] 속성에는 마지막으로 실행된 에이전트의 최종 출력이 포함됩니다. 이는 다음 중 하나입니다.
|
||||
|
||||
- 마지막 에이전트에 `output_type`이 정의되어 있지 않았다면 `str`
|
||||
- 마지막 에이전트에 출력 타입이 정의되어 있었다면 `last_agent.output_type` 타입의 객체
|
||||
- 예를 들어 승인 인터럽션(중단 처리)에서 일시 중지되어 최종 출력이 생성되기 전에 실행이 중단된 경우 `None`
|
||||
|
||||
!!! note
|
||||
|
||||
`final_output`은 `Any` 타입으로 지정되어 있습니다. 핸드오프는 어떤 에이전트가 실행을 완료할지 바꿀 수 있으므로, SDK는 가능한 출력 타입의 전체 집합을 정적으로 알 수 없습니다.
|
||||
|
||||
스트리밍 모드에서는 스트림 처리가 완료될 때까지 `final_output`이 `None`으로 유지됩니다. 이벤트별 흐름은 [스트리밍](streaming.md)을 참고하세요.
|
||||
|
||||
## 입력, 다음 턴 기록 및 새 항목
|
||||
|
||||
이 접근 지점들은 서로 다른 질문에 답합니다.
|
||||
|
||||
| 속성 또는 헬퍼 | 포함 내용 | 적합한 용도 |
|
||||
| --- | --- | --- |
|
||||
| [`input`][agents.result.RunResultBase.input] | 이 실행 구간의 기본 입력입니다. 핸드오프 입력 필터가 기록을 다시 작성한 경우, 실행이 이어서 사용한 필터링된 입력을 반영합니다. | 이 실행이 실제로 입력으로 사용한 내용 감사 |
|
||||
| [`to_input_list()`][agents.result.RunResultBase.to_input_list] | 실행의 입력 항목 뷰입니다. 기본 `mode="preserve_all"`은 `new_items`에서 변환된 전체 기록을 유지합니다. `mode="normalized"`는 핸드오프 필터링이 모델 기록을 다시 작성할 때 표준 이어가기 입력을 우선합니다. | 수동 채팅 루프, 클라이언트 관리 대화 상태, 일반 항목 기록 검사 |
|
||||
| [`new_items`][agents.result.RunResultBase.new_items] | 에이전트, 도구, 핸드오프, 승인 메타데이터가 포함된 풍부한 [`RunItem`][agents.items.RunItem] 래퍼입니다. | 로그, UI, 감사, 디버깅 |
|
||||
| [`raw_responses`][agents.result.RunResultBase.raw_responses] | 실행의 각 모델 호출에서 나온 원문 [`ModelResponse`][agents.items.ModelResponse] 객체입니다. | 프로바이더 수준 진단 또는 원문 응답 검사 |
|
||||
|
||||
실제로는 다음과 같이 사용합니다.
|
||||
|
||||
- 실행의 일반 입력 항목 뷰가 필요할 때는 `to_input_list()`를 사용합니다.
|
||||
- 핸드오프 필터링 또는 중첩 핸드오프 기록 재작성 이후 다음 `Runner.run(..., input=...)` 호출에 사용할 표준 로컬 입력이 필요할 때는 `to_input_list(mode="normalized")`를 사용합니다.
|
||||
- SDK가 기록을 로드하고 저장해 주기를 원할 때는 [`session=...`](sessions/index.md)을 사용합니다.
|
||||
- `conversation_id` 또는 `previous_response_id`와 함께 OpenAI 서버 관리 상태를 사용하는 경우, 보통 `to_input_list()`를 다시 보내는 대신 새 사용자 입력만 전달하고 저장된 ID를 재사용합니다.
|
||||
- 로그, UI, 감사에 사용할 변환된 전체 기록이 필요할 때는 기본 `to_input_list()` 모드 또는 `new_items`를 사용합니다.
|
||||
|
||||
JavaScript SDK와 달리 Python은 모델 형태의 델타만을 위한 별도의 `output` 속성을 노출하지 않습니다. SDK 메타데이터가 필요할 때는 `new_items`를 사용하고, 원문 모델 페이로드가 필요할 때는 `raw_responses`를 검사하세요.
|
||||
|
||||
컴퓨터 도구 재생은 원문 Responses 페이로드 형태를 따릅니다. 프리뷰 모델의 `computer_call` 항목은 단일 `action`을 보존하고, `gpt-5.5` 컴퓨터 호출은 배치된 `actions[]`를 보존할 수 있습니다. [`to_input_list()`][agents.result.RunResultBase.to_input_list]와 [`RunState`][agents.run_state.RunState]는 모델이 생성한 형태를 그대로 유지하므로, 수동 재생, 일시 중지/재개 흐름, 저장된 대화 기록이 프리뷰 및 GA 컴퓨터 도구 호출 모두에서 계속 작동합니다. 로컬 실행 결과는 여전히 `new_items`의 `computer_call_output` 항목으로 나타납니다.
|
||||
|
||||
### 새 항목
|
||||
|
||||
[`new_items`][agents.result.RunResultBase.new_items]는 실행 중 발생한 일을 가장 풍부하게 보여줍니다. 일반적인 항목 타입은 다음과 같습니다.
|
||||
|
||||
- 어시스턴트 메시지용 [`MessageOutputItem`][agents.items.MessageOutputItem]
|
||||
- 추론 항목용 [`ReasoningItem`][agents.items.ReasoningItem]
|
||||
- Responses 도구 검색 요청 및 로드된 도구 검색 결과용 [`ToolSearchCallItem`][agents.items.ToolSearchCallItem] 및 [`ToolSearchOutputItem`][agents.items.ToolSearchOutputItem]
|
||||
- 도구 호출 및 그 결과용 [`ToolCallItem`][agents.items.ToolCallItem] 및 [`ToolCallOutputItem`][agents.items.ToolCallOutputItem]
|
||||
- 승인을 위해 일시 중지된 도구 호출용 [`ToolApprovalItem`][agents.items.ToolApprovalItem]
|
||||
- 핸드오프 요청 및 완료된 전달용 [`HandoffCallItem`][agents.items.HandoffCallItem] 및 [`HandoffOutputItem`][agents.items.HandoffOutputItem]
|
||||
|
||||
에이전트 연결, 도구 출력, 핸드오프 경계 또는 승인 경계가 필요할 때는 항상 `to_input_list()`보다 `new_items`를 선택하세요.
|
||||
|
||||
호스티드 툴 검색을 사용할 때는 모델이 내보낸 검색 요청을 보려면 `ToolSearchCallItem.raw_item`을 검사하고, 해당 턴에 어떤 네임스페이스, 함수 또는 호스티드 MCP 서버가 로드되었는지 보려면 `ToolSearchOutputItem.raw_item`을 검사하세요.
|
||||
|
||||
## 대화 계속 또는 재개
|
||||
|
||||
### 다음 턴 에이전트
|
||||
|
||||
[`last_agent`][agents.result.RunResultBase.last_agent]에는 마지막으로 실행된 에이전트가 포함됩니다. 이는 핸드오프 후 다음 사용자 턴에 재사용하기 가장 좋은 에이전트인 경우가 많습니다.
|
||||
|
||||
스트리밍 모드에서는 실행이 진행됨에 따라 [`RunResultStreaming.current_agent`][agents.result.RunResultStreaming.current_agent]가 업데이트되므로, 스트림이 끝나기 전에 핸드오프를 관찰할 수 있습니다.
|
||||
|
||||
### 인터럽션(중단 처리) 및 실행 상태
|
||||
|
||||
도구에 승인이 필요한 경우, 대기 중인 승인은 [`RunResult.interruptions`][agents.result.RunResult.interruptions] 또는 [`RunResultStreaming.interruptions`][agents.result.RunResultStreaming.interruptions]에 노출됩니다. 여기에는 직접 도구, 핸드오프 후 도달한 도구 또는 중첩된 [`Agent.as_tool()`][agents.agent.Agent.as_tool] 실행에서 발생한 승인이 포함될 수 있습니다.
|
||||
|
||||
[`to_state()`][agents.result.RunResult.to_state]를 호출하여 재개 가능한 [`RunState`][agents.run_state.RunState]를 캡처하고, 대기 중인 항목을 승인하거나 거부한 다음 `Runner.run(...)` 또는 `Runner.run_streamed(...)`로 재개하세요.
|
||||
|
||||
```python
|
||||
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)
|
||||
```
|
||||
|
||||
스트리밍 실행의 경우 먼저 [`stream_events()`][agents.result.RunResultStreaming.stream_events] 소비를 완료한 다음, `result.interruptions`를 검사하고 `result.to_state()`에서 재개하세요. 전체 승인 흐름은 [휴먼인더루프 (HITL)](human_in_the_loop.md)를 참고하세요.
|
||||
|
||||
### 서버 관리 지속
|
||||
|
||||
[`last_response_id`][agents.result.RunResultBase.last_response_id]는 실행에서 나온 최신 모델 응답 ID입니다. OpenAI Responses API 체인을 계속하려면 다음 턴에 이를 `previous_response_id`로 다시 전달하세요.
|
||||
|
||||
이미 `to_input_list()`, `session` 또는 `conversation_id`로 대화를 계속하고 있다면 보통 `last_response_id`가 필요하지 않습니다. 다단계 실행의 모든 모델 응답이 필요하면 대신 `raw_responses`를 검사하세요.
|
||||
|
||||
## Agent-as-tool 메타데이터
|
||||
|
||||
결과가 중첩된 [`Agent.as_tool()`][agents.agent.Agent.as_tool] 실행에서 나온 경우, [`agent_tool_invocation`][agents.result.RunResultBase.agent_tool_invocation]은 외부 도구 호출에 대한 불변 메타데이터를 노출합니다.
|
||||
|
||||
- `tool_name`
|
||||
- `tool_call_id`
|
||||
- `tool_arguments`
|
||||
|
||||
일반적인 최상위 실행에서는 `agent_tool_invocation`이 `None`입니다.
|
||||
|
||||
이는 특히 `custom_output_extractor` 내부에서 유용합니다. 중첩 결과를 후처리하는 동안 외부 도구 이름, 호출 ID 또는 원문 인수가 필요할 수 있기 때문입니다. 관련 `Agent.as_tool()` 패턴은 [도구](tools.md)를 참고하세요.
|
||||
|
||||
해당 중첩 실행의 파싱된 구조화 입력도 필요하다면 `context_wrapper.tool_input`을 읽으세요. 이는 [`RunState`][agents.run_state.RunState]가 중첩 도구 입력을 위해 일반화하여 직렬화하는 필드이며, `agent_tool_invocation`은 현재 중첩 호출에 대한 라이브 결과 접근자입니다.
|
||||
|
||||
## 스트리밍 수명 주기 및 진단
|
||||
|
||||
[`RunResultStreaming`][agents.result.RunResultStreaming]은 위와 동일한 결과 접근 지점을 상속하지만, 스트리밍 전용 제어 기능을 추가합니다.
|
||||
|
||||
- 의미론적 스트림 이벤트를 소비하는 [`stream_events()`][agents.result.RunResultStreaming.stream_events]
|
||||
- 실행 중 활성 에이전트를 추적하는 [`current_agent`][agents.result.RunResultStreaming.current_agent]
|
||||
- 스트리밍 실행이 완전히 끝났는지 확인하는 [`is_complete`][agents.result.RunResultStreaming.is_complete]
|
||||
- 실행을 즉시 또는 현재 턴 이후에 중지하는 [`cancel(...)`][agents.result.RunResultStreaming.cancel]
|
||||
|
||||
비동기 이터레이터가 끝날 때까지 `stream_events()`를 계속 소비하세요. 해당 이터레이터가 종료되기 전까지 스트리밍 실행은 완료된 것이 아니며, `final_output`, `interruptions`, `raw_responses` 같은 요약 속성과 세션 영속화 부수 효과는 마지막으로 보이는 토큰이 도착한 뒤에도 아직 확정되는 중일 수 있습니다.
|
||||
|
||||
`cancel()`을 호출한 경우에도 취소와 정리가 올바르게 완료될 수 있도록 `stream_events()`를 계속 소비하세요.
|
||||
|
||||
Python은 별도의 스트리밍 `completed` 프라미스나 `error` 속성을 노출하지 않습니다. 최종 스트리밍 실패는 `stream_events()`에서 예외가 발생하는 방식으로 표면화되며, `is_complete`는 실행이 종료 상태에 도달했는지 여부를 반영합니다.
|
||||
|
||||
### 원문 응답
|
||||
|
||||
[`raw_responses`][agents.result.RunResultBase.raw_responses]에는 실행 중 수집된 원문 모델 응답이 포함됩니다. 다단계 실행은 예를 들어 핸드오프 또는 반복되는 모델/도구/모델 사이클을 거치며 둘 이상의 응답을 생성할 수 있습니다.
|
||||
|
||||
[`last_response_id`][agents.result.RunResultBase.last_response_id]는 `raw_responses`의 마지막 항목에서 가져온 ID일 뿐입니다.
|
||||
|
||||
### 가드레일 결과
|
||||
|
||||
에이전트 수준 가드레일은 [`input_guardrail_results`][agents.result.RunResultBase.input_guardrail_results] 및 [`output_guardrail_results`][agents.result.RunResultBase.output_guardrail_results]로 노출됩니다.
|
||||
|
||||
도구 가드레일은 [`tool_input_guardrail_results`][agents.result.RunResultBase.tool_input_guardrail_results] 및 [`tool_output_guardrail_results`][agents.result.RunResultBase.tool_output_guardrail_results]로 별도로 노출됩니다.
|
||||
|
||||
이 배열들은 실행 전반에 걸쳐 누적되므로, 결정 사항을 기록하거나 추가 가드레일 메타데이터를 저장하거나 실행이 차단된 이유를 디버깅하는 데 유용합니다.
|
||||
|
||||
### 컨텍스트 및 사용량
|
||||
|
||||
[`context_wrapper`][agents.result.RunResultBase.context_wrapper]는 승인, 사용량, 중첩 `tool_input` 같은 SDK 관리 런타임 메타데이터와 함께 앱 컨텍스트를 노출합니다.
|
||||
|
||||
사용량은 `context_wrapper.usage`에서 추적됩니다. 스트리밍 실행의 경우 스트림의 최종 청크가 처리될 때까지 사용량 합계가 지연될 수 있습니다. 전체 래퍼 형태와 지속성 관련 주의 사항은 [컨텍스트 관리](context.md)를 참고하세요.
|
||||
@@ -0,0 +1,596 @@
|
||||
---
|
||||
search:
|
||||
exclude: true
|
||||
---
|
||||
# 에이전트 실행
|
||||
|
||||
[`Runner`][agents.run.Runner] 클래스를 통해 에이전트를 실행할 수 있습니다. 다음 3가지 옵션이 있습니다.
|
||||
|
||||
1. [`Runner.run()`][agents.run.Runner.run]: 비동기 방식으로 실행되며 [`RunResult`][agents.result.RunResult]를 반환합니다.
|
||||
2. [`Runner.run_sync()`][agents.run.Runner.run_sync]: 동기 메서드이며 내부적으로 `.run()`을 실행합니다.
|
||||
3. [`Runner.run_streamed()`][agents.run.Runner.run_streamed]: 비동기 방식으로 실행되며 [`RunResultStreaming`][agents.result.RunResultStreaming]을 반환합니다. 스트리밍 모드로 LLM을 호출하고, 이벤트가 수신되는 즉시 스트리밍합니다.
|
||||
|
||||
```python
|
||||
from agents import Agent, Runner
|
||||
|
||||
async def main():
|
||||
agent = Agent(name="Assistant", instructions="You are a helpful assistant")
|
||||
|
||||
result = await Runner.run(agent, "Write a haiku about recursion in programming.")
|
||||
print(result.final_output)
|
||||
# Code within the code,
|
||||
# Functions calling themselves,
|
||||
# Infinite loop's dance
|
||||
```
|
||||
|
||||
자세한 내용은 [결과 가이드](results.md)를 참고하세요.
|
||||
|
||||
## Runner 수명 주기 및 구성
|
||||
|
||||
### 에이전트 루프
|
||||
|
||||
`Runner`의 실행 메서드를 사용할 때 시작 에이전트와 입력을 전달합니다. 입력은 다음 중 하나일 수 있습니다.
|
||||
|
||||
- 문자열(사용자 메시지로 처리)
|
||||
- OpenAI Responses API 형식의 입력 항목 목록
|
||||
- 인터럽션(중단 처리)된 실행을 재개할 때의 [`RunState`][agents.run_state.RunState]
|
||||
|
||||
그런 다음 Runner가 다음 루프를 실행합니다.
|
||||
|
||||
1. 현재 입력을 사용하여 현재 에이전트의 LLM을 호출합니다.
|
||||
2. LLM이 출력을 생성합니다.
|
||||
1. LLM이 `final_output`을 반환하면 루프를 종료하고 결과를 반환합니다.
|
||||
2. LLM이 핸드오프를 수행하면 현재 에이전트와 입력을 업데이트하고 루프를 다시 실행합니다.
|
||||
3. LLM이 도구 호출을 생성하면 해당 도구 호출을 실행하고 결과를 추가한 후 루프를 다시 실행합니다.
|
||||
3. 전달된 `max_turns`를 초과하면 [`MaxTurnsExceeded`][agents.exceptions.MaxTurnsExceeded] 예외가 발생합니다. 이 턴 제한을 비활성화하려면 `max_turns=None`을 전달하세요.
|
||||
|
||||
!!! note
|
||||
|
||||
LLM 출력이 "최종 출력"으로 간주되는 기준은 원하는 유형의 텍스트 출력을 생성하고 도구 호출이 없는 것입니다.
|
||||
|
||||
### 스트리밍
|
||||
|
||||
스트리밍을 사용하면 LLM이 실행되는 동안 스트리밍 이벤트도 수신할 수 있습니다. 스트림이 완료되면 [`RunResultStreaming`][agents.result.RunResultStreaming]에 새로 생성된 모든 출력을 비롯한 전체 실행 정보가 포함됩니다. 스트리밍 이벤트를 받으려면 `.stream_events()`를 호출할 수 있습니다. 자세한 내용은 [스트리밍 가이드](streaming.md)를 참고하세요.
|
||||
|
||||
#### Responses WebSocket 전송(선택적 도우미)
|
||||
|
||||
OpenAI Responses WebSocket 전송을 활성화해도 기존 `Runner` API를 계속 사용할 수 있습니다. 연결 재사용을 위해 WebSocket 세션 도우미를 사용하는 것이 권장되지만 필수는 아닙니다.
|
||||
|
||||
이는 WebSocket 전송을 통한 Responses API이며, [Realtime API](realtime/guide.md)가 아닙니다.
|
||||
|
||||
전송 선택 규칙과 구체적인 모델 객체 또는 사용자 지정 제공자에 관한 주의 사항은 [모델](models/index.md#responses-websocket-transport)을 참고하세요.
|
||||
|
||||
##### 패턴 1: 세션 도우미 미사용
|
||||
|
||||
WebSocket 전송만 필요하고 SDK가 공유 제공자나 세션을 관리할 필요가 없을 때 사용합니다.
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
|
||||
from agents import Agent, Runner, set_default_openai_responses_transport
|
||||
|
||||
|
||||
async def main():
|
||||
set_default_openai_responses_transport("websocket")
|
||||
|
||||
agent = Agent(name="Assistant", instructions="Be concise.")
|
||||
result = Runner.run_streamed(agent, "Summarize recursion in one sentence.")
|
||||
|
||||
async for event in result.stream_events():
|
||||
if event.type == "raw_response_event":
|
||||
continue
|
||||
print(event.type)
|
||||
|
||||
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
이 패턴은 단일 실행에 적합합니다. `Runner.run()` / `Runner.run_streamed()`를 반복적으로 호출하면 동일한 `RunConfig` / 제공자 인스턴스를 직접 재사용하지 않는 한 실행할 때마다 다시 연결될 수 있습니다.
|
||||
|
||||
##### 패턴 2: `responses_websocket_session()` 사용(다중 턴 재사용에 권장)
|
||||
|
||||
동일한 `run_config`를 상속하는 중첩된 에이전트 도구 호출을 포함하여 여러 실행에서 WebSocket을 지원하는 공유 제공자와 `RunConfig`를 사용하려면 [`responses_websocket_session()`][agents.responses_websocket_session]을 사용하세요.
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
|
||||
from agents import Agent, responses_websocket_session
|
||||
|
||||
|
||||
async def main():
|
||||
agent = Agent(name="Assistant", instructions="Be concise.")
|
||||
|
||||
async with responses_websocket_session(
|
||||
responses_websocket_options={"ping_interval": 20.0, "ping_timeout": 60.0},
|
||||
) as ws:
|
||||
first = ws.run_streamed(agent, "Say hello in one short sentence.")
|
||||
async for _event in first.stream_events():
|
||||
pass
|
||||
|
||||
second = ws.run_streamed(
|
||||
agent,
|
||||
"Now say goodbye.",
|
||||
previous_response_id=first.last_response_id,
|
||||
)
|
||||
async for _event in second.stream_events():
|
||||
pass
|
||||
|
||||
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
컨텍스트가 종료되기 전에 스트리밍된 결과를 모두 소비하세요. WebSocket 요청이 아직 진행 중일 때 컨텍스트를 종료하면 공유 연결이 강제로 닫힐 수 있습니다.
|
||||
|
||||
긴 추론 턴에서 WebSocket 연결 유지 시간 초과가 발생하면 `ping_timeout`을 늘리거나 `ping_timeout=None`으로 설정하여 하트비트 시간 초과를 비활성화하세요. WebSocket 지연 시간보다 안정성이 더 중요한 실행에는 HTTP/SSE 전송을 사용하세요.
|
||||
|
||||
### 실행 구성
|
||||
|
||||
`run_config` 매개변수를 사용하면 에이전트 실행의 일부 전역 설정을 구성할 수 있습니다.
|
||||
|
||||
#### 일반적인 실행 구성 카테고리
|
||||
|
||||
각 에이전트 정의를 변경하지 않고 단일 실행의 동작을 재정의하려면 `RunConfig`를 사용하세요.
|
||||
|
||||
##### 모델, 제공자 및 세션 기본값
|
||||
|
||||
- [`model`][agents.run.RunConfig.model]: 각 Agent에 설정된 `model`과 관계없이 사용할 전역 LLM 모델을 설정할 수 있습니다.
|
||||
- [`model_provider`][agents.run.RunConfig.model_provider]: 모델 이름을 조회하는 모델 제공자이며 기본값은 OpenAI입니다.
|
||||
- [`model_settings`][agents.run.RunConfig.model_settings]: 에이전트별 설정을 재정의합니다. 예를 들어 전역 `temperature` 또는 `top_p`를 설정할 수 있습니다.
|
||||
- [`session_settings`][agents.run.RunConfig.session_settings]: 실행 중 기록을 가져올 때 세션 수준 기본값(예: `SessionSettings(limit=...)`)을 재정의합니다.
|
||||
- [`session_input_callback`][agents.run.RunConfig.session_input_callback]: Sessions를 사용할 때 각 턴 전에 새로운 사용자 입력을 세션 기록과 병합하는 방식을 사용자 지정합니다. 콜백은 동기 또는 비동기일 수 있습니다.
|
||||
|
||||
##### 가드레일, 핸드오프 및 모델 입력 구성
|
||||
|
||||
- [`input_guardrails`][agents.run.RunConfig.input_guardrails], [`output_guardrails`][agents.run.RunConfig.output_guardrails]: 모든 실행에 포함할 입력 또는 출력 가드레일 목록입니다.
|
||||
- [`handoff_input_filter`][agents.run.RunConfig.handoff_input_filter]: 핸드오프에 자체 입력 필터가 아직 없는 경우 모든 핸드오프에 적용할 전역 입력 필터입니다. 입력 필터를 사용하면 새 에이전트로 전송되는 입력을 수정할 수 있습니다. 자세한 내용은 [`Handoff.input_filter`][agents.handoffs.Handoff.input_filter] 문서를 참고하세요.
|
||||
- [`nest_handoff_history`][agents.run.RunConfig.nest_handoff_history]: 다음 에이전트를 호출하기 전에 이전 트랜스크립트를 단일 어시스턴트 메시지로 축약하는 선택적 베타 기능입니다. 중첩된 핸드오프를 안정화하는 동안에는 기본적으로 비활성화되어 있습니다. 활성화하려면 `True`로 설정하고, 원문 트랜스크립트를 그대로 전달하려면 `False`로 두세요. [Runner 메서드][agents.run.Runner]는 `RunConfig`를 전달하지 않으면 자동으로 생성하므로 빠른 시작과 예제에서는 기본적으로 비활성화된 상태를 유지하며, 명시적인 [`Handoff.input_filter`][agents.handoffs.Handoff.input_filter] 콜백은 계속해서 이 설정보다 우선합니다. 개별 핸드오프는 [`Handoff.nest_handoff_history`][agents.handoffs.Handoff.nest_handoff_history]를 통해 이 설정을 재정의할 수 있습니다.
|
||||
- [`handoff_history_mapper`][agents.run.RunConfig.handoff_history_mapper]: `nest_handoff_history`를 선택할 때마다 정규화된 트랜스크립트(기록 + 핸드오프 항목)를 받는 선택적 호출 가능 객체입니다. 다음 에이전트로 전달할 정확한 입력 항목 목록을 반환해야 하므로, 전체 핸드오프 필터를 작성하지 않고도 기본 제공 요약을 대체할 수 있습니다.
|
||||
- [`call_model_input_filter`][agents.run.RunConfig.call_model_input_filter]: 모델 호출 직전에 완전히 준비된 모델 입력(instructions 및 입력 항목)을 수정하는 훅입니다. 예를 들어 기록을 줄이거나 시스템 프롬프트를 삽입할 수 있습니다.
|
||||
- [`reasoning_item_id_policy`][agents.run.RunConfig.reasoning_item_id_policy]: Runner가 이전 출력을 다음 턴의 모델 입력으로 변환할 때 추론 항목 ID를 유지할지 생략할지 제어합니다.
|
||||
|
||||
##### 트레이싱 및 관측 가능성
|
||||
|
||||
- [`tracing_disabled`][agents.run.RunConfig.tracing_disabled]: 전체 실행에서 [트레이싱](tracing.md)을 비활성화할 수 있습니다.
|
||||
- [`tracing`][agents.run.RunConfig.tracing]: 실행별 트레이싱 API 키와 같은 트레이스 내보내기 설정을 재정의하려면 [`TracingConfig`][agents.tracing.TracingConfig]를 전달합니다.
|
||||
- [`trace_include_sensitive_data`][agents.run.RunConfig.trace_include_sensitive_data]: 트레이스에 LLM 및 도구 호출 입력/출력과 같이 잠재적으로 민감한 데이터를 포함할지 구성합니다.
|
||||
- [`workflow_name`][agents.run.RunConfig.workflow_name], [`trace_id`][agents.run.RunConfig.trace_id], [`group_id`][agents.run.RunConfig.group_id]: 실행의 트레이싱 워크플로 이름, 트레이스 ID 및 트레이스 그룹 ID를 설정합니다. 최소한 `workflow_name`은 설정하는 것이 좋습니다. 그룹 ID는 여러 실행의 트레이스를 연결할 수 있는 선택적 필드입니다.
|
||||
- [`trace_metadata`][agents.run.RunConfig.trace_metadata]: 모든 트레이스에 포함할 메타데이터입니다.
|
||||
|
||||
##### 도구 실행, 승인 및 도구 오류 동작
|
||||
|
||||
- [`tool_execution`][agents.run.RunConfig.tool_execution]: 한 번에 실행되는 함수 도구 수를 제한하는 등 로컬 도구 호출의 SDK 측 실행 동작을 구성합니다.
|
||||
- [`tool_not_found_behavior`][agents.run.RunConfig.tool_not_found_behavior]: 모델이 생성한 함수 도구 호출을 확인할 수 없을 때 Runner가 이를 처리하는 방식을 구성합니다. 기본적으로 `ModelBehaviorError`가 발생하며, 대신 모델에 표시되는 오류 출력을 반환하도록 선택할 수 있습니다.
|
||||
- [`tool_error_formatter`][agents.run.RunConfig.tool_error_formatter]: 승인 거부 및 선택적 도구 미발견 출력과 같이 모델에 표시되는 도구 오류 메시지를 사용자 지정합니다.
|
||||
|
||||
중첩된 핸드오프는 선택적 베타 기능으로 제공됩니다. `RunConfig(nest_handoff_history=True)`를 전달하거나 특정 핸드오프에서 `handoff(..., nest_handoff_history=True)`를 설정하여 축약된 트랜스크립트 동작을 활성화하세요. 원문 트랜스크립트를 유지하려면(기본값) 플래그를 설정하지 않거나 대화를 필요한 방식 그대로 전달하는 `handoff_input_filter` 또는 `handoff_history_mapper`를 제공하세요. 사용자 지정 매퍼를 작성하지 않고 생성된 요약에 사용되는 래퍼 텍스트를 변경하려면 [`set_conversation_history_wrappers`][agents.handoffs.set_conversation_history_wrappers]를 호출하세요. 기본값으로 복원하려면 [`reset_conversation_history_wrappers`][agents.handoffs.reset_conversation_history_wrappers]를 호출합니다.
|
||||
|
||||
#### 실행 구성 세부 정보
|
||||
|
||||
##### `tool_execution`
|
||||
|
||||
로컬 함수 도구의 동시 실행 수 제한과 같이 단일 실행에서 로컬 함수 도구에 대한 SDK 측 동작을 구성하려면 `tool_execution`을 사용하세요.
|
||||
|
||||
```python
|
||||
from agents import Agent, RunConfig, Runner, ToolExecutionConfig
|
||||
|
||||
agent = Agent(name="Assistant", tools=[...])
|
||||
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
"Run the required tool calls.",
|
||||
run_config=RunConfig(
|
||||
tool_execution=ToolExecutionConfig(
|
||||
max_function_tool_concurrency=2,
|
||||
pre_approval_tool_input_guardrails=True,
|
||||
),
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
`max_function_tool_concurrency=None`은 기본 동작을 유지합니다. 모델이 한 턴에 여러 함수 도구 호출을 생성하면 SDK가 생성된 모든 로컬 함수 도구 호출을 시작합니다. 동시에 실행되는 로컬 함수 도구 수를 제한하려면 정수 값을 설정하세요.
|
||||
|
||||
이는 제공자 측 [`ModelSettings.parallel_tool_calls`][agents.model_settings.ModelSettings.parallel_tool_calls]와 별개입니다. `parallel_tool_calls`는 모델이 단일 응답에서 여러 도구 호출을 생성할 수 있는지를 제어합니다. `tool_execution.max_function_tool_concurrency`는 모델이 도구 호출을 생성한 후 SDK가 로컬 함수 도구 호출을 실행하는 방식을 제어합니다.
|
||||
|
||||
`pre_approval_tool_input_guardrails=False`는 기본 승인 흐름을 유지합니다. 함수 도구에 승인이 필요한 경우 실행이 먼저 일시 중지되고, 도구 입력 가드레일은 승인 후 실행 직전에만 실행됩니다. 대기 중인 승인 인터럽션(중단 처리)이 생성되기 전에 함수 도구 입력 가드레일을 실행하려면 `True`로 설정하세요. 이 사전 승인 검사를 통과한 호출도 승인 후 동일한 입력 가드레일을 다시 실행하므로, 시간에 민감한 검사가 실행 전에 다시 검증됩니다.
|
||||
|
||||
##### `tool_not_found_behavior`
|
||||
|
||||
기본적으로 모델이 현재 에이전트에서 사용할 수 있는 함수 도구와 일치하지 않는 함수 도구 호출을 생성하면 Runner가 `ModelBehaviorError`를 발생시킵니다.
|
||||
|
||||
실행을 복구 가능한 상태로 유지하려면 `tool_not_found_behavior="return_error_to_model"`로 설정하세요. 이 모드에서는 SDK가 확인할 수 없는 도구 호출에 대한 `function_call_output`을 추가하고 모델을 다시 실행하므로, 모델이 사용 가능한 도구를 선택하거나 해당 도구 없이 응답할 수 있습니다.
|
||||
|
||||
```python
|
||||
from agents import Agent, RunConfig, Runner
|
||||
|
||||
agent = Agent(name="Assistant", tools=[...])
|
||||
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
"Handle this request with the available tools.",
|
||||
run_config=RunConfig(tool_not_found_behavior="return_error_to_model"),
|
||||
)
|
||||
```
|
||||
|
||||
현재 이 옵션은 확인할 수 없는 함수 도구 호출에만 적용됩니다. 그 밖의 잘못된 도구 페이로드에는 기존 오류 동작이 계속 적용됩니다.
|
||||
|
||||
##### `tool_error_formatter`
|
||||
|
||||
SDK가 모델에 표시되는 도구 오류 출력을 생성할 때 모델에 반환되는 메시지를 사용자 지정하려면 `tool_error_formatter`를 사용하세요.
|
||||
|
||||
포매터는 다음 필드가 포함된 [`ToolErrorFormatterArgs`][agents.run_config.ToolErrorFormatterArgs]를 받습니다.
|
||||
|
||||
- `kind`: `"approval_rejected"` 또는 `"tool_not_found"`과 같은 오류 카테고리
|
||||
- `tool_type`: 도구 런타임(`"function"`, `"computer"`, `"shell"`, `"apply_patch"` 또는 `"custom"`)
|
||||
- `tool_name`: 도구 이름
|
||||
- `call_id`: 도구 호출 ID
|
||||
- `default_message`: 모델에 표시되는 SDK의 기본 메시지
|
||||
- `run_context`: 활성 실행 컨텍스트 래퍼
|
||||
|
||||
메시지를 대체하려면 문자열을 반환하고, SDK 기본값을 사용하려면 `None`을 반환하세요.
|
||||
|
||||
```python
|
||||
from agents import Agent, RunConfig, Runner, ToolErrorFormatterArgs
|
||||
|
||||
|
||||
def format_rejection(args: ToolErrorFormatterArgs[None]) -> str | None:
|
||||
if args.kind == "approval_rejected":
|
||||
return (
|
||||
f"Tool call '{args.tool_name}' was rejected by a human reviewer. "
|
||||
"Ask for confirmation or propose a safer alternative."
|
||||
)
|
||||
if args.kind == "tool_not_found":
|
||||
return f"Tool '{args.tool_name}' is not available. Choose one of the listed tools."
|
||||
return None
|
||||
|
||||
|
||||
agent = Agent(name="Assistant")
|
||||
result = Runner.run_sync(
|
||||
agent,
|
||||
"Please delete the production database.",
|
||||
run_config=RunConfig(tool_error_formatter=format_rejection),
|
||||
)
|
||||
```
|
||||
|
||||
##### `reasoning_item_id_policy`
|
||||
|
||||
`reasoning_item_id_policy`는 Runner가 기록을 다음 턴으로 전달할 때 추론 항목이 다음 턴의 모델 입력으로 변환되는 방식을 제어합니다(예: `RunResult.to_input_list()` 또는 세션 기반 실행을 사용하는 경우).
|
||||
|
||||
- `None` 또는 `"preserve"`(기본값): 추론 항목 ID 유지
|
||||
- `"omit"`: 생성된 다음 턴 입력에서 추론 항목 ID 제거
|
||||
|
||||
주로 추론 항목이 `id`와 함께 전송되지만 필수 후속 항목 없이 전송되어 발생하는 Responses API 400 오류 유형에 대한 선택적 완화책으로 `"omit"`을 사용하세요(예: `Item 'rs_...' of type 'reasoning' was provided without its required following item.`).
|
||||
|
||||
SDK가 이전 출력에서 후속 입력을 구성하는 다중 턴 에이전트 실행에서 이런 문제가 발생할 수 있습니다. 여기에는 세션 지속성, 서버 관리형 대화 델타, 스트리밍/비스트리밍 후속 턴 및 재개 경로가 포함됩니다. 이때 추론 항목 ID는 유지되지만 제공자는 해당 ID가 대응하는 후속 항목과 계속 쌍을 이루도록 요구할 수 있습니다.
|
||||
|
||||
`reasoning_item_id_policy="omit"`으로 설정하면 추론 콘텐츠는 유지하되 추론 항목의 `id`를 제거하여 SDK가 생성한 후속 입력에서 해당 API 불변 조건이 위반되는 것을 방지합니다.
|
||||
|
||||
적용 범위 참고 사항:
|
||||
|
||||
- SDK가 후속 입력을 구성할 때 생성하거나 전달하는 추론 항목만 변경합니다.
|
||||
- 사용자가 제공한 초기 입력 항목은 다시 작성하지 않습니다.
|
||||
- 이 정책이 적용된 후에도 `call_model_input_filter`가 의도적으로 추론 ID를 다시 추가할 수 있습니다.
|
||||
|
||||
## 상태 및 대화 관리
|
||||
|
||||
### 메모리 전략 선택
|
||||
|
||||
다음 턴에 상태를 전달하는 일반적인 방법은 네 가지입니다.
|
||||
|
||||
| 전략 | 상태 저장 위치 | 적합한 용도 | 다음 턴에 전달하는 항목 |
|
||||
| --- | --- | --- | --- |
|
||||
| `result.to_input_list()` | 애플리케이션 메모리 | 작은 채팅 루프, 완전한 수동 제어, 모든 제공자 | `result.to_input_list()`의 목록과 다음 사용자 메시지 |
|
||||
| `session` | 스토리지 및 SDK | 지속적인 채팅 상태, 재개 가능한 실행, 사용자 지정 저장소 | 동일한 `session` 인스턴스 또는 동일한 저장소를 가리키는 다른 인스턴스 |
|
||||
| `conversation_id` | OpenAI Conversations API | 작업자 또는 서비스 간에 공유하려는 이름이 지정된 서버 측 대화 | 동일한 `conversation_id`와 새 사용자 턴만 전달 |
|
||||
| `previous_response_id` | OpenAI Responses API | 대화 리소스를 생성하지 않는 경량 서버 관리형 연속 실행 | `result.last_response_id`와 새 사용자 턴만 전달 |
|
||||
|
||||
`result.to_input_list()`와 `session`은 클라이언트 관리형입니다. `conversation_id`와 `previous_response_id`는 OpenAI 관리형이며 OpenAI Responses API를 사용할 때만 적용됩니다. 대부분의 애플리케이션에서는 대화별로 하나의 지속성 전략을 선택하세요. 클라이언트 관리형 기록과 OpenAI 관리형 상태를 혼합하면 두 계층을 의도적으로 조정하지 않는 한 컨텍스트가 중복될 수 있습니다.
|
||||
|
||||
!!! note
|
||||
|
||||
세션 지속성은 동일한 실행에서 서버 관리형 대화 설정
|
||||
(`conversation_id`, `previous_response_id` 또는 `auto_previous_response_id`)과 함께 사용할 수
|
||||
없습니다. 호출마다 하나의 방식을 선택하세요.
|
||||
|
||||
### 대화 및 채팅 스레드
|
||||
|
||||
실행 메서드 중 하나를 호출하면 하나 이상의 에이전트가 실행되어 하나 이상의 LLM 호출이 발생할 수 있지만, 채팅 대화에서는 단일 논리적 턴을 나타냅니다. 예를 들면 다음과 같습니다.
|
||||
|
||||
1. 사용자 턴: 사용자가 텍스트 입력
|
||||
2. Runner 실행: 첫 번째 에이전트가 LLM을 호출하고 도구를 실행한 뒤 두 번째 에이전트로 핸드오프하고, 두 번째 에이전트가 추가 도구를 실행한 다음 출력을 생성
|
||||
|
||||
에이전트 실행이 끝나면 사용자에게 표시할 내용을 선택할 수 있습니다. 예를 들어 에이전트가 생성한 모든 새 항목을 사용자에게 표시하거나 최종 출력만 표시할 수 있습니다. 어느 쪽이든 사용자가 후속 질문을 하면 실행 메서드를 다시 호출할 수 있습니다.
|
||||
|
||||
#### 수동 대화 관리
|
||||
|
||||
[`RunResultBase.to_input_list()`][agents.result.RunResultBase.to_input_list] 메서드를 사용해 다음 턴의 입력을 가져오는 방식으로 대화 기록을 수동으로 관리할 수 있습니다.
|
||||
|
||||
```python
|
||||
async def main():
|
||||
agent = Agent(name="Assistant", instructions="Reply very concisely.")
|
||||
|
||||
thread_id = "thread_123" # Example thread ID
|
||||
with trace(workflow_name="Conversation", group_id=thread_id):
|
||||
# First turn
|
||||
result = await Runner.run(agent, "What city is the Golden Gate Bridge in?")
|
||||
print(result.final_output)
|
||||
# San Francisco
|
||||
|
||||
# Second turn
|
||||
new_input = result.to_input_list() + [{"role": "user", "content": "What state is it in?"}]
|
||||
result = await Runner.run(agent, new_input)
|
||||
print(result.final_output)
|
||||
# California
|
||||
```
|
||||
|
||||
#### 세션을 통한 자동 대화 관리
|
||||
|
||||
더 간단한 방법으로 [Sessions](sessions/index.md)를 사용하면 `.to_input_list()`를 직접 호출하지 않고도 대화 기록을 자동으로 처리할 수 있습니다.
|
||||
|
||||
```python
|
||||
from agents import Agent, Runner, SQLiteSession
|
||||
|
||||
async def main():
|
||||
agent = Agent(name="Assistant", instructions="Reply very concisely.")
|
||||
|
||||
# Create session instance
|
||||
session = SQLiteSession("conversation_123")
|
||||
|
||||
thread_id = "thread_123" # Example thread ID
|
||||
with trace(workflow_name="Conversation", group_id=thread_id):
|
||||
# First turn
|
||||
result = await Runner.run(agent, "What city is the Golden Gate Bridge in?", session=session)
|
||||
print(result.final_output)
|
||||
# San Francisco
|
||||
|
||||
# Second turn - agent automatically remembers previous context
|
||||
result = await Runner.run(agent, "What state is it in?", session=session)
|
||||
print(result.final_output)
|
||||
# California
|
||||
```
|
||||
|
||||
Sessions는 다음 작업을 자동으로 수행합니다.
|
||||
|
||||
- 각 실행 전에 대화 기록 검색
|
||||
- 각 실행 후 새 메시지 저장
|
||||
- 서로 다른 세션 ID에 대해 별도의 대화 유지
|
||||
|
||||
자세한 내용은 [Sessions 문서](sessions/index.md)를 참고하세요.
|
||||
|
||||
|
||||
#### 서버 관리형 대화
|
||||
|
||||
`to_input_list()` 또는 `Sessions`를 사용해 로컬에서 처리하는 대신 OpenAI 대화 상태 기능이 서버 측에서 대화 상태를 관리하도록 할 수도 있습니다. 이를 통해 이전의 모든 메시지를 직접 다시 보내지 않고도 대화 기록을 유지할 수 있습니다. 아래의 서버 관리형 방식 중 하나를 사용할 때는 각 요청에 새 턴의 입력만 전달하고 저장된 ID를 재사용하세요. 자세한 내용은 [OpenAI 대화 상태 가이드](https://platform.openai.com/docs/guides/conversation-state?api-mode=responses)를 참고하세요.
|
||||
|
||||
OpenAI는 여러 턴에 걸쳐 상태를 추적하는 두 가지 방법을 제공합니다.
|
||||
|
||||
##### 1. `conversation_id` 사용
|
||||
|
||||
먼저 OpenAI Conversations API를 사용하여 대화를 생성한 다음 이후의 모든 호출에서 해당 ID를 재사용합니다.
|
||||
|
||||
```python
|
||||
from agents import Agent, Runner
|
||||
from openai import AsyncOpenAI
|
||||
|
||||
client = AsyncOpenAI()
|
||||
|
||||
async def main():
|
||||
agent = Agent(name="Assistant", instructions="Reply very concisely.")
|
||||
|
||||
# Create a server-managed conversation
|
||||
conversation = await client.conversations.create()
|
||||
conv_id = conversation.id
|
||||
|
||||
while True:
|
||||
user_input = input("You: ")
|
||||
result = await Runner.run(agent, user_input, conversation_id=conv_id)
|
||||
print(f"Assistant: {result.final_output}")
|
||||
```
|
||||
|
||||
##### 2. `previous_response_id` 사용
|
||||
|
||||
또 다른 옵션은 각 턴을 이전 턴의 응답 ID에 명시적으로 연결하는 **응답 체이닝**입니다.
|
||||
|
||||
```python
|
||||
from agents import Agent, Runner
|
||||
|
||||
async def main():
|
||||
agent = Agent(name="Assistant", instructions="Reply very concisely.")
|
||||
|
||||
previous_response_id = None
|
||||
|
||||
while True:
|
||||
user_input = input("You: ")
|
||||
|
||||
# Setting auto_previous_response_id=True enables response chaining automatically
|
||||
# for the first turn, even when there's no actual previous response ID yet.
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
user_input,
|
||||
previous_response_id=previous_response_id,
|
||||
auto_previous_response_id=True,
|
||||
)
|
||||
previous_response_id = result.last_response_id
|
||||
print(f"Assistant: {result.final_output}")
|
||||
```
|
||||
|
||||
실행이 승인을 위해 일시 중지되고 [`RunState`][agents.run_state.RunState]에서 재개하면 SDK는 저장된 `conversation_id` / `previous_response_id` / `auto_previous_response_id` 설정을 유지하므로 재개된 턴이 동일한 서버 관리형 대화에서 계속됩니다.
|
||||
|
||||
`conversation_id`와 `previous_response_id`는 상호 배타적입니다. 여러 시스템에서 공유할 수 있는 이름이 지정된 대화 리소스가 필요하면 `conversation_id`를 사용하세요. 한 턴에서 다음 턴으로 이어지는 가장 가벼운 Responses API 연속 실행 기본 구성 요소가 필요하면 `previous_response_id`를 사용하세요.
|
||||
|
||||
!!! note
|
||||
|
||||
SDK는 `conversation_locked` 오류를 백오프 방식으로 자동 재시도합니다. 서버 관리형
|
||||
대화 실행에서는 재시도 전에 내부 대화 추적기의 입력을 되돌려 동일하게 준비된
|
||||
항목을 문제없이 다시 전송할 수 있도록 합니다.
|
||||
|
||||
`conversation_id`, `previous_response_id` 또는 `auto_previous_response_id`와 함께 사용할 수 없는
|
||||
로컬 세션 기반 실행에서도 SDK는 최근에 저장된 입력 항목을 최선의 방식으로
|
||||
롤백하여 재시도 후 기록 항목의 중복을 줄입니다.
|
||||
|
||||
이 호환성 재시도는 `ModelSettings.retry`를 구성하지 않아도 수행됩니다. 모델 요청에
|
||||
대해 더 광범위한 선택적 재시도 동작을 사용하려면 [Runner 관리형 재시도](models/index.md#runner-managed-retries)를 참고하세요.
|
||||
|
||||
## 훅 및 사용자 지정
|
||||
|
||||
### 모델 호출 입력 필터
|
||||
|
||||
모델 호출 직전에 모델 입력을 수정하려면 `call_model_input_filter`를 사용하세요. 이 훅은 현재 에이전트, 컨텍스트 및 결합된 입력 항목(존재하는 경우 세션 기록 포함)을 받고 새로운 `ModelInputData`를 반환합니다.
|
||||
|
||||
반환 값은 [`ModelInputData`][agents.run.ModelInputData] 객체여야 합니다. 해당 객체의 `input` 필드는 필수이며 입력 항목 목록이어야 합니다. 다른 형태를 반환하면 `UserError`가 발생합니다.
|
||||
|
||||
```python
|
||||
from agents import Agent, Runner, RunConfig
|
||||
from agents.run import CallModelData, ModelInputData
|
||||
|
||||
def drop_old_messages(data: CallModelData[None]) -> ModelInputData:
|
||||
# Keep only the last 5 items and preserve existing instructions.
|
||||
trimmed = data.model_data.input[-5:]
|
||||
return ModelInputData(input=trimmed, instructions=data.model_data.instructions)
|
||||
|
||||
agent = Agent(name="Assistant", instructions="Answer concisely.")
|
||||
result = Runner.run_sync(
|
||||
agent,
|
||||
"Explain quines",
|
||||
run_config=RunConfig(call_model_input_filter=drop_old_messages),
|
||||
)
|
||||
```
|
||||
|
||||
Runner는 준비된 입력 목록의 복사본을 훅에 전달하므로 호출자의 원래 목록을 제자리에서 변경하지 않고도 항목을 줄이거나 대체하거나 순서를 변경할 수 있습니다.
|
||||
|
||||
세션을 사용하는 경우 세션 기록을 이미 불러와 현재 턴과 병합한 후 `call_model_input_filter`가 실행됩니다. 이보다 앞선 병합 단계 자체를 사용자 지정하려면 [`session_input_callback`][agents.run.RunConfig.session_input_callback]을 사용하세요.
|
||||
|
||||
`conversation_id`, `previous_response_id` 또는 `auto_previous_response_id`를 사용하여 OpenAI 서버 관리형 대화 상태를 사용하는 경우 훅은 다음 Responses API 호출을 위해 준비된 페이로드에서 실행됩니다. 이 페이로드는 이전 기록의 전체 재생이 아니라 이미 새 턴의 델타만 나타낼 수 있습니다. 반환한 항목만 해당 서버 관리형 연속 실행에서 전송된 것으로 표시됩니다.
|
||||
|
||||
민감한 데이터를 제거하거나, 긴 기록을 줄이거나, 추가 시스템 지침을 삽입하려면 `run_config`를 통해 실행별로 훅을 설정하세요.
|
||||
|
||||
## 오류 및 복구
|
||||
|
||||
### 오류 처리기
|
||||
|
||||
모든 `Runner` 진입점은 오류 종류를 키로 사용하는 딕셔너리인 `error_handlers`를 허용합니다. 지원되는 키는 `"max_turns"`, `"model_refusal"` 및 `"invalid_final_output"`입니다. 해당 오류로 실행을 종료하는 대신 제어된 최종 출력을 반환하려면 이를 사용하세요.
|
||||
|
||||
```python
|
||||
from agents import (
|
||||
Agent,
|
||||
RunErrorHandlerInput,
|
||||
RunErrorHandlerResult,
|
||||
Runner,
|
||||
)
|
||||
|
||||
agent = Agent(name="Assistant", instructions="Be concise.")
|
||||
|
||||
|
||||
def on_max_turns(_data: RunErrorHandlerInput[None]) -> RunErrorHandlerResult:
|
||||
return RunErrorHandlerResult(
|
||||
final_output="I couldn't finish within the turn limit. Please narrow the request.",
|
||||
include_in_history=False,
|
||||
)
|
||||
|
||||
|
||||
result = Runner.run_sync(
|
||||
agent,
|
||||
"Analyze this long transcript",
|
||||
max_turns=3,
|
||||
error_handlers={"max_turns": on_max_turns},
|
||||
)
|
||||
print(result.final_output)
|
||||
```
|
||||
|
||||
모델 메시지가 에이전트의 구조화된 `output_type`에 대해 검증되지 않거나 모델이 구조화된 최종 메시지를 반환하지 않을 때 `"invalid_final_output"`을 사용하세요. 처리기는 애플리케이션별 대체 값을 반환할 수 있으며, SDK는 동일한 `output_type`에 대해 이를 검증합니다. 모델 호출을 재시도하거나 도구의 부작용을 다시 실행하지는 않습니다. `None`을 반환하면 복구를 수행하지 않습니다. 대체 값이 없으면 비어 있지 않은 응답의 검증 실패에서는 계속 `ModelBehaviorError`가 발생하고, 비어 있는 구조화된 응답에는 기존 다음 턴 동작이 유지됩니다.
|
||||
|
||||
```python
|
||||
from pydantic import BaseModel
|
||||
|
||||
from agents import Agent, ModelBehaviorError, RunErrorHandlerInput, Runner
|
||||
|
||||
|
||||
class Recipe(BaseModel):
|
||||
ingredients: list[str]
|
||||
recovered_from_invalid_output: bool = False
|
||||
|
||||
|
||||
def on_invalid_final_output(data: RunErrorHandlerInput[None]) -> Recipe:
|
||||
assert isinstance(data.error, ModelBehaviorError)
|
||||
return Recipe(ingredients=[], recovered_from_invalid_output=True)
|
||||
|
||||
|
||||
agent = Agent(
|
||||
name="Recipe assistant",
|
||||
instructions="Return a structured recipe.",
|
||||
output_type=Recipe,
|
||||
)
|
||||
|
||||
result = Runner.run_sync(
|
||||
agent,
|
||||
"Plan tonight's dinner.",
|
||||
error_handlers={"invalid_final_output": on_invalid_final_output},
|
||||
)
|
||||
print(result.final_output)
|
||||
```
|
||||
|
||||
대체 출력을 대화 기록에 추가하지 않으려면 `include_in_history=False`로 설정하세요.
|
||||
|
||||
모델 거부 시 `ModelRefusalError`로 실행을 종료하는 대신 애플리케이션별 대체 값을 생성하려면 `"model_refusal"`을 사용하세요.
|
||||
|
||||
```python
|
||||
from pydantic import BaseModel
|
||||
|
||||
from agents import Agent, ModelRefusalError, RunErrorHandlerInput, Runner
|
||||
|
||||
|
||||
class Recipe(BaseModel):
|
||||
ingredients: list[str]
|
||||
refusal_reason: str | None = None
|
||||
|
||||
|
||||
def on_model_refusal(data: RunErrorHandlerInput[None]) -> Recipe:
|
||||
assert isinstance(data.error, ModelRefusalError)
|
||||
return Recipe(ingredients=[], refusal_reason=data.error.refusal)
|
||||
|
||||
|
||||
agent = Agent(
|
||||
name="Recipe assistant",
|
||||
instructions="Return a structured recipe.",
|
||||
output_type=Recipe,
|
||||
)
|
||||
|
||||
result = Runner.run_sync(
|
||||
agent,
|
||||
"Make me something unsafe.",
|
||||
error_handlers={"model_refusal": on_model_refusal},
|
||||
)
|
||||
print(result.final_output)
|
||||
```
|
||||
|
||||
## 내구성 실행 통합 및 휴먼인더루프 (HITL)
|
||||
|
||||
도구 승인 일시 중지/재개 패턴은 전용 [휴먼인더루프 (HITL) 가이드](human_in_the_loop.md)부터 참고하세요. 아래 통합은 실행이 긴 대기, 재시도 또는 프로세스 재시작에 걸쳐 지속될 수 있는 내구성 오케스트레이션을 위한 것입니다.
|
||||
|
||||
### Dapr
|
||||
|
||||
Agents SDK [Dapr](https://dapr.io) Diagrid 통합을 사용하면 휴먼인더루프 (HITL) 지원과 함께 장애에서 자동으로 복구되는 내구성 있는 장기 실행 에이전트를 실행할 수 있습니다. Dapr는 공급업체 중립적인 [CNCF](https://cncf.io) 워크플로 오케스트레이터입니다. Dapr 및 OpenAI 에이전트 사용은 [여기](https://docs.diagrid.io/getting-started/quickstarts/ai-agents/?agentframework=openai)에서 시작할 수 있습니다.
|
||||
|
||||
### Temporal
|
||||
|
||||
Agents SDK [Temporal](https://temporal.io/) 통합을 사용하면 휴먼인더루프 (HITL) 작업을 포함하여 내구성 있는 장기 실행 워크플로를 실행할 수 있습니다. 장기 실행 작업을 완료하기 위해 Temporal과 Agents SDK가 함께 작동하는 데모는 [이 동영상](https://www.youtube.com/watch?v=fFBZqzT4DD8)에서 확인할 수 있으며, [문서는 여기](https://github.com/temporalio/sdk-python/tree/main/temporalio/contrib/openai_agents)에서 볼 수 있습니다.
|
||||
|
||||
### Restate
|
||||
|
||||
Agents SDK [Restate](https://restate.dev/) 통합을 사용하면 사람의 승인, 핸드오프 및 세션 관리를 포함하는 경량의 내구성 있는 에이전트를 실행할 수 있습니다. 이 통합은 Restate의 단일 바이너리 런타임을 종속성으로 필요로 하며, 에이전트를 프로세스/컨테이너 또는 서버리스 함수로 실행할 수 있습니다. 자세한 내용은 [개요](https://www.restate.dev/blog/durable-orchestration-for-ai-agents-with-restate-and-openai-sdk) 또는 [문서](https://docs.restate.dev/ai)를 참고하세요.
|
||||
|
||||
### DBOS
|
||||
|
||||
Agents SDK [DBOS](https://dbos.dev/) 통합을 사용하면 장애와 재시작 후에도 진행 상태를 보존하는 신뢰할 수 있는 에이전트를 실행할 수 있습니다. 장기 실행 에이전트, 휴먼인더루프 (HITL) 워크플로 및 핸드오프를 지원합니다. 동기 및 비동기 메서드를 모두 지원합니다. 이 통합에는 SQLite 또는 Postgres 데이터베이스만 필요합니다. 자세한 내용은 통합 [리포지토리](https://github.com/dbos-inc/dbos-openai-agents) 및 [문서](https://docs.dbos.dev/integrations/openai-agents)를 참고하세요.
|
||||
|
||||
## 예외
|
||||
|
||||
SDK는 특정 상황에서 예외를 발생시킵니다. 전체 목록은 [`agents.exceptions`][]에 있습니다. 개요는 다음과 같습니다.
|
||||
|
||||
- [`AgentsException`][agents.exceptions.AgentsException]: SDK 내에서 발생하는 모든 예외의 기본 클래스입니다. 다른 모든 특정 예외가 파생되는 일반 유형입니다.
|
||||
- [`MaxTurnsExceeded`][agents.exceptions.MaxTurnsExceeded]: 에이전트 실행이 `Runner.run`, `Runner.run_sync` 또는 `Runner.run_streamed` 메서드에 전달된 `max_turns` 제한을 초과하면 이 예외가 발생합니다. 지정된 상호작용 턴 수 안에 에이전트가 작업을 완료하지 못했음을 나타냅니다. 제한을 비활성화하려면 `max_turns=None`으로 설정하세요.
|
||||
- [`ModelBehaviorError`][agents.exceptions.ModelBehaviorError]: 기반 모델(LLM)이 예상하지 못한 출력이나 유효하지 않은 출력을 생성하면 이 예외가 발생합니다. 다음과 같은 경우가 포함될 수 있습니다.
|
||||
- 잘못된 형식의 JSON: 특히 특정 `output_type`이 정의된 경우 모델이 도구 호출 또는 직접 출력에서 잘못된 형식의 JSON 구조를 제공하는 경우
|
||||
- 예상하지 못한 도구 관련 실패: 모델이 예상된 방식으로 도구를 사용하지 못하는 경우
|
||||
- [`ToolTimeoutError`][agents.exceptions.ToolTimeoutError]: 함수 도구 호출이 구성된 제한 시간을 초과하고 도구에서 `timeout_behavior="raise_exception"`을 사용하는 경우 이 예외가 발생합니다.
|
||||
- [`UserError`][agents.exceptions.UserError]: SDK를 사용하는 코드를 작성한 사람이 SDK를 사용하는 중 오류를 범하면 이 예외가 발생합니다. 일반적으로 잘못된 코드 구현, 유효하지 않은 구성 또는 SDK API의 잘못된 사용으로 인해 발생합니다.
|
||||
- [`InputGuardrailTripwireTriggered`][agents.exceptions.InputGuardrailTripwireTriggered], [`OutputGuardrailTripwireTriggered`][agents.exceptions.OutputGuardrailTripwireTriggered]: 각각 입력 가드레일 또는 출력 가드레일의 조건이 충족되면 이 예외가 발생합니다. 입력 가드레일은 처리 전에 수신 메시지를 검사하고, 출력 가드레일은 전달 전에 에이전트의 최종 응답을 검사합니다.
|
||||
@@ -0,0 +1,141 @@
|
||||
---
|
||||
search:
|
||||
exclude: true
|
||||
---
|
||||
# 샌드박스 클라이언트
|
||||
|
||||
이 페이지를 사용하여 샌드박스 작업을 어디에서 실행할지 선택하세요. 대부분의 경우 `SandboxAgent` 정의는 그대로 두고, [`SandboxRunConfig`][agents.run_config.SandboxRunConfig]에서 샌드박스 클라이언트와 클라이언트별 옵션만 변경합니다.
|
||||
|
||||
!!! warning "베타 기능"
|
||||
|
||||
샌드박스 에이전트는 베타입니다. API의 세부 사항, 기본값, 지원 기능은 정식 출시 전 변경될 수 있으며, 시간이 지남에 따라 더 고급 기능이 추가될 수 있습니다.
|
||||
|
||||
## 결정 가이드
|
||||
|
||||
<div class="sandbox-nowrap-first-column-table" markdown="1">
|
||||
|
||||
| 목표 | 시작 대상 | 이유 |
|
||||
| --- | --- | --- |
|
||||
| macOS 또는 Linux에서 가장 빠른 로컬 반복 개발 | `UnixLocalSandboxClient` | 추가 설치가 필요 없고, 로컬 파일 시스템 개발이 간단합니다. |
|
||||
| 기본 컨테이너 격리 | `DockerSandboxClient` | 특정 이미지로 Docker 내부에서 작업을 실행합니다. |
|
||||
| 호스티드 실행 또는 프로덕션 스타일 격리 | 호스티드 샌드박스 클라이언트 | 워크스페이스 경계를 제공자가 관리하는 환경으로 이동합니다. |
|
||||
|
||||
</div>
|
||||
|
||||
## 로컬 클라이언트
|
||||
|
||||
대부분의 사용자는 다음 두 샌드박스 클라이언트 중 하나로 시작하는 것이 좋습니다.
|
||||
|
||||
<div class="sandbox-nowrap-first-column-table" markdown="1">
|
||||
|
||||
| 클라이언트 | 설치 | 선택 시점 | 예시 |
|
||||
| --- | --- | --- | --- |
|
||||
| `UnixLocalSandboxClient` | 없음 | macOS 또는 Linux에서 가장 빠른 로컬 반복 개발이 필요할 때. 로컬 개발의 좋은 기본값입니다. | [Unix-local 시작 예제](https://github.com/openai/openai-agents-python/blob/main/examples/sandbox/unix_local_runner.py) |
|
||||
| `DockerSandboxClient` | `openai-agents[docker]` | 컨테이너 격리 또는 로컬 환경과의 동등성을 위한 특정 이미지가 필요할 때. | [Docker 시작 예제](https://github.com/openai/openai-agents-python/blob/main/examples/sandbox/docker/docker_runner.py) |
|
||||
|
||||
</div>
|
||||
|
||||
Unix-local은 로컬 파일 시스템을 대상으로 개발을 시작하는 가장 쉬운 방법입니다. 더 강력한 환경 격리 또는 프로덕션 스타일의 동등성이 필요할 때 Docker나 호스티드 제공자로 이동하세요.
|
||||
|
||||
Unix-local에서 Docker로 전환하려면 에이전트 정의는 그대로 두고 실행 구성만 변경하세요.
|
||||
|
||||
```python
|
||||
from docker import from_env as docker_from_env
|
||||
|
||||
from agents.run import RunConfig
|
||||
from agents.sandbox import SandboxRunConfig
|
||||
from agents.sandbox.sandboxes.docker import DockerSandboxClient, DockerSandboxClientOptions
|
||||
|
||||
run_config = RunConfig(
|
||||
sandbox=SandboxRunConfig(
|
||||
client=DockerSandboxClient(docker_from_env()),
|
||||
options=DockerSandboxClientOptions(image="python:3.14-slim"),
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
컨테이너 격리 또는 이미지 동등성이 필요할 때 사용하세요. [examples/sandbox/docker/docker_runner.py](https://github.com/openai/openai-agents-python/blob/main/examples/sandbox/docker/docker_runner.py)를 참고하세요.
|
||||
|
||||
## 마운트와 원격 스토리지
|
||||
|
||||
마운트 항목은 노출할 스토리지를 설명하고, 마운트 전략은 샌드박스 백엔드가 해당 스토리지를 연결하는 방식을 설명합니다. 기본 제공 마운트 항목과 일반 전략은 `agents.sandbox.entries`에서 가져오세요. 호스티드 제공자 전략은 `agents.extensions.sandbox` 또는 제공자별 확장 패키지에서 사용할 수 있습니다.
|
||||
|
||||
일반적인 마운트 옵션:
|
||||
|
||||
- `mount_path`: 샌드박스에서 스토리지가 나타나는 위치입니다. 상대 경로는 매니페스트 루트 아래에서 해석되고, 절대 경로는 그대로 사용됩니다.
|
||||
- `read_only`: 기본값은 `True`입니다. 샌드박스가 마운트된 스토리지에 다시 써야 할 때만 `False`로 설정하세요.
|
||||
- `mount_strategy`: 필수입니다. 마운트 항목과 샌드박스 백엔드 모두에 맞는 전략을 사용하세요.
|
||||
|
||||
마운트는 임시 워크스페이스 항목으로 취급됩니다. 스냅샷 및 지속성 플로우는 마운트된 원격 스토리지를 저장된 워크스페이스로 복사하는 대신, 마운트된 경로를 분리하거나 건너뜁니다.
|
||||
|
||||
일반 로컬/컨테이너 전략:
|
||||
|
||||
<div class="sandbox-nowrap-first-column-table" markdown="1">
|
||||
|
||||
| 전략 또는 패턴 | 사용 시점 | 참고 사항 |
|
||||
| --- | --- | --- |
|
||||
| `InContainerMountStrategy(pattern=RcloneMountPattern(...))` | 샌드박스 이미지에서 `rclone`을 실행할 수 있을 때. | S3, GCS, R2, Azure Blob, Box를 지원합니다. `RcloneMountPattern`은 `fuse` 모드 또는 `nfs` 모드로 실행할 수 있습니다. |
|
||||
| `InContainerMountStrategy(pattern=MountpointMountPattern(...))` | 이미지에 `mount-s3`가 있고 Mountpoint 스타일의 S3 또는 S3 호환 액세스를 원할 때. | `S3Mount` 및 `GCSMount`를 지원합니다. |
|
||||
| `InContainerMountStrategy(pattern=FuseMountPattern(...))` | 이미지에 `blobfuse2`와 FUSE 지원이 있을 때. | `AzureBlobMount`를 지원합니다. |
|
||||
| `InContainerMountStrategy(pattern=S3FilesMountPattern(...))` | 이미지에 `mount.s3files`가 있고 기존 S3 Files 마운트 대상에 접근할 수 있을 때. | `S3FilesMount`를 지원합니다. |
|
||||
| `DockerVolumeMountStrategy(driver=...)` | Docker가 컨테이너 시작 전에 볼륨 드라이버 기반 마운트를 연결해야 할 때. | Docker 전용입니다. S3, GCS, R2, Azure Blob, Box는 `rclone`을 지원하며, S3와 GCS는 `mountpoint`도 지원합니다. |
|
||||
|
||||
</div>
|
||||
|
||||
## 지원되는 호스티드 플랫폼
|
||||
|
||||
호스티드 환경이 필요한 경우 동일한 `SandboxAgent` 정의를 대개 그대로 사용할 수 있으며 [`SandboxRunConfig`][agents.run_config.SandboxRunConfig]에서 샌드박스 클라이언트만 변경하면 됩니다.
|
||||
|
||||
이 저장소 체크아웃 대신 배포된 SDK를 사용하는 경우, 일치하는 패키지 extra를 통해 샌드박스 클라이언트 종속성을 설치하세요.
|
||||
|
||||
제공자별 설정 참고 사항과 저장소에 포함된 확장 예제 링크는 [examples/sandbox/extensions/README.md](https://github.com/openai/openai-agents-python/blob/main/examples/sandbox/extensions/README.md)를 참고하세요.
|
||||
|
||||
<div class="sandbox-nowrap-first-column-table" markdown="1">
|
||||
|
||||
| 클라이언트 | 설치 | 예시 |
|
||||
| --- | --- | --- |
|
||||
| `BlaxelSandboxClient` | `openai-agents[blaxel]` | [Blaxel 실행 예제](https://github.com/openai/openai-agents-python/blob/main/examples/sandbox/extensions/blaxel_runner.py) |
|
||||
| `CloudflareSandboxClient` | `openai-agents[cloudflare]` | [Cloudflare 실행 예제](https://github.com/openai/openai-agents-python/blob/main/examples/sandbox/extensions/cloudflare_runner.py) |
|
||||
| `DaytonaSandboxClient` | `openai-agents[daytona]` | [Daytona 실행 예제](https://github.com/openai/openai-agents-python/blob/main/examples/sandbox/extensions/daytona/daytona_runner.py) |
|
||||
| `E2BSandboxClient` | `openai-agents[e2b]` | [E2B 실행 예제](https://github.com/openai/openai-agents-python/blob/main/examples/sandbox/extensions/e2b_runner.py) |
|
||||
| `ModalSandboxClient` | `openai-agents[modal]` | [Modal 실행 예제](https://github.com/openai/openai-agents-python/blob/main/examples/sandbox/extensions/modal_runner.py) |
|
||||
| `RunloopSandboxClient` | `openai-agents[runloop]` | [Runloop 실행 예제](https://github.com/openai/openai-agents-python/blob/main/examples/sandbox/extensions/runloop/runner.py) |
|
||||
| `VercelSandboxClient` | `openai-agents[vercel]` | [Vercel 실행 예제](https://github.com/openai/openai-agents-python/blob/main/examples/sandbox/extensions/vercel_runner.py) |
|
||||
|
||||
</div>
|
||||
|
||||
호스티드 샌드박스 클라이언트는 제공자별 마운트 전략을 노출합니다. 사용 중인 스토리지 제공자에 가장 적합한 백엔드와 마운트 전략을 선택하세요.
|
||||
|
||||
<div class="sandbox-nowrap-first-column-table" markdown="1">
|
||||
|
||||
| 백엔드 | 마운트 참고 사항 |
|
||||
| --- | --- |
|
||||
| Docker | `InContainerMountStrategy` 및 `DockerVolumeMountStrategy` 같은 로컬 전략으로 `S3Mount`, `GCSMount`, `R2Mount`, `AzureBlobMount`, `BoxMount`, `S3FilesMount`를 지원합니다. |
|
||||
| `ModalSandboxClient` | `S3Mount`, `R2Mount`, HMAC 인증 `GCSMount`에서 `ModalCloudBucketMountStrategy`로 Modal 클라우드 버킷 마운트를 지원합니다. 인라인 자격 증명 또는 이름이 지정된 Modal Secret을 사용할 수 있습니다. |
|
||||
| `CloudflareSandboxClient` | `S3Mount`, `R2Mount`, HMAC 인증 `GCSMount`에서 `CloudflareBucketMountStrategy`로 Cloudflare 버킷 마운트를 지원합니다. |
|
||||
| `BlaxelSandboxClient` | `S3Mount`, `R2Mount`, `GCSMount`에서 `BlaxelCloudBucketMountStrategy`로 클라우드 버킷 마운트를 지원합니다. 또한 `agents.extensions.sandbox.blaxel`의 `BlaxelDriveMount` 및 `BlaxelDriveMountStrategy`를 통해 영구 Blaxel Drives도 지원합니다. |
|
||||
| `DaytonaSandboxClient` | `DaytonaCloudBucketMountStrategy`로 rclone 기반 클라우드 스토리지 마운트를 지원합니다. `S3Mount`, `GCSMount`, `R2Mount`, `AzureBlobMount`, `BoxMount`와 함께 사용하세요. |
|
||||
| `E2BSandboxClient` | `E2BCloudBucketMountStrategy`로 rclone 기반 클라우드 스토리지 마운트를 지원합니다. `S3Mount`, `GCSMount`, `R2Mount`, `AzureBlobMount`, `BoxMount`와 함께 사용하세요. |
|
||||
| `RunloopSandboxClient` | `RunloopCloudBucketMountStrategy`로 rclone 기반 클라우드 스토리지 마운트를 지원합니다. `S3Mount`, `GCSMount`, `R2Mount`, `AzureBlobMount`, `BoxMount`와 함께 사용하세요. |
|
||||
| `VercelSandboxClient` | 현재 노출된 호스티드 전용 마운트 전략은 없습니다. 대신 매니페스트 파일, 리포지토리 또는 기타 워크스페이스 입력을 사용하세요. |
|
||||
|
||||
</div>
|
||||
|
||||
아래 표는 각 백엔드가 직접 마운트할 수 있는 원격 스토리지 항목을 요약합니다.
|
||||
|
||||
<div class="sandbox-nowrap-first-column-table" markdown="1">
|
||||
|
||||
| 백엔드 | AWS S3 | Cloudflare R2 | GCS | Azure Blob Storage | Box | S3 Files |
|
||||
| --- | --- | --- | --- | --- | --- | --- |
|
||||
| Docker | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| `ModalSandboxClient` | ✓ | ✓ | ✓ | - | - | - |
|
||||
| `CloudflareSandboxClient` | ✓ | ✓ | ✓ | - | - | - |
|
||||
| `BlaxelSandboxClient` | ✓ | ✓ | ✓ | - | - | - |
|
||||
| `DaytonaSandboxClient` | ✓ | ✓ | ✓ | ✓ | ✓ | - |
|
||||
| `E2BSandboxClient` | ✓ | ✓ | ✓ | ✓ | ✓ | - |
|
||||
| `RunloopSandboxClient` | ✓ | ✓ | ✓ | ✓ | ✓ | - |
|
||||
| `VercelSandboxClient` | - | - | - | - | - | - |
|
||||
|
||||
</div>
|
||||
|
||||
실행 가능한 더 많은 예제는 로컬, 코딩, 메모리, 핸드오프 및 에이전트 구성 패턴에 대해 [examples/sandbox/](https://github.com/openai/openai-agents-python/tree/main/examples/sandbox)를, 호스티드 샌드박스 클라이언트에 대해 [examples/sandbox/extensions/](https://github.com/openai/openai-agents-python/tree/main/examples/sandbox/extensions)를 둘러보세요.
|
||||
@@ -0,0 +1,861 @@
|
||||
---
|
||||
search:
|
||||
exclude: true
|
||||
---
|
||||
# 개념
|
||||
|
||||
!!! warning "베타 기능"
|
||||
|
||||
샌드박스 에이전트는 베타 버전입니다. 정식 출시 전까지 API 세부 정보, 기본값, 지원 기능이 변경될 수 있으며, 향후 더 고급 기능이 추가될 수 있습니다.
|
||||
|
||||
최신 에이전트는 파일 시스템의 실제 파일을 다룰 수 있을 때 가장 효과적으로 작동합니다. **샌드박스 에이전트**는 특화된 도구와 셸 명령을 사용하여 대규모 문서 집합을 검색하고 조작하며, 파일을 편집하고, 아티팩트를 생성하고, 명령을 실행할 수 있습니다. 샌드박스는 에이전트가 사용자를 대신해 작업할 수 있는 영구 워크스페이스를 모델에 제공합니다. Agents SDK의 샌드박스 에이전트를 사용하면 에이전트를 샌드박스 환경과 연결하여 손쉽게 실행할 수 있으며, 적절한 파일을 파일 시스템에 배치하고 샌드박스를 오케스트레이션하여 대규모 작업을 쉽게 시작, 중지, 재개할 수 있습니다.
|
||||
|
||||
에이전트에 필요한 데이터를 중심으로 워크스페이스를 정의합니다. 워크스페이스는 GitHub 저장소, 로컬 파일과 디렉터리, 합성 작업 파일, S3 또는 Azure Blob Storage 같은 원격 파일 시스템, 그리고 사용자가 제공하는 기타 샌드박스 입력으로 시작할 수 있습니다.
|
||||
|
||||
<div class="sandbox-harness-image" markdown="1">
|
||||
|
||||

|
||||
|
||||
</div>
|
||||
|
||||
`SandboxAgent`도 여전히 `Agent`입니다. `instructions`, `prompt`, `tools`, `handoffs`, `mcp_servers`, `model_settings`, `output_type`, 가드레일, 훅과 같은 일반적인 에이전트 인터페이스를 그대로 유지하며, 일반적인 `Runner` API를 통해 실행됩니다. 달라지는 부분은 실행 경계입니다.
|
||||
|
||||
- `SandboxAgent`는 에이전트 자체를 정의합니다. 일반적인 에이전트 구성에 더해 `default_manifest`, `base_instructions`, `run_as` 같은 샌드박스 전용 기본값과 파일 시스템 도구, 셸 액세스, 스킬, 메모리 또는 압축 같은 기능을 포함합니다.
|
||||
- `Manifest`는 파일, 저장소, 마운트, 환경을 포함하여 새 샌드박스 워크스페이스에 필요한 초기 콘텐츠와 레이아웃을 선언합니다.
|
||||
- 샌드박스 세션은 명령이 실행되고 파일이 변경되는 실제 격리 환경입니다.
|
||||
- [`SandboxRunConfig`][agents.run_config.SandboxRunConfig]는 샌드박스 세션을 직접 주입하거나, 직렬화된 샌드박스 세션 상태로 다시 연결하거나, 샌드박스 클라이언트를 통해 새 샌드박스 세션을 생성하는 등 실행에서 샌드박스 세션을 가져오는 방식을 결정합니다.
|
||||
- 저장된 샌드박스 상태와 스냅샷을 사용하면 이후 실행에서 이전 작업에 다시 연결하거나, 저장된 콘텐츠를 바탕으로 새 샌드박스 세션을 초기화할 수 있습니다.
|
||||
|
||||
`Manifest`는 새 세션의 워크스페이스 계약이며, 모든 실제 샌드박스에 관한 완전한 정보의 원천은 아닙니다. 실행의 유효 워크스페이스는 재사용된 샌드박스 세션, 직렬화된 샌드박스 세션 상태 또는 실행 시 선택한 스냅샷에서 가져올 수도 있습니다.
|
||||
|
||||
이 페이지에서 "샌드박스 세션"은 샌드박스 클라이언트가 관리하는 실제 실행 환경을 의미합니다. 이는 [세션](../sessions/index.md)에서 설명하는 SDK의 대화형 [`Session`][agents.memory.session.Session] 인터페이스와 다릅니다.
|
||||
|
||||
외부 런타임은 계속해서 승인, 트레이싱, 핸드오프, 재개 관련 기록을 담당합니다. 샌드박스 세션은 명령, 파일 변경, 환경 격리를 담당합니다. 이러한 역할 분리는 모델의 핵심 요소입니다.
|
||||
|
||||
### 구성 요소 간 연계
|
||||
|
||||
샌드박스 실행은 에이전트 정의와 실행별 샌드박스 구성을 결합합니다. 러너는 에이전트를 준비하고 실제 샌드박스 세션에 바인딩하며, 이후 실행을 위해 상태를 저장할 수 있습니다.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
agent["SandboxAgent<br/><small>full Agent + sandbox defaults</small>"]
|
||||
config["SandboxRunConfig<br/><small>client / session / resume inputs</small>"]
|
||||
runner["Runner<br/><small>prepare instructions<br/>bind capability tools</small>"]
|
||||
sandbox["sandbox session<br/><small>workspace where commands run<br/>and files change</small>"]
|
||||
saved["saved state / snapshot<br/><small>for resume or fresh-start later</small>"]
|
||||
|
||||
agent --> runner
|
||||
config --> runner
|
||||
runner --> sandbox
|
||||
sandbox --> saved
|
||||
```
|
||||
|
||||
샌드박스 전용 기본값은 `SandboxAgent`에 둡니다. 실행별 샌드박스 세션 선택 사항은 `SandboxRunConfig`에 둡니다.
|
||||
|
||||
수명 주기는 다음 세 단계로 생각할 수 있습니다.
|
||||
|
||||
1. `SandboxAgent`, `Manifest`, 기능을 사용하여 에이전트와 새 워크스페이스 계약을 정의합니다.
|
||||
2. 샌드박스 세션을 주입, 재개 또는 생성하는 `SandboxRunConfig`를 `Runner`에 제공하여 실행합니다.
|
||||
3. 러너가 관리하는 `RunState`, 명시적인 샌드박스 `session_state` 또는 저장된 워크스페이스 스냅샷에서 나중에 작업을 이어갑니다.
|
||||
|
||||
셸 액세스가 가끔 사용하는 도구 중 하나일 뿐이라면 [도구 가이드](../tools.md)의 호스티드 셸부터 사용하세요. 워크스페이스 격리, 샌드박스 클라이언트 선택 또는 샌드박스 세션 재개 동작이 설계의 일부라면 샌드박스 에이전트를 사용하세요.
|
||||
|
||||
## 사용 시점
|
||||
|
||||
샌드박스 에이전트는 다음과 같은 워크스페이스 중심 워크플로에 적합합니다.
|
||||
|
||||
- 코딩과 디버깅(예: GitHub 저장소의 이슈 보고서에 대한 자동 수정 작업을 오케스트레이션하고 특정 테스트 실행)
|
||||
- 문서 처리와 편집(예: 사용자의 재무 문서에서 정보를 추출하고 작성된 세금 양식 초안 생성)
|
||||
- 파일에 기반한 검토 또는 분석(예: 답변하기 전에 온보딩 패킷, 생성된 보고서 또는 아티팩트 번들 확인)
|
||||
- 격리된 멀티 에이전트 패턴(예: 각 검토자나 코딩 하위 에이전트에 자체 워크스페이스 제공)
|
||||
- 여러 단계로 구성된 워크스페이스 작업(예: 한 실행에서 버그를 수정하고 나중에 회귀 테스트를 추가하거나, 스냅샷 또는 샌드박스 세션 상태에서 재개)
|
||||
|
||||
파일이나 지속적으로 변경되는 파일 시스템에 액세스할 필요가 없다면 `Agent`를 계속 사용하세요. 셸 액세스가 가끔 필요한 기능일 뿐이라면 호스티드 셸을 추가하고, 워크스페이스 경계 자체가 기능의 일부라면 샌드박스 에이전트를 사용하세요.
|
||||
|
||||
## 샌드박스 클라이언트 선택
|
||||
|
||||
로컬 개발에는 `UnixLocalSandboxClient`부터 사용하세요. 컨테이너 격리 또는 이미지 일관성이 필요하면 `DockerSandboxClient`로 전환하세요. 공급자가 관리하는 실행이 필요하면 호스티드 공급자로 전환하세요.
|
||||
|
||||
대부분의 경우 [`SandboxRunConfig`][agents.run_config.SandboxRunConfig]에서 샌드박스 클라이언트와 해당 옵션만 변경하면 `SandboxAgent` 정의는 그대로 유지됩니다. 로컬, Docker, 호스티드, 원격 마운트 옵션은 [샌드박스 클라이언트](clients.md)를 참고하세요.
|
||||
|
||||
## 핵심 구성 요소
|
||||
|
||||
<div class="sandbox-nowrap-first-column-table" markdown="1">
|
||||
|
||||
| 계층 | 주요 SDK 구성 요소 | 답하는 질문 |
|
||||
| --- | --- | --- |
|
||||
| 에이전트 정의 | `SandboxAgent`, `Manifest`, 기능 | 어떤 에이전트를 실행하며, 어떤 새 세션 워크스페이스 계약에서 시작해야 합니까? |
|
||||
| 샌드박스 실행 | `SandboxRunConfig`, 샌드박스 클라이언트, 실제 샌드박스 세션 | 이 실행은 실제 샌드박스 세션을 어떻게 가져오며, 작업은 어디에서 실행됩니까? |
|
||||
| 저장된 샌드박스 상태 | `RunState` 샌드박스 페이로드, `session_state`, 스냅샷 | 이 워크플로는 이전 샌드박스 작업에 어떻게 다시 연결하거나 저장된 콘텐츠에서 새 샌드박스 세션을 초기화합니까? |
|
||||
|
||||
</div>
|
||||
|
||||
주요 SDK 구성 요소는 다음과 같이 각 계층에 대응합니다.
|
||||
|
||||
<div class="sandbox-nowrap-first-column-table" markdown="1">
|
||||
|
||||
| 구성 요소 | 담당 범위 | 확인할 질문 |
|
||||
| --- | --- | --- |
|
||||
| [`SandboxAgent`][agents.sandbox.sandbox_agent.SandboxAgent] | 에이전트 정의 | 이 에이전트는 무엇을 해야 하며, 어떤 기본값을 함께 유지해야 합니까? |
|
||||
| [`Manifest`][agents.sandbox.manifest.Manifest] | 새 세션의 워크스페이스 파일과 폴더 | 실행이 시작될 때 파일 시스템에 어떤 파일과 폴더가 있어야 합니까? |
|
||||
| [`Capability`][agents.sandbox.capabilities.capability.Capability] | 샌드박스 네이티브 동작 | 이 에이전트에 어떤 도구, 지침 조각 또는 런타임 동작을 연결해야 합니까? |
|
||||
| [`SandboxRunConfig`][agents.run_config.SandboxRunConfig] | 실행별 샌드박스 클라이언트와 샌드박스 세션 소스 | 이 실행은 샌드박스 세션을 주입, 재개 또는 생성해야 합니까? |
|
||||
| [`RunState`][agents.run_state.RunState] | 러너가 관리하는 저장된 샌드박스 상태 | 러너가 관리하던 이전 워크플로를 재개하면서 해당 샌드박스 상태를 자동으로 이어가고 있습니까? |
|
||||
| [`SandboxRunConfig.session_state`][agents.run_config.SandboxRunConfig.session_state] | 명시적으로 직렬화된 샌드박스 세션 상태 | `RunState` 외부에서 이미 직렬화한 샌드박스 상태로부터 재개하려고 합니까? |
|
||||
| [`SandboxRunConfig.snapshot`][agents.run_config.SandboxRunConfig.snapshot] | 새 샌드박스 세션을 위한 저장된 워크스페이스 콘텐츠 | 새 샌드박스 세션을 저장된 파일과 아티팩트에서 시작해야 합니까? |
|
||||
|
||||
</div>
|
||||
|
||||
실용적인 설계 순서는 다음과 같습니다.
|
||||
|
||||
1. `Manifest`로 새 세션의 워크스페이스 계약을 정의합니다.
|
||||
2. `SandboxAgent`로 에이전트를 정의합니다.
|
||||
3. 기본 제공 또는 사용자 정의 기능을 추가합니다.
|
||||
4. `RunConfig(sandbox=SandboxRunConfig(...))`에서 각 실행이 샌드박스 세션을 가져오는 방식을 결정합니다.
|
||||
|
||||
## 샌드박스 실행 준비 과정
|
||||
|
||||
실행 시 러너는 해당 정의를 구체적인 샌드박스 기반 실행으로 변환합니다.
|
||||
|
||||
1. `SandboxRunConfig`에서 샌드박스 세션을 확인합니다. `session=...`을 전달하면 해당 실제 샌드박스 세션을 재사용합니다. 그렇지 않으면 `client=...`를 사용하여 세션을 생성하거나 재개합니다.
|
||||
2. 실행에 사용할 유효 워크스페이스 입력을 결정합니다. 실행에서 샌드박스 세션을 주입하거나 재개하면 기존 샌드박스 상태가 우선합니다. 그렇지 않으면 러너는 일회성 매니페스트 재정의 또는 `agent.default_manifest`에서 시작합니다. 따라서 `Manifest`만으로 모든 실행의 최종 실제 워크스페이스가 정의되지는 않습니다.
|
||||
3. 기능이 결과 매니페스트를 처리하도록 합니다. 이를 통해 최종 에이전트를 준비하기 전에 기능이 파일, 마운트 또는 기타 워크스페이스 범위 동작을 추가할 수 있습니다.
|
||||
4. 고정된 순서로 최종 지침을 구성합니다. 먼저 SDK의 기본 샌드박스 프롬프트 또는 명시적으로 재정의한 경우 `base_instructions`를 사용하고, 이어서 `instructions`, 기능 지침 조각, 원격 마운트 정책 텍스트, 렌더링된 파일 시스템 트리를 추가합니다.
|
||||
5. 기능 도구를 실제 샌드박스 세션에 바인딩하고 일반적인 `Runner` API를 통해 준비된 에이전트를 실행합니다.
|
||||
|
||||
샌드박스 사용은 턴의 의미를 바꾸지 않습니다. 턴은 여전히 단일 셸 명령이나 샌드박스 작업이 아니라 모델 단계입니다. 샌드박스 측 작업과 턴 사이에는 고정된 1:1 대응 관계가 없습니다. 일부 작업은 샌드박스 실행 계층 내부에서 처리될 수 있지만, 다른 작업은 추가 모델 단계가 필요한 도구 결과, 승인 또는 기타 상태를 반환합니다. 실용적인 원칙으로는 샌드박스 작업이 발생한 후 에이전트 런타임에서 추가 모델 응답이 필요할 때만 턴이 하나 더 사용됩니다.
|
||||
|
||||
이러한 준비 단계 때문에 `default_manifest`, `instructions`, `base_instructions`, `capabilities`, `run_as`가 `SandboxAgent`를 설계할 때 고려해야 할 주요 샌드박스 전용 옵션입니다.
|
||||
|
||||
## `SandboxAgent` 옵션
|
||||
|
||||
일반적인 `Agent` 필드에 추가되는 샌드박스 전용 옵션은 다음과 같습니다.
|
||||
|
||||
<div class="sandbox-nowrap-first-column-table" markdown="1">
|
||||
|
||||
| 옵션 | 적합한 용도 |
|
||||
| --- | --- |
|
||||
| `default_manifest` | 러너가 생성하는 새 샌드박스 세션의 기본 워크스페이스 |
|
||||
| `instructions` | SDK 샌드박스 프롬프트 뒤에 추가되는 역할, 워크플로, 성공 기준 |
|
||||
| `base_instructions` | SDK 샌드박스 프롬프트를 대체하는 고급 우회 수단 |
|
||||
| `capabilities` | 이 에이전트와 함께 유지되어야 하는 샌드박스 네이티브 도구와 동작 |
|
||||
| `run_as` | 셸 명령, 파일 읽기, 패치 등 모델에 노출되는 샌드박스 도구의 사용자 ID |
|
||||
|
||||
</div>
|
||||
|
||||
샌드박스 클라이언트 선택, 샌드박스 세션 재사용, 매니페스트 재정의, 스냅샷 선택은 에이전트가 아니라 [`SandboxRunConfig`][agents.run_config.SandboxRunConfig]에 속합니다.
|
||||
|
||||
### `default_manifest`
|
||||
|
||||
`default_manifest`는 러너가 이 에이전트의 새 샌드박스 세션을 생성할 때 사용하는 기본 [`Manifest`][agents.sandbox.manifest.Manifest]입니다. 에이전트가 일반적으로 시작할 때 갖추어야 할 파일, 저장소, 보조 자료, 출력 디렉터리, 마운트에 사용합니다.
|
||||
|
||||
이는 기본값일 뿐입니다. 실행에서 `SandboxRunConfig(manifest=...)`로 재정의할 수 있으며, 재사용하거나 재개한 샌드박스 세션은 기존 워크스페이스 상태를 유지합니다.
|
||||
|
||||
### `instructions`와 `base_instructions`
|
||||
|
||||
여러 프롬프트에서도 유지되어야 하는 짧은 규칙에는 `instructions`를 사용하세요. `SandboxAgent`에서는 이러한 지침이 SDK의 샌드박스 기본 프롬프트 뒤에 추가되므로, 기본 제공 샌드박스 지침을 유지하면서 자체 역할, 워크플로, 성공 기준을 추가할 수 있습니다.
|
||||
|
||||
SDK 샌드박스 기본 프롬프트를 대체하려는 경우에만 `base_instructions`를 사용하세요. 대부분의 에이전트에서는 설정하지 않는 것이 좋습니다.
|
||||
|
||||
<div class="sandbox-nowrap-first-column-table" markdown="1">
|
||||
|
||||
| 배치 위치 | 용도 | 예시 |
|
||||
| --- | --- | --- |
|
||||
| `instructions` | 에이전트의 일관된 역할, 워크플로 규칙, 성공 기준 | "온보딩 문서를 검사한 다음 핸드오프하세요.", "최종 파일을 `output/`에 작성하세요." |
|
||||
| `base_instructions` | SDK 샌드박스 기본 프롬프트의 전체 대체 | 사용자 정의 저수준 샌드박스 래퍼 프롬프트 |
|
||||
| 사용자 프롬프트 | 이 실행을 위한 일회성 요청 | "이 워크스페이스를 요약하세요." |
|
||||
| 매니페스트의 워크스페이스 파일 | 더 긴 작업 명세, 저장소 로컬 지침 또는 범위가 한정된 참조 자료 | `repo/task.md`, 문서 번들, 샘플 패킷 |
|
||||
|
||||
</div>
|
||||
|
||||
`instructions`를 효과적으로 사용하는 예시는 다음과 같습니다.
|
||||
|
||||
- [examples/sandbox/unix_local_pty.py](https://github.com/openai/openai-agents-python/blob/main/examples/sandbox/unix_local_pty.py)는 PTY 상태가 중요한 경우 에이전트가 하나의 대화형 프로세스에서 작업하도록 합니다.
|
||||
- [examples/sandbox/handoffs.py](https://github.com/openai/openai-agents-python/blob/main/examples/sandbox/handoffs.py)는 샌드박스 검토자가 검사 후 사용자에게 직접 답변하지 못하도록 합니다.
|
||||
- [examples/sandbox/tax_prep.py](https://github.com/openai/openai-agents-python/blob/main/examples/sandbox/tax_prep.py)는 최종 작성 파일이 실제로 `output/`에 저장되도록 요구합니다.
|
||||
- [examples/sandbox/docs/coding_task.py](https://github.com/openai/openai-agents-python/blob/main/examples/sandbox/docs/coding_task.py)는 정확한 검증 명령을 지정하고 워크스페이스 루트 기준 패치 경로를 명확히 설명합니다.
|
||||
|
||||
사용자의 일회성 작업을 `instructions`에 복사하거나, 매니페스트에 속하는 긴 참조 자료를 포함하거나, 기본 제공 기능이 이미 주입하는 도구 문서를 반복하거나, 모델이 실행 시 필요로 하지 않는 로컬 설치 참고 사항을 섞지 마세요.
|
||||
|
||||
`instructions`를 생략해도 SDK는 기본 샌드박스 프롬프트를 포함합니다. 저수준 래퍼에는 이것으로 충분하지만, 대부분의 사용자 대상 에이전트는 명시적인 `instructions`도 제공해야 합니다.
|
||||
|
||||
### `capabilities`
|
||||
|
||||
기능은 샌드박스 네이티브 동작을 `SandboxAgent`에 연결합니다. 실행이 시작되기 전에 워크스페이스를 구성하고, 샌드박스 전용 지침을 추가하며, 실제 샌드박스 세션에 바인딩되는 도구를 노출하고, 해당 에이전트의 모델 동작이나 입력 처리를 조정할 수 있습니다.
|
||||
|
||||
기본 제공 기능은 다음과 같습니다.
|
||||
|
||||
<div class="sandbox-nowrap-first-column-table" markdown="1">
|
||||
|
||||
| 기능 | 추가 시점 | 참고 |
|
||||
| --- | --- | --- |
|
||||
| `Shell` | 에이전트에 셸 액세스가 필요할 때 | `exec_command`를 추가하며, 샌드박스 클라이언트가 PTY 상호작용을 지원하면 `write_stdin`도 추가합니다. |
|
||||
| `Filesystem` | 에이전트가 파일을 편집하거나 로컬 이미지를 검사해야 할 때 | `apply_patch`와 `view_image`를 추가하며, 패치 경로는 워크스페이스 루트 기준입니다. |
|
||||
| `Skills` | 샌드박스에서 스킬 검색과 구체화를 사용하려고 할 때 | `.agents` 또는 `.agents/skills`를 수동으로 마운트하는 대신 이를 사용하는 것이 좋습니다. `Skills`가 스킬의 인덱스를 생성하고 샌드박스에 구체화합니다. |
|
||||
| `Memory` | 후속 실행에서 메모리 아티팩트를 읽거나 생성해야 할 때 | `Shell`이 필요하며, 실시간 업데이트에는 `Filesystem`도 필요합니다. |
|
||||
| `Compaction` | 장기 실행 흐름에서 압축 항목 이후 컨텍스트 축소가 필요할 때 | 모델 샘플링과 입력 처리를 조정합니다. |
|
||||
|
||||
</div>
|
||||
|
||||
기본적으로 `SandboxAgent.capabilities`는 `Filesystem()`, `Shell()`, `Compaction()`을 포함하는 `Capabilities.default()`를 사용합니다. `capabilities=[...]`를 전달하면 해당 목록이 기본값을 대체하므로, 계속 사용하려는 기본 기능을 모두 포함하세요.
|
||||
|
||||
스킬은 원하는 구체화 방식에 따라 소스를 선택하세요.
|
||||
|
||||
- `Skills(lazy_from=LocalDirLazySkillSource(...))`는 모델이 먼저 인덱스를 검색하고 필요한 항목만 로드할 수 있으므로 규모가 큰 로컬 스킬 디렉터리에 적합한 기본 선택입니다.
|
||||
- `LocalDirLazySkillSource(source=LocalDir(src=...))`는 SDK 프로세스가 실행 중인 파일 시스템에서 읽습니다. 샌드박스 이미지나 워크스페이스 내부에만 존재하는 경로가 아니라 원래 호스트 측 스킬 디렉터리를 전달하세요.
|
||||
- `Skills(from_=LocalDir(src=...))`는 사전에 스테이징하려는 소규모 로컬 번들에 더 적합합니다.
|
||||
- `Skills(from_=GitRepo(repo=..., ref=...))`는 스킬 자체를 저장소에서 가져와야 할 때 적합합니다.
|
||||
|
||||
`LocalDir.src`는 SDK 호스트의 소스 경로입니다. `skills_path`는 `load_skill`을 호출할 때 스킬이 스테이징되는 샌드박스 워크스페이스 내부의 상대 대상 경로입니다.
|
||||
|
||||
스킬이 이미 `.agents/skills/<name>/SKILL.md` 같은 경로의 디스크에 있다면 `LocalDir(...)`가 해당 소스 루트를 가리키도록 하고, 계속 `Skills(...)`를 사용하여 노출하세요. 다른 샌드박스 내부 레이아웃에 의존하는 기존 워크스페이스 계약이 없다면 기본값인 `skills_path=".agents"`를 유지하세요.
|
||||
|
||||
적합한 기본 제공 기능이 있으면 이를 우선 사용하세요. 기본 제공 기능이 다루지 않는 샌드박스 전용 도구나 지침 인터페이스가 필요한 경우에만 사용자 정의 기능을 작성하세요.
|
||||
|
||||
## 개념
|
||||
|
||||
### 매니페스트
|
||||
|
||||
[`Manifest`][agents.sandbox.manifest.Manifest]는 새 샌드박스 세션의 워크스페이스를 설명합니다. 워크스페이스 `root`를 설정하고, 파일과 디렉터리를 선언하며, 로컬 파일을 복사하고, Git 저장소를 복제하고, 원격 스토리지 마운트를 연결하고, 환경 변수를 설정하고, 사용자나 그룹을 정의하며, 워크스페이스 외부의 특정 절대 경로에 대한 액세스를 허용할 수 있습니다.
|
||||
|
||||
매니페스트 항목의 경로는 워크스페이스 기준 상대 경로입니다. 절대 경로를 사용하거나 `..`로 워크스페이스를 벗어날 수 없으므로, 로컬, Docker, 호스티드 클라이언트 간에 워크스페이스 계약의 이식성을 유지할 수 있습니다.
|
||||
|
||||
작업을 시작하기 전에 에이전트에 필요한 자료에는 매니페스트 항목을 사용하세요.
|
||||
|
||||
<div class="sandbox-nowrap-first-column-table" markdown="1">
|
||||
|
||||
| 매니페스트 항목 | 용도 |
|
||||
| --- | --- |
|
||||
| `File`, `Dir` | 소규모 합성 입력, 보조 파일 또는 출력 디렉터리 |
|
||||
| `LocalFile`, `LocalDir` | 샌드박스에 구체화해야 하는 호스트 파일 또는 디렉터리 |
|
||||
| `GitRepo` | 워크스페이스로 가져와야 하는 저장소 |
|
||||
| `S3Mount`, `GCSMount`, `R2Mount`, `AzureBlobMount`, `BoxMount`, `S3FilesMount` 같은 마운트 | 샌드박스 내부에 표시되어야 하는 외부 스토리지 |
|
||||
|
||||
</div>
|
||||
|
||||
`Dir`은 합성 자식 항목으로 샌드박스 워크스페이스 내부에 디렉터리를 생성하거나 출력 위치를 만듭니다. 호스트 파일 시스템에서는 읽지 않습니다. 기존 호스트 디렉터리를 샌드박스 워크스페이스로 복사해야 할 때는 `LocalDir`을 사용하세요.
|
||||
|
||||
기본적으로 `LocalFile.src`와 `LocalDir.src`는 SDK 프로세스 작업 디렉터리를 기준으로 확인됩니다. `extra_path_grants`에 포함되지 않는 한 소스는 해당 기본 디렉터리 아래에 있어야 합니다. 이를 통해 로컬 소스 구체화가 나머지 샌드박스 매니페스트와 동일한 호스트 경로 신뢰 경계 안에 유지됩니다.
|
||||
|
||||
마운트 항목은 노출할 스토리지를 설명하고, 마운트 전략은 샌드박스 백엔드가 해당 스토리지를 연결하는 방식을 설명합니다. 마운트 옵션과 공급자 지원은 [샌드박스 클라이언트](clients.md#mounts-and-remote-storage)를 참고하세요.
|
||||
|
||||
좋은 매니페스트 설계는 일반적으로 워크스페이스 계약의 범위를 좁게 유지하고, 긴 작업 절차는 `repo/task.md` 같은 워크스페이스 파일에 배치하며, 지침에서는 `repo/task.md` 또는 `output/report.md`처럼 워크스페이스 기준 상대 경로를 사용하는 것입니다. 에이전트가 `Filesystem` 기능의 `apply_patch` 도구로 파일을 편집하는 경우, 패치 경로는 셸 `workdir`이 아니라 샌드박스 워크스페이스 루트를 기준으로 한다는 점을 기억하세요.
|
||||
|
||||
에이전트에 워크스페이스 외부의 구체적인 절대 경로가 필요하거나, 매니페스트가 SDK 프로세스 작업 디렉터리 외부의 신뢰할 수 있는 로컬 소스를 복사해야 하는 경우에만 `extra_path_grants`를 사용하세요. 예를 들어 임시 도구 출력을 위한 `/tmp`, 읽기 전용 런타임을 위한 `/opt/toolchain`, 샌드박스에 구체화해야 하는 생성된 스킬 디렉터리 등이 있습니다. 백엔드에서 파일 시스템 정책을 적용할 수 있는 경우 권한 부여는 로컬 소스 구체화, SDK 파일 API, 셸 실행에 적용됩니다.
|
||||
|
||||
```python
|
||||
from agents.sandbox import Manifest, SandboxPathGrant
|
||||
|
||||
manifest = Manifest(
|
||||
extra_path_grants=(
|
||||
SandboxPathGrant(path="/tmp"),
|
||||
SandboxPathGrant(path="/opt/toolchain", read_only=True),
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
`extra_path_grants`가 포함된 매니페스트는 신뢰할 수 있는 구성으로 취급하세요. 애플리케이션이 해당 호스트 경로를 이미 승인한 경우가 아니라면 모델 출력이나 기타 신뢰할 수 없는 페이로드에서 권한 부여를 로드하지 마세요.
|
||||
|
||||
스냅샷과 `persist_workspace()`에는 여전히 워크스페이스 루트만 포함됩니다. 추가로 권한이 부여된 경로는 런타임 액세스이며, 영구 워크스페이스 상태가 아닙니다.
|
||||
|
||||
### 권한
|
||||
|
||||
`Permissions`는 매니페스트 항목의 파일 시스템 권한을 제어합니다. 이는 샌드박스에서 구체화하는 파일에 관한 것이며, 모델 권한, 승인 정책 또는 API 자격 증명과는 관련이 없습니다.
|
||||
|
||||
기본적으로 매니페스트 항목은 소유자가 읽고 쓰고 실행할 수 있으며, 그룹과 기타 사용자는 읽고 실행할 수 있습니다. 스테이징된 파일을 비공개, 읽기 전용 또는 실행 가능 상태로 지정해야 한다면 이를 재정의하세요.
|
||||
|
||||
```python
|
||||
from agents.sandbox import FileMode, Permissions
|
||||
from agents.sandbox.entries import File
|
||||
|
||||
private_notes = File(
|
||||
content=b"internal notes",
|
||||
permissions=Permissions(
|
||||
owner=FileMode.READ | FileMode.WRITE,
|
||||
group=FileMode.NONE,
|
||||
other=FileMode.NONE,
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
`Permissions`는 항목이 디렉터리인지 여부와 함께 소유자, 그룹, 기타 사용자의 비트를 각각 저장합니다. 직접 구성하거나, `Permissions.from_str(...)`로 모드 문자열에서 파싱하거나, `Permissions.from_mode(...)`로 OS 모드에서 파생할 수 있습니다.
|
||||
|
||||
사용자는 샌드박스에서 작업을 실행할 수 있는 ID입니다. 샌드박스에 특정 ID가 존재해야 한다면 매니페스트에 `User`를 추가하고, 셸 명령, 파일 읽기, 패치 같은 모델에 노출되는 샌드박스 도구가 해당 사용자로 실행되어야 한다면 `SandboxAgent.run_as`를 설정하세요. `run_as`가 매니페스트에 아직 없는 사용자를 가리키면 러너가 해당 사용자를 유효 매니페스트에 자동으로 추가합니다.
|
||||
|
||||
```python
|
||||
from agents import Runner
|
||||
from agents.run import RunConfig
|
||||
from agents.sandbox import FileMode, Manifest, Permissions, SandboxAgent, SandboxRunConfig, User
|
||||
from agents.sandbox.entries import Dir, LocalDir
|
||||
from agents.sandbox.sandboxes.unix_local import UnixLocalSandboxClient
|
||||
|
||||
analyst = User(name="analyst")
|
||||
|
||||
agent = SandboxAgent(
|
||||
name="Dataroom analyst",
|
||||
instructions="Review the files in `dataroom/` and write findings to `output/`.",
|
||||
default_manifest=Manifest(
|
||||
# Declare the sandbox user so manifest entries can grant access to it.
|
||||
users=[analyst],
|
||||
entries={
|
||||
"dataroom": LocalDir(
|
||||
src="./dataroom",
|
||||
# Let the analyst traverse and read the mounted dataroom, but not edit it.
|
||||
group=analyst,
|
||||
permissions=Permissions(
|
||||
owner=FileMode.READ | FileMode.EXEC,
|
||||
group=FileMode.READ | FileMode.EXEC,
|
||||
other=FileMode.NONE,
|
||||
),
|
||||
),
|
||||
"output": Dir(
|
||||
# Give the analyst a writable scratch/output directory for artifacts.
|
||||
group=analyst,
|
||||
permissions=Permissions(
|
||||
owner=FileMode.ALL,
|
||||
group=FileMode.ALL,
|
||||
other=FileMode.NONE,
|
||||
),
|
||||
),
|
||||
},
|
||||
),
|
||||
# Run model-facing sandbox actions as this user, so those permissions apply.
|
||||
run_as=analyst,
|
||||
)
|
||||
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
"Summarize the contracts and call out renewal dates.",
|
||||
run_config=RunConfig(
|
||||
sandbox=SandboxRunConfig(client=UnixLocalSandboxClient()),
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
파일 수준 공유 규칙도 필요하다면 사용자를 매니페스트 그룹 및 항목의 `group` 메타데이터와 결합하세요. `run_as` 사용자는 샌드박스 네이티브 작업을 실행하는 주체를 제어하며, `Permissions`는 샌드박스가 워크스페이스를 구체화한 후 해당 사용자가 어떤 파일을 읽고, 쓰고, 실행할 수 있는지 제어합니다.
|
||||
|
||||
### SnapshotSpec
|
||||
|
||||
`SnapshotSpec`은 새 샌드박스 세션에서 저장된 워크스페이스 콘텐츠를 복원할 위치와 다시 영구 저장할 위치를 지정합니다. 이는 샌드박스 워크스페이스의 스냅샷 정책이며, `session_state`는 특정 샌드박스 백엔드를 재개하기 위한 직렬화된 연결 상태입니다.
|
||||
|
||||
로컬 영구 스냅샷에는 `LocalSnapshotSpec`을 사용하고, 앱에서 원격 스냅샷 클라이언트를 제공하는 경우 `RemoteSnapshotSpec`을 사용하세요. 로컬 스냅샷 설정을 사용할 수 없으면 대체 수단으로 아무 작업도 하지 않는 스냅샷이 사용되며, 워크스페이스 스냅샷 영속성을 원하지 않는 고급 호출자는 이를 명시적으로 사용할 수도 있습니다.
|
||||
|
||||
```python
|
||||
from pathlib import Path
|
||||
|
||||
from agents.run import RunConfig
|
||||
from agents.sandbox import LocalSnapshotSpec, SandboxRunConfig
|
||||
from agents.sandbox.sandboxes.unix_local import UnixLocalSandboxClient
|
||||
|
||||
run_config = RunConfig(
|
||||
sandbox=SandboxRunConfig(
|
||||
client=UnixLocalSandboxClient(),
|
||||
snapshot=LocalSnapshotSpec(base_path=Path("/tmp/my-sandbox-snapshots")),
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
러너가 새 샌드박스 세션을 생성하면 샌드박스 클라이언트가 해당 세션의 스냅샷 인스턴스를 구성합니다. 시작 시 스냅샷을 복원할 수 있다면 실행이 계속되기 전에 샌드박스가 저장된 워크스페이스 콘텐츠를 복원합니다. 정리 시 러너가 소유한 샌드박스 세션은 워크스페이스를 아카이브하고 스냅샷을 통해 다시 영구 저장합니다.
|
||||
|
||||
`snapshot`을 생략하면 런타임은 가능한 경우 기본 로컬 스냅샷 위치를 사용하려고 합니다. 설정할 수 없다면 아무 작업도 하지 않는 스냅샷을 대신 사용합니다. 마운트된 경로와 임시 경로는 영구 워크스페이스 콘텐츠로 스냅샷에 복사되지 않습니다.
|
||||
|
||||
### 샌드박스 수명 주기
|
||||
|
||||
수명 주기에는 **SDK 소유**와 **개발자 소유**라는 두 가지 모드가 있습니다.
|
||||
|
||||
<div class="sandbox-lifecycle-diagram" markdown="1">
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant App
|
||||
participant Runner
|
||||
participant Client
|
||||
participant Sandbox
|
||||
|
||||
App->>Runner: Runner.run(..., SandboxRunConfig(client=...))
|
||||
Runner->>Client: create or resume sandbox
|
||||
Client-->>Runner: sandbox session
|
||||
Runner->>Sandbox: start, run tools
|
||||
Runner->>Sandbox: stop and persist snapshot
|
||||
Runner->>Client: delete runner-owned resources
|
||||
|
||||
App->>Client: create(...)
|
||||
Client-->>App: sandbox session
|
||||
App->>Sandbox: async with sandbox
|
||||
App->>Runner: Runner.run(..., SandboxRunConfig(session=sandbox))
|
||||
Runner->>Sandbox: run tools
|
||||
App->>Sandbox: cleanup on context exit / aclose()
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
샌드박스가 한 번의 실행 동안만 유지되어도 된다면 SDK 소유 수명 주기를 사용하세요. `client`, 선택적 `manifest`, 선택적 `snapshot`, 클라이언트 `options`를 전달하면 러너가 샌드박스를 생성하거나 재개하고, 시작하고, 에이전트를 실행하고, 스냅샷 기반 워크스페이스 상태를 영구 저장하고, 샌드박스를 종료한 다음, 클라이언트가 러너 소유 리소스를 정리하도록 합니다.
|
||||
|
||||
```python
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
"Inspect the workspace and summarize what changed.",
|
||||
run_config=RunConfig(
|
||||
sandbox=SandboxRunConfig(client=UnixLocalSandboxClient()),
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
샌드박스를 미리 생성하거나, 여러 실행에서 하나의 실제 샌드박스를 재사용하거나, 실행 후 파일을 검사하거나, 직접 생성한 샌드박스를 통해 스트리밍하거나, 정리 시점을 정확히 결정하려면 개발자 소유 수명 주기를 사용하세요. `session=...`을 전달하면 러너가 해당 실제 샌드박스를 사용하지만 대신 닫지는 않습니다.
|
||||
|
||||
```python
|
||||
sandbox = await client.create(manifest=agent.default_manifest)
|
||||
|
||||
async with sandbox:
|
||||
run_config = RunConfig(sandbox=SandboxRunConfig(session=sandbox))
|
||||
await Runner.run(agent, "Analyze the files.", run_config=run_config)
|
||||
await Runner.run(agent, "Write the final report.", run_config=run_config)
|
||||
```
|
||||
|
||||
일반적으로 컨텍스트 관리자를 사용합니다. 진입 시 샌드박스를 시작하고 종료 시 세션 정리 수명 주기를 실행합니다. 앱에서 컨텍스트 관리자를 사용할 수 없다면 수명 주기 메서드를 직접 호출하세요.
|
||||
|
||||
```python
|
||||
sandbox = await client.create(
|
||||
manifest=agent.default_manifest,
|
||||
snapshot=LocalSnapshotSpec(base_path=Path("/tmp/my-sandbox-snapshots")),
|
||||
)
|
||||
try:
|
||||
await sandbox.start()
|
||||
await Runner.run(
|
||||
agent,
|
||||
"Analyze the files.",
|
||||
run_config=RunConfig(sandbox=SandboxRunConfig(session=sandbox)),
|
||||
)
|
||||
# Persist a checkpoint of the live workspace before doing more work.
|
||||
# `aclose()` also calls `stop()`, so this is only needed for an explicit mid-lifecycle save.
|
||||
await sandbox.stop()
|
||||
finally:
|
||||
await sandbox.aclose()
|
||||
```
|
||||
|
||||
`stop()`은 스냅샷 기반 워크스페이스 콘텐츠만 영구 저장하며 샌드박스를 해제하지 않습니다. `aclose()`는 전체 세션 정리 경로입니다. 중지 전 훅을 실행하고, `stop()`을 호출하고, 샌드박스 리소스를 종료하고, 세션 범위 종속성을 닫습니다.
|
||||
|
||||
## `SandboxRunConfig` 옵션
|
||||
|
||||
[`SandboxRunConfig`][agents.run_config.SandboxRunConfig]는 샌드박스 세션을 가져오는 위치와 새 세션을 초기화하는 방식을 결정하는 실행별 옵션을 보유합니다.
|
||||
|
||||
### 샌드박스 소스
|
||||
|
||||
다음 옵션은 러너가 샌드박스 세션을 재사용, 재개 또는 생성할지 결정합니다.
|
||||
|
||||
<div class="sandbox-nowrap-first-column-table" markdown="1">
|
||||
|
||||
| 옵션 | 사용 시점 | 참고 |
|
||||
| --- | --- | --- |
|
||||
| `client` | 러너가 샌드박스 세션을 생성, 재개, 정리하도록 하려는 경우 | 실제 샌드박스 `session`을 제공하지 않는 한 필수입니다. |
|
||||
| `session` | 실제 샌드박스 세션을 이미 직접 생성한 경우 | 호출자가 수명 주기를 소유하며, 러너는 해당 실제 샌드박스 세션을 재사용합니다. |
|
||||
| `session_state` | 직렬화된 샌드박스 세션 상태는 있지만 실제 샌드박스 세션 객체는 없는 경우 | `client`가 필요하며, 러너는 해당 명시적 상태에서 소유 세션으로 재개합니다. |
|
||||
|
||||
</div>
|
||||
|
||||
실제로 러너는 다음 순서로 샌드박스 세션을 결정합니다.
|
||||
|
||||
1. `run_config.sandbox.session`을 주입하면 해당 실제 샌드박스 세션을 직접 재사용합니다.
|
||||
2. 그렇지 않고 `RunState`에서 실행을 재개하는 경우 저장된 샌드박스 세션 상태를 재개합니다.
|
||||
3. 그렇지 않고 `run_config.sandbox.session_state`를 전달하면 해당 명시적인 직렬화된 샌드박스 세션 상태에서 재개합니다.
|
||||
4. 그렇지 않으면 러너가 새 샌드박스 세션을 생성합니다. 새 세션에서는 제공된 경우 `run_config.sandbox.manifest`를 사용하고, 그렇지 않으면 `agent.default_manifest`를 사용합니다.
|
||||
|
||||
### 새 세션 입력
|
||||
|
||||
다음 옵션은 러너가 새 샌드박스 세션을 생성할 때만 적용됩니다.
|
||||
|
||||
<div class="sandbox-nowrap-first-column-table" markdown="1">
|
||||
|
||||
| 옵션 | 사용 시점 | 참고 |
|
||||
| --- | --- | --- |
|
||||
| `manifest` | 일회성 새 세션 워크스페이스 재정의가 필요한 경우 | 생략하면 `agent.default_manifest`를 사용합니다. |
|
||||
| `snapshot` | 새 샌드박스 세션을 스냅샷에서 초기화해야 하는 경우 | 재개와 유사한 흐름이나 원격 스냅샷 클라이언트에 유용합니다. |
|
||||
| `options` | 샌드박스 클라이언트에 생성 시점 옵션이 필요한 경우 | Docker 이미지, Modal 앱 이름, E2B 템플릿, 타임아웃 및 유사한 클라이언트별 설정에 자주 사용됩니다. |
|
||||
|
||||
</div>
|
||||
|
||||
### 구체화 제어
|
||||
|
||||
`concurrency_limits`는 동시에 실행할 수 있는 샌드박스 구체화 작업량을 제어합니다. 대규모 매니페스트나 로컬 디렉터리 복사에 더 엄격한 리소스 제어가 필요하다면 `SandboxConcurrencyLimits(manifest_entries=..., local_dir_files=...)`를 사용하세요. 특정 제한을 비활성화하려면 해당 값을 `None`으로 설정하세요.
|
||||
|
||||
`archive_limits`는 아카이브 추출에 대한 SDK 측 리소스 검사를 제어합니다. SDK 기본 임곗값을 활성화하려면 `archive_limits=SandboxArchiveLimits()`를 설정하고, 아카이브에 더 엄격한 리소스 제어가 필요하면 `SandboxArchiveLimits(max_input_bytes=..., max_extracted_bytes=..., max_members=...)` 같은 명시적 값을 전달하세요. SDK 아카이브 리소스 제한이 없는 기본 동작을 유지하려면 `archive_limits=None`으로 두고, 특정 제한만 비활성화하려면 개별 필드를 `None`으로 설정하세요.
|
||||
|
||||
다음과 같은 사항에 유의해야 합니다.
|
||||
|
||||
- 새 세션: `manifest=`와 `snapshot=`은 러너가 새 샌드박스 세션을 생성할 때만 적용됩니다.
|
||||
- 재개와 스냅샷의 차이: `session_state=`는 이전에 직렬화된 샌드박스 상태에 다시 연결하지만, `snapshot=`은 저장된 워크스페이스 콘텐츠에서 새 샌드박스 세션을 초기화합니다.
|
||||
- 클라이언트별 옵션: `options=`는 샌드박스 클라이언트에 따라 달라지며, Docker와 다수의 호스티드 클라이언트에서 필수입니다.
|
||||
- 주입된 실제 세션: 실행 중인 샌드박스 `session`을 전달하면 기능이 주도하는 매니페스트 업데이트로 호환되는 비마운트 항목을 추가할 수 있습니다. 그러나 `manifest.root`, `manifest.environment`, `manifest.users`, `manifest.groups`를 변경하거나, 기존 항목을 제거하거나, 항목 유형을 대체하거나, 마운트 항목을 추가 또는 변경할 수는 없습니다.
|
||||
- 러너 API: `SandboxAgent` 실행도 일반적인 `Runner.run()`, `Runner.run_sync()`, `Runner.run_streamed()` API를 사용합니다.
|
||||
|
||||
## 전체 예제: 코딩 작업
|
||||
|
||||
다음 코딩 스타일 예제는 기본 출발점으로 적합합니다.
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
from pathlib import Path
|
||||
|
||||
from agents import ModelSettings, Runner
|
||||
from agents.run import RunConfig
|
||||
from agents.sandbox import Manifest, SandboxAgent, SandboxRunConfig
|
||||
from agents.sandbox.capabilities import (
|
||||
Capabilities,
|
||||
LocalDirLazySkillSource,
|
||||
Skills,
|
||||
)
|
||||
from agents.sandbox.entries import LocalDir
|
||||
from agents.sandbox.sandboxes.unix_local import UnixLocalSandboxClient
|
||||
|
||||
EXAMPLE_DIR = Path(__file__).resolve().parent
|
||||
HOST_REPO_DIR = EXAMPLE_DIR / "repo"
|
||||
HOST_SKILLS_DIR = EXAMPLE_DIR / "skills"
|
||||
TARGET_TEST_CMD = "sh tests/test_credit_note.sh"
|
||||
|
||||
|
||||
def build_agent(model: str) -> SandboxAgent[None]:
|
||||
return SandboxAgent(
|
||||
name="Sandbox engineer",
|
||||
model=model,
|
||||
instructions=(
|
||||
"Inspect the repo, make the smallest correct change, run the most relevant checks, "
|
||||
"and summarize the file changes and risks. "
|
||||
"Read `repo/task.md` before editing files. Stay grounded in the repository, preserve "
|
||||
"existing behavior, and mention the exact verification command you ran. "
|
||||
"Use the `$credit-note-fixer` skill before editing files. If the repo lives under "
|
||||
"`repo/`, remember that `apply_patch` paths stay relative to the sandbox workspace "
|
||||
"root, so edits still target `repo/...`."
|
||||
),
|
||||
# Put repos and task files in the manifest.
|
||||
default_manifest=Manifest(
|
||||
entries={
|
||||
"repo": LocalDir(src=HOST_REPO_DIR),
|
||||
}
|
||||
),
|
||||
capabilities=Capabilities.default() + [
|
||||
Skills(
|
||||
lazy_from=LocalDirLazySkillSource(
|
||||
# This is a host path read by the SDK process.
|
||||
# Requested skills are copied into `skills_path` in the sandbox.
|
||||
source=LocalDir(src=HOST_SKILLS_DIR),
|
||||
)
|
||||
),
|
||||
],
|
||||
model_settings=ModelSettings(tool_choice="required"),
|
||||
)
|
||||
|
||||
|
||||
async def main(model: str, prompt: str) -> None:
|
||||
result = await Runner.run(
|
||||
build_agent(model),
|
||||
prompt,
|
||||
run_config=RunConfig(
|
||||
sandbox=SandboxRunConfig(client=UnixLocalSandboxClient()),
|
||||
workflow_name="Sandbox coding example",
|
||||
),
|
||||
)
|
||||
print(result.final_output)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(
|
||||
main(
|
||||
model="gpt-5.6-sol",
|
||||
prompt=(
|
||||
"Open `repo/task.md`, use the `$credit-note-fixer` skill, fix the bug, "
|
||||
f"run `{TARGET_TEST_CMD}`, and summarize the change."
|
||||
),
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
[examples/sandbox/docs/coding_task.py](https://github.com/openai/openai-agents-python/blob/main/examples/sandbox/docs/coding_task.py)를 참고하세요. 이 예제는 Unix 로컬 실행 전반에서 결정론적으로 검증할 수 있도록 소규모 셸 기반 저장소를 사용합니다. 실제 작업 저장소는 물론 Python, JavaScript 또는 기타 어떤 언어로 작성되어도 됩니다.
|
||||
|
||||
## 일반적인 패턴
|
||||
|
||||
위의 전체 예제에서 시작하세요. 대부분의 경우 동일한 `SandboxAgent`를 그대로 유지하면서 샌드박스 클라이언트, 샌드박스 세션 소스 또는 워크스페이스 소스만 변경할 수 있습니다.
|
||||
|
||||
### 샌드박스 클라이언트 전환
|
||||
|
||||
에이전트 정의는 그대로 유지하고 실행 구성만 변경하세요. 컨테이너 격리나 이미지 일관성이 필요하면 Docker를 사용하고, 공급자가 관리하는 실행이 필요하면 호스티드 공급자를 사용하세요. 예제와 공급자 옵션은 [샌드박스 클라이언트](clients.md)를 참고하세요.
|
||||
|
||||
### 워크스페이스 재정의
|
||||
|
||||
에이전트 정의는 그대로 유지하고 새 세션 매니페스트만 교체하세요.
|
||||
|
||||
```python
|
||||
from agents.run import RunConfig
|
||||
from agents.sandbox import Manifest, SandboxRunConfig
|
||||
from agents.sandbox.entries import GitRepo
|
||||
from agents.sandbox.sandboxes.unix_local import UnixLocalSandboxClient
|
||||
|
||||
run_config = RunConfig(
|
||||
sandbox=SandboxRunConfig(
|
||||
client=UnixLocalSandboxClient(),
|
||||
manifest=Manifest(
|
||||
entries={
|
||||
"repo": GitRepo(repo="openai/openai-agents-python", ref="main"),
|
||||
}
|
||||
),
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
에이전트를 다시 구성하지 않고 동일한 에이전트 역할을 서로 다른 저장소, 패킷 또는 작업 번들에 적용하려면 이 방식을 사용하세요. 위에서 검증한 코딩 예제는 일회성 재정의 대신 `default_manifest`를 사용하여 동일한 패턴을 보여줍니다.
|
||||
|
||||
### 샌드박스 세션 주입
|
||||
|
||||
수명 주기를 명시적으로 제어하거나, 실행 후 검사하거나, 출력을 복사해야 한다면 실제 샌드박스 세션을 주입하세요.
|
||||
|
||||
```python
|
||||
from agents import Runner
|
||||
from agents.run import RunConfig
|
||||
from agents.sandbox import SandboxRunConfig
|
||||
from agents.sandbox.sandboxes.unix_local import UnixLocalSandboxClient
|
||||
|
||||
client = UnixLocalSandboxClient()
|
||||
sandbox = await client.create(manifest=agent.default_manifest)
|
||||
|
||||
async with sandbox:
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
prompt,
|
||||
run_config=RunConfig(
|
||||
sandbox=SandboxRunConfig(session=sandbox),
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
실행 후 워크스페이스를 검사하거나 이미 시작된 샌드박스 세션을 통해 스트리밍하려면 이 방식을 사용하세요. [examples/sandbox/docs/coding_task.py](https://github.com/openai/openai-agents-python/blob/main/examples/sandbox/docs/coding_task.py)와 [examples/sandbox/docker/docker_runner.py](https://github.com/openai/openai-agents-python/blob/main/examples/sandbox/docker/docker_runner.py)를 참고하세요.
|
||||
|
||||
### 세션 상태에서 재개
|
||||
|
||||
`RunState` 외부에서 샌드박스 상태를 이미 직렬화했다면 러너가 해당 상태에 다시 연결하도록 하세요.
|
||||
|
||||
```python
|
||||
from agents.run import RunConfig
|
||||
from agents.sandbox import SandboxRunConfig
|
||||
|
||||
serialized = load_saved_payload()
|
||||
restored_state = client.deserialize_session_state(serialized)
|
||||
|
||||
run_config = RunConfig(
|
||||
sandbox=SandboxRunConfig(
|
||||
client=client,
|
||||
session_state=restored_state,
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
샌드박스 상태를 자체 스토리지나 작업 시스템에 저장하고 있으며 `Runner`가 해당 상태에서 직접 재개하도록 하려면 이 방식을 사용하세요. 직렬화/역직렬화 흐름은 [examples/sandbox/extensions/blaxel_runner.py](https://github.com/openai/openai-agents-python/blob/main/examples/sandbox/extensions/blaxel_runner.py)를 참고하세요.
|
||||
|
||||
### 스냅샷에서 시작
|
||||
|
||||
저장된 파일과 아티팩트로 새 샌드박스를 초기화합니다.
|
||||
|
||||
```python
|
||||
from pathlib import Path
|
||||
|
||||
from agents.run import RunConfig
|
||||
from agents.sandbox import LocalSnapshotSpec, SandboxRunConfig
|
||||
from agents.sandbox.sandboxes.unix_local import UnixLocalSandboxClient
|
||||
|
||||
run_config = RunConfig(
|
||||
sandbox=SandboxRunConfig(
|
||||
client=UnixLocalSandboxClient(),
|
||||
snapshot=LocalSnapshotSpec(base_path=Path("/tmp/my-sandbox-snapshot")),
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
새 실행이 `agent.default_manifest`만 사용하는 대신 저장된 워크스페이스 콘텐츠에서 시작해야 한다면 이 방식을 사용하세요. 로컬 스냅샷 흐름은 [examples/sandbox/memory.py](https://github.com/openai/openai-agents-python/blob/main/examples/sandbox/memory.py)를, 원격 스냅샷 클라이언트는 [examples/sandbox/sandbox_agent_with_remote_snapshot.py](https://github.com/openai/openai-agents-python/blob/main/examples/sandbox/sandbox_agent_with_remote_snapshot.py)를 참고하세요.
|
||||
|
||||
### Git에서 스킬 로드
|
||||
|
||||
로컬 스킬 소스를 저장소 기반 소스로 교체합니다.
|
||||
|
||||
```python
|
||||
from agents.sandbox.capabilities import Capabilities, Skills
|
||||
from agents.sandbox.entries import GitRepo
|
||||
|
||||
capabilities = Capabilities.default() + [
|
||||
Skills(from_=GitRepo(repo="sdcoffey/tax-prep-skills", ref="main")),
|
||||
]
|
||||
```
|
||||
|
||||
스킬 번들에 자체 릴리스 주기가 있거나 여러 샌드박스에서 공유해야 한다면 이 방식을 사용하세요. [examples/sandbox/tax_prep.py](https://github.com/openai/openai-agents-python/blob/main/examples/sandbox/tax_prep.py)를 참고하세요.
|
||||
|
||||
### 도구로 노출
|
||||
|
||||
도구 에이전트는 자체 샌드박스 경계를 사용하거나 상위 실행의 실제 샌드박스를 재사용할 수 있습니다. 빠른 읽기 전용 탐색기 에이전트에는 재사용 방식이 유용합니다. 다른 샌드박스를 생성하고, 초기 콘텐츠를 채우고, 스냅샷을 만드는 비용 없이 상위 에이전트가 사용하는 정확한 워크스페이스를 검사할 수 있습니다.
|
||||
|
||||
```python
|
||||
from agents import Runner
|
||||
from agents.run import RunConfig
|
||||
from agents.sandbox import FileMode, Manifest, Permissions, SandboxAgent, SandboxRunConfig, User
|
||||
from agents.sandbox.entries import Dir, File
|
||||
from agents.sandbox.sandboxes.unix_local import UnixLocalSandboxClient
|
||||
|
||||
coordinator = User(name="coordinator")
|
||||
explorer = User(name="explorer")
|
||||
|
||||
manifest = Manifest(
|
||||
users=[coordinator, explorer],
|
||||
entries={
|
||||
"pricing_packet": Dir(
|
||||
group=coordinator,
|
||||
permissions=Permissions(
|
||||
owner=FileMode.ALL,
|
||||
group=FileMode.ALL,
|
||||
other=FileMode.READ | FileMode.EXEC,
|
||||
directory=True,
|
||||
),
|
||||
children={
|
||||
"pricing.md": File(
|
||||
content=b"Pricing packet contents...",
|
||||
group=coordinator,
|
||||
permissions=Permissions(
|
||||
owner=FileMode.ALL,
|
||||
group=FileMode.ALL,
|
||||
other=FileMode.READ,
|
||||
),
|
||||
),
|
||||
},
|
||||
),
|
||||
"work": Dir(
|
||||
group=coordinator,
|
||||
permissions=Permissions(
|
||||
owner=FileMode.ALL,
|
||||
group=FileMode.ALL,
|
||||
other=FileMode.NONE,
|
||||
directory=True,
|
||||
),
|
||||
),
|
||||
},
|
||||
)
|
||||
|
||||
pricing_explorer = SandboxAgent(
|
||||
name="Pricing Explorer",
|
||||
instructions="Read `pricing_packet/` and summarize commercial risk. Do not edit files.",
|
||||
run_as=explorer,
|
||||
)
|
||||
|
||||
client = UnixLocalSandboxClient()
|
||||
sandbox = await client.create(manifest=manifest)
|
||||
|
||||
async with sandbox:
|
||||
shared_run_config = RunConfig(
|
||||
sandbox=SandboxRunConfig(session=sandbox),
|
||||
)
|
||||
|
||||
orchestrator = SandboxAgent(
|
||||
name="Revenue Operations Coordinator",
|
||||
instructions="Coordinate the review and write final notes to `work/`.",
|
||||
run_as=coordinator,
|
||||
tools=[
|
||||
pricing_explorer.as_tool(
|
||||
tool_name="review_pricing_packet",
|
||||
tool_description="Inspect the pricing packet and summarize commercial risk.",
|
||||
run_config=shared_run_config,
|
||||
max_turns=2,
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
result = await Runner.run(
|
||||
orchestrator,
|
||||
"Review the pricing packet, then write final notes to `work/summary.md`.",
|
||||
run_config=shared_run_config,
|
||||
)
|
||||
```
|
||||
|
||||
여기에서 상위 에이전트는 `coordinator`로 실행되고, 탐색기 도구 에이전트는 동일한 실제 샌드박스 세션 내에서 `explorer`로 실행됩니다. `pricing_packet/` 항목은 `other` 사용자가 읽을 수 있으므로 탐색기가 빠르게 검사할 수 있지만 쓰기 비트는 없습니다. `work/` 디렉터리는 코디네이터의 사용자/그룹만 사용할 수 있으므로, 상위 에이전트는 최종 아티팩트를 작성할 수 있고 탐색기는 읽기 전용으로 유지됩니다.
|
||||
|
||||
도구 에이전트에 실제 격리가 필요하다면 자체 샌드박스 `RunConfig`를 제공하세요.
|
||||
|
||||
```python
|
||||
from docker import from_env as docker_from_env
|
||||
|
||||
from agents.run import RunConfig
|
||||
from agents.sandbox import SandboxAgent, SandboxRunConfig
|
||||
from agents.sandbox.sandboxes.docker import DockerSandboxClient, DockerSandboxClientOptions
|
||||
|
||||
rollout_agent = SandboxAgent(
|
||||
name="Rollout Reviewer",
|
||||
instructions="Inspect the rollout packet and summarize implementation risk.",
|
||||
)
|
||||
|
||||
rollout_agent.as_tool(
|
||||
tool_name="review_rollout_risk",
|
||||
tool_description="Inspect the rollout packet and summarize implementation risk.",
|
||||
run_config=RunConfig(
|
||||
sandbox=SandboxRunConfig(
|
||||
client=DockerSandboxClient(docker_from_env()),
|
||||
options=DockerSandboxClientOptions(image="python:3.14-slim"),
|
||||
),
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
도구 에이전트가 자유롭게 변경하거나, 신뢰할 수 없는 명령을 실행하거나, 다른 백엔드/이미지를 사용해야 한다면 별도의 샌드박스를 사용하세요. [examples/sandbox/sandbox_agents_as_tools.py](https://github.com/openai/openai-agents-python/blob/main/examples/sandbox/sandbox_agents_as_tools.py)를 참고하세요.
|
||||
|
||||
### 로컬 도구 및 MCP와 결합
|
||||
|
||||
샌드박스 워크스페이스를 유지하면서 동일한 에이전트에서 일반 도구도 사용할 수 있습니다.
|
||||
|
||||
```python
|
||||
from agents.sandbox import SandboxAgent
|
||||
from agents.sandbox.capabilities import Shell
|
||||
|
||||
agent = SandboxAgent(
|
||||
name="Workspace reviewer",
|
||||
instructions="Inspect the workspace and call host tools when needed.",
|
||||
tools=[get_discount_approval_path],
|
||||
mcp_servers=[server],
|
||||
capabilities=[Shell()],
|
||||
)
|
||||
```
|
||||
|
||||
워크스페이스 검사가 에이전트 작업의 일부일 뿐이라면 이 방식을 사용하세요. [examples/sandbox/sandbox_agent_with_tools.py](https://github.com/openai/openai-agents-python/blob/main/examples/sandbox/sandbox_agent_with_tools.py)를 참고하세요.
|
||||
|
||||
## 메모리
|
||||
|
||||
향후 샌드박스 에이전트 실행이 이전 실행에서 학습해야 한다면 `Memory` 기능을 사용하세요. 메모리는 SDK의 대화형 `Session` 메모리와 별개입니다. 학습한 내용을 샌드박스 워크스페이스 내부의 파일로 정제하고, 이후 실행에서 해당 파일을 읽을 수 있도록 합니다.
|
||||
|
||||
설정, 읽기/생성 동작, 멀티턴 대화, 레이아웃 격리는 [에이전트 메모리](memory.md)를 참고하세요.
|
||||
|
||||
## 구성 패턴
|
||||
|
||||
단일 에이전트 패턴을 이해한 다음에는 더 큰 시스템에서 샌드박스 경계를 어디에 둘지 결정해야 합니다.
|
||||
|
||||
샌드박스 에이전트는 SDK의 나머지 기능과 계속 함께 구성할 수 있습니다.
|
||||
|
||||
- [핸드오프](../handoffs.md): 샌드박스를 사용하지 않는 접수 에이전트에서 문서 중심 작업을 샌드박스 검토자에게 전달합니다.
|
||||
- [Agents as tools](../tools.md#agents-as-tools): 여러 샌드박스 에이전트를 도구로 노출합니다. 일반적으로 각 `Agent.as_tool(...)` 호출에 `run_config=RunConfig(sandbox=SandboxRunConfig(...))`를 전달하여 각 도구에 자체 샌드박스 경계를 제공합니다.
|
||||
- [MCP](../mcp.md)와 일반 함수 도구: 샌드박스 기능은 `mcp_servers` 및 일반 Python 도구와 함께 사용할 수 있습니다.
|
||||
- [에이전트 실행](../running_agents.md): 샌드박스 실행도 일반적인 `Runner` API를 사용합니다.
|
||||
|
||||
특히 일반적인 두 가지 패턴은 다음과 같습니다.
|
||||
|
||||
- 샌드박스를 사용하지 않는 에이전트가 워크스페이스 격리가 필요한 워크플로 부분만 샌드박스 에이전트에 핸드오프
|
||||
- 오케스트레이터가 여러 샌드박스 에이전트를 도구로 노출하고, 일반적으로 각 `Agent.as_tool(...)` 호출에 별도의 샌드박스 `RunConfig`를 사용하여 각 도구에 자체 격리 워크스페이스 제공
|
||||
|
||||
### 턴과 샌드박스 실행
|
||||
|
||||
핸드오프와 에이전트 도구 호출은 별도로 설명하는 것이 이해에 도움이 됩니다.
|
||||
|
||||
핸드오프에서는 여전히 하나의 최상위 실행과 하나의 최상위 턴 루프가 유지됩니다. 활성 에이전트는 변경되지만 실행이 중첩되지는 않습니다. 샌드박스를 사용하지 않는 접수 에이전트가 샌드박스 검토자에게 핸드오프하면, 동일한 실행의 다음 모델 호출이 샌드박스 에이전트용으로 준비되며 해당 샌드박스 에이전트가 다음 턴을 수행합니다. 즉, 핸드오프는 동일한 실행의 다음 턴을 담당하는 에이전트를 변경합니다. [examples/sandbox/handoffs.py](https://github.com/openai/openai-agents-python/blob/main/examples/sandbox/handoffs.py)를 참고하세요.
|
||||
|
||||
`Agent.as_tool(...)`에서는 관계가 다릅니다. 외부 오케스트레이터는 외부 턴 하나를 사용하여 도구 호출을 결정하고, 해당 도구 호출은 샌드박스 에이전트의 중첩 실행을 시작합니다. 중첩 실행에는 자체 턴 루프, `max_turns`, 승인, 일반적으로 자체 샌드박스 `RunConfig`가 있습니다. 중첩 턴 하나로 완료될 수도 있고 여러 턴이 걸릴 수도 있습니다. 외부 오케스트레이터의 관점에서는 이 모든 작업이 하나의 도구 호출 뒤에서 이루어지므로, 중첩 턴은 외부 실행의 턴 카운터를 증가시키지 않습니다. [examples/sandbox/sandbox_agents_as_tools.py](https://github.com/openai/openai-agents-python/blob/main/examples/sandbox/sandbox_agents_as_tools.py)를 참고하세요.
|
||||
|
||||
승인 동작도 동일한 구분을 따릅니다.
|
||||
|
||||
- 핸드오프에서는 샌드박스 에이전트가 해당 실행의 활성 에이전트가 되므로 승인이 동일한 최상위 실행에 유지됩니다.
|
||||
- `Agent.as_tool(...)`에서는 샌드박스 도구 에이전트 내부에서 발생한 승인도 외부 실행에 표시되지만, 저장된 중첩 실행 상태에서 가져오며 외부 실행이 재개될 때 중첩 샌드박스 실행을 재개합니다.
|
||||
|
||||
## 추가 자료
|
||||
|
||||
- [빠른 시작](../sandbox_agents.md): 샌드박스 에이전트 하나를 실행합니다.
|
||||
- [샌드박스 클라이언트](clients.md): 로컬, Docker, 호스티드, 마운트 옵션을 선택합니다.
|
||||
- [에이전트 메모리](memory.md): 이전 샌드박스 실행에서 얻은 학습 내용을 보존하고 재사용합니다.
|
||||
- [examples/sandbox/](https://github.com/openai/openai-agents-python/tree/main/examples/sandbox): 실행 가능한 로컬, 코딩, 메모리, 핸드오프, 에이전트 구성 패턴입니다.
|
||||
@@ -0,0 +1,189 @@
|
||||
---
|
||||
search:
|
||||
exclude: true
|
||||
---
|
||||
# 에이전트 메모리
|
||||
|
||||
메모리는 향후 sandbox-agent 실행이 이전 실행에서 학습할 수 있게 합니다. 이는 메시지 기록을 저장하는 SDK의 대화형 [`Session`](../sessions/index.md) 메모리와는 별개입니다. 메모리는 이전 실행에서 얻은 교훈을 샌드박스 워크스페이스의 파일로 정제합니다.
|
||||
|
||||
!!! warning "베타 기능"
|
||||
|
||||
샌드박스 에이전트는 베타 버전입니다. API, 기본값, 지원 기능의 세부 사항은 정식 출시 전에 변경될 수 있으며, 시간이 지나면서 더 고급 기능이 추가될 예정입니다.
|
||||
|
||||
메모리는 향후 실행에서 세 가지 비용을 줄일 수 있습니다.
|
||||
|
||||
1. 에이전트 비용: 에이전트가 워크플로를 완료하는 데 오랜 시간이 걸렸다면, 다음 실행에서는 탐색이 덜 필요해야 합니다. 이를 통해 토큰 사용량과 완료까지 걸리는 시간을 줄일 수 있습니다.
|
||||
2. 사용자 비용: 사용자가 에이전트를 수정했거나 선호 사항을 표현했다면, 향후 실행에서 해당 피드백을 기억할 수 있습니다. 이를 통해 사람의 개입을 줄일 수 있습니다.
|
||||
3. 컨텍스트 비용: 에이전트가 이전에 작업을 완료했고 사용자가 그 작업을 이어서 진행하려는 경우, 사용자가 이전 스레드를 찾거나 모든 컨텍스트를 다시 입력할 필요가 없어야 합니다. 이를 통해 작업 설명을 더 짧게 만들 수 있습니다.
|
||||
|
||||
버그를 수정하고, 메모리를 생성하고, 스냅샷을 재개한 뒤, 후속 검증 실행에서 해당 메모리를 사용하는 완전한 2회 실행 예제는 [examples/sandbox/memory.py](https://github.com/openai/openai-agents-python/blob/main/examples/sandbox/memory.py)를 참고하세요. 별도의 메모리 레이아웃을 사용하는 멀티턴, 멀티 에이전트 예제는 [examples/sandbox/memory_multi_agent_multiturn.py](https://github.com/openai/openai-agents-python/blob/main/examples/sandbox/memory_multi_agent_multiturn.py)를 참고하세요.
|
||||
|
||||
## 메모리 활성화
|
||||
|
||||
샌드박스 에이전트에 기능으로 `Memory()`를 추가합니다.
|
||||
|
||||
```python
|
||||
from pathlib import Path
|
||||
import tempfile
|
||||
|
||||
from agents.sandbox import LocalSnapshotSpec, SandboxAgent
|
||||
from agents.sandbox.capabilities import Filesystem, Memory, Shell
|
||||
|
||||
agent = SandboxAgent(
|
||||
name="Memory-enabled reviewer",
|
||||
instructions="Inspect the workspace and preserve useful lessons for follow-up runs.",
|
||||
capabilities=[Memory(), Filesystem(), Shell()],
|
||||
)
|
||||
|
||||
with tempfile.TemporaryDirectory(prefix="sandbox-memory-example-") as snapshot_dir:
|
||||
sandbox = await client.create(
|
||||
manifest=manifest,
|
||||
snapshot=LocalSnapshotSpec(base_path=Path(snapshot_dir)),
|
||||
)
|
||||
```
|
||||
|
||||
읽기가 활성화된 경우 `Memory()`에는 `Shell()`이 필요합니다. 이는 주입된 요약만으로 충분하지 않을 때 에이전트가 메모리 파일을 읽고 검색할 수 있게 합니다. 라이브 메모리 업데이트가 활성화된 경우(기본값), `Filesystem()`도 필요합니다. 이는 에이전트가 오래된 메모리를 발견하거나 사용자가 메모리 업데이트를 요청할 때 `memories/MEMORY.md`를 업데이트할 수 있게 합니다.
|
||||
|
||||
기본적으로 메모리 아티팩트는 샌드박스 워크스페이스의 `memories/` 아래에 저장됩니다. 나중 실행에서 이를 재사용하려면 동일한 라이브 샌드박스 세션을 유지하거나, 영구 저장된 세션 상태 또는 스냅샷에서 재개하여 구성된 memories 디렉터리 전체를 보존하고 재사용하세요. 새 빈 샌드박스는 빈 메모리로 시작합니다.
|
||||
|
||||
`Memory()`는 메모리 읽기와 생성을 모두 활성화합니다. 메모리를 읽어야 하지만 새 메모리를 생성해서는 안 되는 에이전트에는 `Memory(generate=None)`을 사용하세요. 예를 들어 내부 에이전트, 서브에이전트, 검사기, 또는 실행이 많은 신호를 추가하지 않는 일회성 도구 에이전트가 이에 해당합니다. 실행이 나중을 위한 메모리는 생성해야 하지만, 기존 메모리의 영향을 받는 것을 사용자가 원하지 않는 경우에는 `Memory(read=None)`을 사용하세요.
|
||||
|
||||
## 메모리 읽기
|
||||
|
||||
메모리 읽기는 점진적 공개 방식을 사용합니다. 실행 시작 시 SDK는 일반적으로 유용한 팁, 사용자 선호 사항, 사용 가능한 메모리에 대한 작은 요약(`memory_summary.md`)을 에이전트의 개발자 프롬프트에 주입합니다. 이를 통해 에이전트는 이전 작업이 관련될 수 있는지 판단하기에 충분한 컨텍스트를 얻습니다.
|
||||
|
||||
이전 작업이 관련 있어 보이면, 에이전트는 현재 작업의 키워드로 구성된 메모리 인덱스(`memories_dir` 아래의 `MEMORY.md`)를 검색합니다. 작업에 더 자세한 정보가 필요할 때만 구성된 `rollout_summaries/` 디렉터리 아래의 해당 이전 롤아웃 요약을 엽니다.
|
||||
|
||||
메모리는 오래될 수 있습니다. 에이전트는 메모리를 지침으로만 취급하고 현재 환경을 신뢰하도록 지시받습니다. 기본적으로 메모리 읽기에는 `live_update`가 활성화되어 있으므로, 에이전트가 오래된 메모리를 발견하면 동일한 실행에서 구성된 `MEMORY.md`를 업데이트할 수 있습니다. 에이전트가 메모리를 읽어야 하지만 실행 중에 수정해서는 안 되는 경우, 예를 들어 실행이 지연 시간에 민감한 경우에는 라이브 업데이트를 비활성화하세요.
|
||||
|
||||
## 메모리 생성
|
||||
|
||||
실행이 끝나면 샌드박스 런타임은 해당 실행 세그먼트를 대화 파일에 추가합니다. 누적된 대화 파일은 샌드박스 세션이 닫힐 때 처리됩니다.
|
||||
|
||||
메모리 생성에는 두 단계가 있습니다.
|
||||
|
||||
1. 1단계: 대화 추출. 메모리 생성 모델이 누적된 대화 파일 하나를 처리하고 대화 요약을 생성합니다. 시스템, 개발자, 추론 내용은 생략됩니다. 대화가 너무 길면 컨텍스트 창에 맞도록 잘리며, 시작과 끝은 보존됩니다. 또한 원문 메모리 추출도 생성합니다. 이는 2단계에서 통합할 수 있는 대화의 간결한 노트입니다.
|
||||
2. 2단계: 레이아웃 통합. 통합 에이전트가 하나의 메모리 레이아웃에 대한 원문 메모리를 읽고, 더 많은 근거가 필요할 때 대화 요약을 연 다음, 패턴을 `MEMORY.md`와 `memory_summary.md`로 추출합니다.
|
||||
|
||||
기본 워크스페이스 레이아웃은 다음과 같습니다.
|
||||
|
||||
```text
|
||||
workspace/
|
||||
├── sessions/
|
||||
│ └── <rollout-id>.jsonl
|
||||
└── memories/
|
||||
├── memory_summary.md
|
||||
├── MEMORY.md
|
||||
├── raw_memories.md (intermediate)
|
||||
├── phase_two_selection.json (intermediate)
|
||||
├── raw_memories/ (intermediate)
|
||||
│ └── <rollout-id>.md
|
||||
├── rollout_summaries/
|
||||
│ └── <rollout-id>_<slug>.md
|
||||
└── skills/
|
||||
```
|
||||
|
||||
`MemoryGenerateConfig`로 메모리 생성을 구성할 수 있습니다.
|
||||
|
||||
```python
|
||||
from agents.sandbox import MemoryGenerateConfig
|
||||
from agents.sandbox.capabilities import Memory
|
||||
|
||||
memory = Memory(
|
||||
generate=MemoryGenerateConfig(
|
||||
max_raw_memories_for_consolidation=128,
|
||||
extra_prompt="Pay extra attention to what made the customer more satisfied or annoyed",
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
`extra_prompt`를 사용하여 메모리 생성기에 사용 사례에서 가장 중요한 신호를 알려줄 수 있습니다. 예를 들어 GTM 에이전트의 경우 고객 및 회사 세부 정보가 해당됩니다.
|
||||
|
||||
최근 원문 메모리가 `max_raw_memories_for_consolidation`(기본값 256)을 초과하면, 2단계는 가장 최신 대화의 메모리만 유지하고 더 오래된 메모리는 제거합니다. 최신성은 대화가 마지막으로 업데이트된 시간을 기준으로 합니다. 이 망각 메커니즘은 메모리가 최신 환경을 반영하는 데 도움이 됩니다.
|
||||
|
||||
## 멀티턴 대화
|
||||
|
||||
멀티턴 샌드박스 채팅에는 일반 SDK `Session`을 동일한 라이브 샌드박스 세션과 함께 사용하세요.
|
||||
|
||||
```python
|
||||
from agents import Runner, SQLiteSession
|
||||
from agents.run import RunConfig
|
||||
from agents.sandbox import SandboxRunConfig
|
||||
|
||||
conversation_session = SQLiteSession("gtm-q2-pipeline-review")
|
||||
sandbox = await client.create(manifest=agent.default_manifest)
|
||||
|
||||
async with sandbox:
|
||||
run_config = RunConfig(
|
||||
sandbox=SandboxRunConfig(session=sandbox),
|
||||
workflow_name="GTM memory example",
|
||||
)
|
||||
await Runner.run(
|
||||
agent,
|
||||
"Analyze data/leads.csv and identify one promising GTM segment.",
|
||||
session=conversation_session,
|
||||
run_config=run_config,
|
||||
)
|
||||
await Runner.run(
|
||||
agent,
|
||||
"Using that analysis, write a short outreach hypothesis.",
|
||||
session=conversation_session,
|
||||
run_config=run_config,
|
||||
)
|
||||
```
|
||||
|
||||
두 실행은 동일한 SDK 대화 세션(`session=conversation_session`)을 전달하므로 같은 `session.session_id`를 공유하고, 따라서 하나의 메모리 대화 파일에 추가됩니다. 이는 라이브 워크스페이스를 식별하며 메모리 대화 ID로 사용되지 않는 샌드박스(`sandbox`)와 다릅니다. 1단계는 샌드박스 세션이 닫힐 때 누적된 대화를 보므로, 서로 분리된 두 턴이 아니라 전체 교환에서 메모리를 추출할 수 있습니다.
|
||||
|
||||
여러 `Runner.run(...)` 호출이 하나의 메모리 대화가 되도록 하려면 해당 호출들에 안정적인 식별자를 전달하세요. 메모리가 실행을 대화와 연결할 때는 다음 순서로 확인합니다.
|
||||
|
||||
1. `Runner.run(...)`에 전달한 경우 `conversation_id`
|
||||
2. `SQLiteSession` 같은 SDK `Session`을 전달한 경우 `session.session_id`
|
||||
3. 위 둘 중 어느 것도 없을 경우 `RunConfig.group_id`
|
||||
4. 안정적인 식별자가 없을 경우 생성된 실행별 ID
|
||||
|
||||
## 서로 다른 에이전트의 메모리를 격리하기 위한 서로 다른 레이아웃 사용
|
||||
|
||||
메모리 격리는 에이전트 이름이 아니라 `MemoryLayoutConfig`를 기준으로 합니다. 동일한 레이아웃과 동일한 메모리 대화 ID를 가진 에이전트는 하나의 메모리 대화와 하나의 통합된 메모리를 공유합니다. 서로 다른 레이아웃을 가진 에이전트는 동일한 샌드박스 워크스페이스를 공유하더라도 별도의 롤아웃 파일, 원문 메모리, `MEMORY.md`, `memory_summary.md`를 유지합니다.
|
||||
|
||||
여러 에이전트가 하나의 샌드박스를 공유하지만 메모리는 공유해서는 안 되는 경우 별도의 레이아웃을 사용하세요.
|
||||
|
||||
```python
|
||||
from agents import SQLiteSession
|
||||
from agents.sandbox import MemoryLayoutConfig, SandboxAgent
|
||||
from agents.sandbox.capabilities import Filesystem, Memory, Shell
|
||||
|
||||
gtm_agent = SandboxAgent(
|
||||
name="GTM reviewer",
|
||||
instructions="Analyze GTM workspace data and write concise recommendations.",
|
||||
capabilities=[
|
||||
Memory(
|
||||
layout=MemoryLayoutConfig(
|
||||
memories_dir="memories/gtm",
|
||||
sessions_dir="sessions/gtm",
|
||||
)
|
||||
),
|
||||
Filesystem(),
|
||||
Shell(),
|
||||
],
|
||||
)
|
||||
|
||||
engineering_agent = SandboxAgent(
|
||||
name="Engineering reviewer",
|
||||
instructions="Inspect engineering workspaces and summarize fixes and risks.",
|
||||
capabilities=[
|
||||
Memory(
|
||||
layout=MemoryLayoutConfig(
|
||||
memories_dir="memories/engineering",
|
||||
sessions_dir="sessions/engineering",
|
||||
)
|
||||
),
|
||||
Filesystem(),
|
||||
Shell(),
|
||||
],
|
||||
)
|
||||
|
||||
gtm_session = SQLiteSession("gtm-q2-pipeline-review")
|
||||
engineering_session = SQLiteSession("eng-invoice-test-fix")
|
||||
```
|
||||
|
||||
이렇게 하면 GTM 분석이 엔지니어링 버그 수정 메모리로 통합되거나 그 반대가 되는 일을 방지할 수 있습니다.
|
||||
@@ -0,0 +1,117 @@
|
||||
---
|
||||
search:
|
||||
exclude: true
|
||||
---
|
||||
# 빠른 시작
|
||||
|
||||
!!! warning "베타 기능"
|
||||
|
||||
샌드박스 에이전트는 베타 버전입니다. 정식 출시 전까지 API 세부 정보, 기본값, 지원 기능이 변경될 수 있으며, 시간이 지나면서 더 고급 기능이 추가될 수 있습니다.
|
||||
|
||||
최신 에이전트는 파일 시스템의 실제 파일을 다룰 수 있을 때 가장 효과적으로 작동합니다. Agents SDK의 **샌드박스 에이전트**는 모델이 대규모 문서 집합을 검색하고, 파일을 편집하고, 명령을 실행하고, 결과물을 생성하고, 저장된 샌드박스 상태에서 작업을 다시 이어갈 수 있는 영구 워크스페이스를 제공합니다.
|
||||
|
||||
SDK는 파일 스테이징, 파일 시스템 도구, 셸 액세스, 샌드박스 수명 주기, 스냅샷, 공급자별 연동 코드를 직접 구성하지 않아도 이러한 실행 하네스를 제공합니다. 기존 `Agent` 및 `Runner` 흐름을 유지하면서 워크스페이스용 `Manifest`, 샌드박스 네이티브 도구용 기능, 작업 실행 위치를 지정하는 `SandboxRunConfig`를 추가하면 됩니다.
|
||||
|
||||
## 사전 요구 사항
|
||||
|
||||
- Python 3.10 이상
|
||||
- OpenAI Agents SDK에 대한 기본적인 이해
|
||||
- 샌드박스 클라이언트. 로컬 개발의 경우 `UnixLocalSandboxClient`로 시작합니다.
|
||||
|
||||
## 설치
|
||||
|
||||
SDK를 아직 설치하지 않았다면 다음을 실행합니다.
|
||||
|
||||
```bash
|
||||
pip install openai-agents
|
||||
```
|
||||
|
||||
Docker 기반 샌드박스의 경우:
|
||||
|
||||
```bash
|
||||
pip install "openai-agents[docker]"
|
||||
```
|
||||
|
||||
## 로컬 샌드박스 에이전트 생성
|
||||
|
||||
이 예제는 로컬 저장소를 `repo/` 아래에 스테이징하고, 로컬 스킬을 지연 로드하며, 러너가 실행을 위한 Unix 로컬 샌드박스 세션을 생성하도록 합니다.
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
from pathlib import Path
|
||||
|
||||
from agents import Runner
|
||||
from agents.run import RunConfig
|
||||
from agents.sandbox import Manifest, SandboxAgent, SandboxRunConfig
|
||||
from agents.sandbox.capabilities import Capabilities, LocalDirLazySkillSource, Skills
|
||||
from agents.sandbox.entries import LocalDir
|
||||
from agents.sandbox.sandboxes.unix_local import UnixLocalSandboxClient
|
||||
|
||||
EXAMPLE_DIR = Path(__file__).resolve().parent
|
||||
HOST_REPO_DIR = EXAMPLE_DIR / "repo"
|
||||
HOST_SKILLS_DIR = EXAMPLE_DIR / "skills"
|
||||
|
||||
|
||||
def build_agent(model: str) -> SandboxAgent[None]:
|
||||
return SandboxAgent(
|
||||
name="Sandbox engineer",
|
||||
model=model,
|
||||
instructions=(
|
||||
"Read `repo/task.md` before editing files. Stay grounded in the repository, preserve "
|
||||
"existing behavior, and mention the exact verification command you ran. "
|
||||
"If you edit files with apply_patch, paths are relative to the sandbox workspace root."
|
||||
),
|
||||
default_manifest=Manifest(
|
||||
entries={
|
||||
"repo": LocalDir(src=HOST_REPO_DIR),
|
||||
}
|
||||
),
|
||||
capabilities=Capabilities.default() + [
|
||||
Skills(
|
||||
lazy_from=LocalDirLazySkillSource(
|
||||
# This is a host path read by the SDK process.
|
||||
# Requested skills are copied into `skills_path` in the sandbox.
|
||||
source=LocalDir(src=HOST_SKILLS_DIR),
|
||||
)
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
result = await Runner.run(
|
||||
build_agent("gpt-5.6-sol"),
|
||||
"Open `repo/task.md`, fix the issue, run the targeted test, and summarize the change.",
|
||||
run_config=RunConfig(
|
||||
sandbox=SandboxRunConfig(client=UnixLocalSandboxClient()),
|
||||
workflow_name="Sandbox coding example",
|
||||
),
|
||||
)
|
||||
print(result.final_output)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
[examples/sandbox/docs/coding_task.py](https://github.com/openai/openai-agents-python/blob/main/examples/sandbox/docs/coding_task.py)를 참고하세요. 이 예제는 Unix 로컬 실행에서 결정론적으로 검증할 수 있도록 셸 기반의 작은 저장소를 사용합니다.
|
||||
|
||||
## 핵심 선택 사항
|
||||
|
||||
기본 실행이 정상적으로 작동한 후 일반적으로 고려하는 선택 사항은 다음과 같습니다.
|
||||
|
||||
- `default_manifest`: 새로운 샌드박스 세션에 사용할 파일, 저장소, 디렉터리, 마운트
|
||||
- `instructions`: 여러 프롬프트에 공통으로 적용할 간단한 워크플로 규칙
|
||||
- `base_instructions`: SDK 샌드박스 프롬프트를 대체하기 위한 고급 확장 수단
|
||||
- `capabilities`: 파일 시스템 편집/이미지 검사, 셸, 스킬, 메모리, 압축과 같은 샌드박스 네이티브 도구
|
||||
- `run_as`: 모델이 사용하는 도구의 샌드박스 사용자 ID
|
||||
- `SandboxRunConfig.client`: 샌드박스 백엔드
|
||||
- `SandboxRunConfig.session`, `session_state`, 또는 `snapshot`: 후속 실행이 이전 작업에 다시 연결되는 방식
|
||||
|
||||
## 다음 단계
|
||||
|
||||
- [개념](sandbox/guide.md): 매니페스트, 기능, 권한, 스냅샷, 실행 구성, 구성 패턴을 이해합니다.
|
||||
- [샌드박스 클라이언트](sandbox/clients.md): Unix 로컬, Docker, 호스팅 공급자 및 마운트 전략을 선택합니다.
|
||||
- [에이전트 메모리](sandbox/memory.md): 이전 샌드박스 실행에서 얻은 내용을 보존하고 재사용합니다.
|
||||
|
||||
셸 액세스가 가끔 사용하는 도구 중 하나일 뿐이라면 [도구 가이드](tools.md)의 호스팅 셸로 시작하세요. 워크스페이스 격리, 샌드박스 클라이언트 선택 또는 샌드박스 세션 재개 동작이 설계의 일부라면 샌드박스 에이전트를 사용하세요.
|
||||
@@ -0,0 +1,460 @@
|
||||
---
|
||||
search:
|
||||
exclude: true
|
||||
---
|
||||
# 세션
|
||||
|
||||
Agents SDK는 여러 에이전트 실행(run) 간 대화 기록을 자동으로 유지하는 내장 세션 메모리를 제공합니다. 이를 통해 턴 사이에 `.to_input_list()`를 수동으로 처리할 필요가 없습니다.
|
||||
|
||||
세션은 특정 세션의 대화 기록을 저장하여, 에이전트가 명시적인 수동 메모리 관리 없이도 컨텍스트를 유지할 수 있도록 합니다. 이는 이전 상호작용을 기억해야 하는 채팅 애플리케이션 또는 멀티 턴 대화를 구축할 때 특히 유용합니다.
|
||||
|
||||
## 빠른 시작
|
||||
|
||||
```python
|
||||
from agents import Agent, Runner, SQLiteSession
|
||||
|
||||
# Create agent
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
instructions="Reply very concisely.",
|
||||
)
|
||||
|
||||
# Create a session instance with a session ID
|
||||
session = SQLiteSession("conversation_123")
|
||||
|
||||
# First turn
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
"What city is the Golden Gate Bridge in?",
|
||||
session=session
|
||||
)
|
||||
print(result.final_output) # "San Francisco"
|
||||
|
||||
# Second turn - agent automatically remembers previous context
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
"What state is it in?",
|
||||
session=session
|
||||
)
|
||||
print(result.final_output) # "California"
|
||||
|
||||
# Also works with synchronous runner
|
||||
result = Runner.run_sync(
|
||||
agent,
|
||||
"What's the population?",
|
||||
session=session
|
||||
)
|
||||
print(result.final_output) # "Approximately 39 million"
|
||||
```
|
||||
|
||||
## 동작 방식
|
||||
|
||||
세션 메모리가 활성화되면:
|
||||
|
||||
1. **각 실행 전**: 러너가 세션의 대화 기록을 자동으로 가져와 입력 항목 앞에 추가합니다
|
||||
2. **각 실행 후**: 실행 중 생성된 모든 새 항목(사용자 입력, 어시스턴트 응답, 도구 호출 등)이 자동으로 세션에 저장됩니다
|
||||
3. **컨텍스트 보존**: 동일한 세션으로 이어지는 이후 실행에는 전체 대화 기록이 포함되어 에이전트가 컨텍스트를 유지할 수 있습니다
|
||||
|
||||
이를 통해 `.to_input_list()`를 수동으로 호출하고 실행 간 대화 상태를 관리할 필요가 없어집니다.
|
||||
|
||||
## 메모리 작업
|
||||
|
||||
### 기본 작업
|
||||
|
||||
세션은 대화 기록 관리를 위한 여러 작업을 지원합니다:
|
||||
|
||||
```python
|
||||
from agents import SQLiteSession
|
||||
|
||||
session = SQLiteSession("user_123", "conversations.db")
|
||||
|
||||
# Get all items in a session
|
||||
items = await session.get_items()
|
||||
|
||||
# Add new items to a session
|
||||
new_items = [
|
||||
{"role": "user", "content": "Hello"},
|
||||
{"role": "assistant", "content": "Hi there!"}
|
||||
]
|
||||
await session.add_items(new_items)
|
||||
|
||||
# Remove and return the most recent item
|
||||
last_item = await session.pop_item()
|
||||
print(last_item) # {"role": "assistant", "content": "Hi there!"}
|
||||
|
||||
# Clear all items from a session
|
||||
await session.clear_session()
|
||||
```
|
||||
|
||||
### 수정 시 pop_item 사용
|
||||
|
||||
`pop_item` 메서드는 대화에서 마지막 항목을 취소하거나 수정하고 싶을 때 특히 유용합니다:
|
||||
|
||||
```python
|
||||
from agents import Agent, Runner, SQLiteSession
|
||||
|
||||
agent = Agent(name="Assistant")
|
||||
session = SQLiteSession("correction_example")
|
||||
|
||||
# Initial conversation
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
"What's 2 + 2?",
|
||||
session=session
|
||||
)
|
||||
print(f"Agent: {result.final_output}")
|
||||
|
||||
# User wants to correct their question
|
||||
assistant_item = await session.pop_item() # Remove agent's response
|
||||
user_item = await session.pop_item() # Remove user's question
|
||||
|
||||
# Ask a corrected question
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
"What's 2 + 3?",
|
||||
session=session
|
||||
)
|
||||
print(f"Agent: {result.final_output}")
|
||||
```
|
||||
|
||||
## 메모리 옵션
|
||||
|
||||
### 메모리 없음(기본값)
|
||||
|
||||
```python
|
||||
# Default behavior - no session memory
|
||||
result = await Runner.run(agent, "Hello")
|
||||
```
|
||||
|
||||
### OpenAI Conversations API 메모리
|
||||
|
||||
자체 데이터베이스를 관리하지 않고
|
||||
[대화 상태](https://platform.openai.com/docs/guides/conversation-state?api-mode=responses#using-the-conversations-api)를 지속하려면 [OpenAI Conversations API](https://platform.openai.com/docs/api-reference/conversations/create)를 사용하세요. 이는 대화 기록 저장을 위해 OpenAI 호스트하는 인프라에 이미 의존하는 경우에 유용합니다.
|
||||
|
||||
```python
|
||||
from agents import OpenAIConversationsSession
|
||||
|
||||
session = OpenAIConversationsSession()
|
||||
|
||||
# Optionally resume a previous conversation by passing a conversation ID
|
||||
# session = OpenAIConversationsSession(conversation_id="conv_123")
|
||||
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
"Hello",
|
||||
session=session,
|
||||
)
|
||||
```
|
||||
|
||||
### SQLite 메모리
|
||||
|
||||
```python
|
||||
from agents import SQLiteSession
|
||||
|
||||
# In-memory database (lost when process ends)
|
||||
session = SQLiteSession("user_123")
|
||||
|
||||
# Persistent file-based database
|
||||
session = SQLiteSession("user_123", "conversations.db")
|
||||
|
||||
# Use the session
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
"Hello",
|
||||
session=session
|
||||
)
|
||||
```
|
||||
|
||||
### 다중 세션
|
||||
|
||||
```python
|
||||
from agents import Agent, Runner, SQLiteSession
|
||||
|
||||
agent = Agent(name="Assistant")
|
||||
|
||||
# Different sessions maintain separate conversation histories
|
||||
session_1 = SQLiteSession("user_123", "conversations.db")
|
||||
session_2 = SQLiteSession("user_456", "conversations.db")
|
||||
|
||||
result1 = await Runner.run(
|
||||
agent,
|
||||
"Hello",
|
||||
session=session_1
|
||||
)
|
||||
result2 = await Runner.run(
|
||||
agent,
|
||||
"Hello",
|
||||
session=session_2
|
||||
)
|
||||
```
|
||||
|
||||
### SQLAlchemy 기반 세션
|
||||
|
||||
더 고급 사용 사례의 경우, SQLAlchemy 기반 세션 백엔드를 사용할 수 있습니다. 이를 통해 SQLAlchemy가 지원하는 모든 데이터베이스(PostgreSQL, MySQL, SQLite 등)를 세션 저장소로 사용할 수 있습니다.
|
||||
|
||||
**예시 1: 메모리 내 SQLite와 `from_url` 사용**
|
||||
|
||||
개발 및 테스트에 적합한 가장 간단한 시작 방법입니다.
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
from agents import Agent, Runner
|
||||
from agents.extensions.memory.sqlalchemy_session import SQLAlchemySession
|
||||
|
||||
async def main():
|
||||
agent = Agent("Assistant")
|
||||
session = SQLAlchemySession.from_url(
|
||||
"user-123",
|
||||
url="sqlite+aiosqlite:///:memory:",
|
||||
create_tables=True, # Auto-create tables for the demo
|
||||
)
|
||||
|
||||
result = await Runner.run(agent, "Hello", session=session)
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
**예시 2: 기존 SQLAlchemy 엔진 사용**
|
||||
|
||||
프로덕션 애플리케이션에서는 이미 SQLAlchemy `AsyncEngine` 인스턴스를 가지고 있을 수 있습니다. 이를 세션에 직접 전달할 수 있습니다.
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
from agents import Agent, Runner
|
||||
from agents.extensions.memory.sqlalchemy_session import SQLAlchemySession
|
||||
from sqlalchemy.ext.asyncio import create_async_engine
|
||||
|
||||
async def main():
|
||||
# In your application, you would use your existing engine
|
||||
engine = create_async_engine("sqlite+aiosqlite:///conversations.db")
|
||||
|
||||
agent = Agent("Assistant")
|
||||
session = SQLAlchemySession(
|
||||
"user-456",
|
||||
engine=engine,
|
||||
create_tables=True, # Auto-create tables for the demo
|
||||
)
|
||||
|
||||
result = await Runner.run(agent, "Hello", session=session)
|
||||
print(result.final_output)
|
||||
|
||||
await engine.dispose()
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
### 암호화된 세션
|
||||
|
||||
보관 중인 대화 데이터를 암호화해야 하는 애플리케이션의 경우, `EncryptedSession`을 사용해 투명한 암호화와 자동 TTL 기반 만료로 어떤 세션 백엔드든 래핑할 수 있습니다. `encrypt` extra가 필요합니다: `pip install openai-agents[encrypt]`.
|
||||
|
||||
`EncryptedSession`은 세션별 키 유도(HKDF)를 사용하는 Fernet 암호화를 사용하며, 오래된 메시지의 자동 만료를 지원합니다. 항목이 TTL을 초과하면 검색 시 조용히 건너뜁니다.
|
||||
|
||||
**예시: SQLAlchemy 세션 데이터 암호화**
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
from agents import Agent, Runner
|
||||
from agents.extensions.memory import EncryptedSession, SQLAlchemySession
|
||||
|
||||
async def main():
|
||||
# Create underlying session (works with any SessionABC implementation)
|
||||
underlying_session = SQLAlchemySession.from_url(
|
||||
session_id="user-123",
|
||||
url="postgresql+asyncpg://app:secret@db.example.com/agents",
|
||||
create_tables=True,
|
||||
)
|
||||
|
||||
# Wrap with encryption and TTL-based expiration
|
||||
session = EncryptedSession(
|
||||
session_id="user-123",
|
||||
underlying_session=underlying_session,
|
||||
encryption_key="your-encryption-key", # Use a secure key from your secrets management
|
||||
ttl=600, # 10 minutes - items older than this are silently skipped
|
||||
)
|
||||
|
||||
agent = Agent("Assistant")
|
||||
result = await Runner.run(agent, "Hello", session=session)
|
||||
print(result.final_output)
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
**주요 기능:**
|
||||
|
||||
- **투명한 암호화**: 저장 전 모든 세션 항목을 자동으로 암호화하고, 검색 시 복호화
|
||||
- **세션별 키 유도**: 세션 ID를 솔트로 사용하는 HKDF로 고유한 암호화 키 생성
|
||||
- **TTL 기반 만료**: 구성 가능한 TTL(기본값: 10분)에 따라 오래된 메시지를 자동 만료
|
||||
- **유연한 키 입력**: Fernet 키 또는 원문 문자열을 암호화 키로 허용
|
||||
- **어떤 세션이든 래핑**: SQLite, SQLAlchemy 또는 커스텀 세션 구현과 호환
|
||||
|
||||
!!! warning "중요한 보안 참고"
|
||||
|
||||
- 암호화 키를 안전하게 저장하세요(예: 환경 변수, 시크릿 매니저)
|
||||
- 만료된 토큰은 애플리케이션 서버의 시스템 시계를 기준으로 거부됩니다 - 유효한 토큰이 시계 드리프트로 인해 거부되지 않도록 모든 서버가 NTP로 시간 동기화되어 있는지 확인하세요
|
||||
- 기본 세션은 여전히 암호화된 데이터를 저장하므로 데이터베이스 인프라에 대한 제어권을 유지합니다
|
||||
|
||||
|
||||
## 커스텀 메모리 구현
|
||||
|
||||
[`Session`][agents.memory.session.Session] 프로토콜을 따르는 클래스를 생성하여 자체 세션 메모리를 구현할 수 있습니다:
|
||||
|
||||
```python
|
||||
from agents.memory.session import SessionABC
|
||||
from agents.items import TResponseInputItem
|
||||
from typing import List
|
||||
|
||||
class MyCustomSession(SessionABC):
|
||||
"""Custom session implementation following the Session protocol."""
|
||||
|
||||
def __init__(self, session_id: str):
|
||||
self.session_id = session_id
|
||||
# Your initialization here
|
||||
|
||||
async def get_items(self, limit: int | None = None) -> List[TResponseInputItem]:
|
||||
"""Retrieve conversation history for this session."""
|
||||
# Your implementation here
|
||||
pass
|
||||
|
||||
async def add_items(self, items: List[TResponseInputItem]) -> None:
|
||||
"""Store new items for this session."""
|
||||
# Your implementation here
|
||||
pass
|
||||
|
||||
async def pop_item(self) -> TResponseInputItem | None:
|
||||
"""Remove and return the most recent item from this session."""
|
||||
# Your implementation here
|
||||
pass
|
||||
|
||||
async def clear_session(self) -> None:
|
||||
"""Clear all items for this session."""
|
||||
# Your implementation here
|
||||
pass
|
||||
|
||||
# Use your custom session
|
||||
agent = Agent(name="Assistant")
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
"Hello",
|
||||
session=MyCustomSession("my_session")
|
||||
)
|
||||
```
|
||||
|
||||
## 세션 관리
|
||||
|
||||
### 세션 ID 네이밍
|
||||
|
||||
대화를 체계적으로 구성할 수 있는 의미 있는 세션 ID를 사용하세요:
|
||||
|
||||
- 사용자 기반: `"user_12345"`
|
||||
- 스레드 기반: `"thread_abc123"`
|
||||
- 컨텍스트 기반: `"support_ticket_456"`
|
||||
|
||||
### 메모리 지속성
|
||||
|
||||
- 임시 대화에는 메모리 내 SQLite(`SQLiteSession("session_id")`) 사용
|
||||
- 지속형 대화에는 파일 기반 SQLite(`SQLiteSession("session_id", "path/to/db.sqlite")`) 사용
|
||||
- SQLAlchemy가 지원하는 기존 데이터베이스가 있는 프로덕션 시스템에는 SQLAlchemy 기반 세션(`SQLAlchemySession("session_id", engine=engine, create_tables=True)`) 사용
|
||||
- 기록을 OpenAI Conversations API에 저장하기를 원하면 OpenAI 호스트하는 스토리지(`OpenAIConversationsSession()`) 사용
|
||||
- 투명한 암호화와 TTL 기반 만료를 위해 어떤 세션이든 래핑하려면 암호화된 세션(`EncryptedSession(session_id, underlying_session, encryption_key)`) 사용
|
||||
- 더 고급 사용 사례를 위해 다른 프로덕션 시스템(Redis, Django 등)에 대한 커스텀 세션 백엔드 구현 고려
|
||||
|
||||
### 세션 관리
|
||||
|
||||
```python
|
||||
# Clear a session when conversation should start fresh
|
||||
await session.clear_session()
|
||||
|
||||
# Different agents can share the same session
|
||||
support_agent = Agent(name="Support")
|
||||
billing_agent = Agent(name="Billing")
|
||||
session = SQLiteSession("user_123")
|
||||
|
||||
# Both agents will see the same conversation history
|
||||
result1 = await Runner.run(
|
||||
support_agent,
|
||||
"Help me with my account",
|
||||
session=session
|
||||
)
|
||||
result2 = await Runner.run(
|
||||
billing_agent,
|
||||
"What are my charges?",
|
||||
session=session
|
||||
)
|
||||
```
|
||||
|
||||
## 전체 예시
|
||||
|
||||
다음은 세션 메모리가 작동하는 방식을 보여주는 전체 예시입니다:
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
from agents import Agent, Runner, SQLiteSession
|
||||
|
||||
|
||||
async def main():
|
||||
# Create an agent
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
instructions="Reply very concisely.",
|
||||
)
|
||||
|
||||
# Create a session instance that will persist across runs
|
||||
session = SQLiteSession("conversation_123", "conversation_history.db")
|
||||
|
||||
print("=== Sessions Example ===")
|
||||
print("The agent will remember previous messages automatically.\n")
|
||||
|
||||
# First turn
|
||||
print("First turn:")
|
||||
print("User: What city is the Golden Gate Bridge in?")
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
"What city is the Golden Gate Bridge in?",
|
||||
session=session
|
||||
)
|
||||
print(f"Assistant: {result.final_output}")
|
||||
print()
|
||||
|
||||
# Second turn - the agent will remember the previous conversation
|
||||
print("Second turn:")
|
||||
print("User: What state is it in?")
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
"What state is it in?",
|
||||
session=session
|
||||
)
|
||||
print(f"Assistant: {result.final_output}")
|
||||
print()
|
||||
|
||||
# Third turn - continuing the conversation
|
||||
print("Third turn:")
|
||||
print("User: What's the population of that state?")
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
"What's the population of that state?",
|
||||
session=session
|
||||
)
|
||||
print(f"Assistant: {result.final_output}")
|
||||
print()
|
||||
|
||||
print("=== Conversation Complete ===")
|
||||
print("Notice how the agent remembered the context from previous turns!")
|
||||
print("Sessions automatically handles conversation history.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
## API 레퍼런스
|
||||
|
||||
자세한 API 문서는 다음을 참고하세요:
|
||||
|
||||
- [`Session`][agents.memory.Session] - 프로토콜 인터페이스
|
||||
- [`SQLiteSession`][agents.memory.SQLiteSession] - SQLite 구현
|
||||
- [`OpenAIConversationsSession`](ref/memory/openai_conversations_session.md) - OpenAI Conversations API 구현
|
||||
- [`SQLAlchemySession`][agents.extensions.memory.sqlalchemy_session.SQLAlchemySession] - SQLAlchemy 기반 구현
|
||||
- [`EncryptedSession`][agents.extensions.memory.encrypt_session.EncryptedSession] - TTL이 포함된 암호화 세션 래퍼
|
||||
@@ -0,0 +1,307 @@
|
||||
---
|
||||
search:
|
||||
exclude: true
|
||||
---
|
||||
# 고급 SQLite 세션
|
||||
|
||||
`AdvancedSQLiteSession`은 기본 `SQLiteSession`의 향상된 버전으로, 대화 분기, 상세 사용량 분석, 구조화된 대화 쿼리 등 고급 대화 관리 기능을 제공합니다.
|
||||
|
||||
## 기능
|
||||
|
||||
- **대화 분기**: 모든 사용자 메시지에서 대체 대화 경로를 생성
|
||||
- **사용량 추적**: 전체 JSON 세부 내역과 함께 턴별 상세 토큰 사용량 분석
|
||||
- **구조화된 쿼리**: 턴별 대화, 도구 사용 통계 등을 조회
|
||||
- **분기 관리**: 독립적인 분기 전환 및 관리
|
||||
- **메시지 구조 메타데이터**: 메시지 유형, 도구 사용, 대화 흐름 추적
|
||||
|
||||
## 빠른 시작
|
||||
|
||||
```python
|
||||
from agents import Agent, Runner
|
||||
from agents.extensions.memory import AdvancedSQLiteSession
|
||||
|
||||
# Create agent
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
instructions="Reply very concisely.",
|
||||
)
|
||||
|
||||
# Create an advanced session
|
||||
session = AdvancedSQLiteSession(
|
||||
session_id="conversation_123",
|
||||
db_path="conversations.db",
|
||||
create_tables=True
|
||||
)
|
||||
|
||||
# First conversation turn
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
"What city is the Golden Gate Bridge in?",
|
||||
session=session
|
||||
)
|
||||
print(result.final_output) # "San Francisco"
|
||||
|
||||
# IMPORTANT: Store usage data
|
||||
await session.store_run_usage(result)
|
||||
|
||||
# Continue conversation
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
"What state is it in?",
|
||||
session=session
|
||||
)
|
||||
print(result.final_output) # "California"
|
||||
await session.store_run_usage(result)
|
||||
```
|
||||
|
||||
## 초기화
|
||||
|
||||
```python
|
||||
from agents.extensions.memory import AdvancedSQLiteSession
|
||||
|
||||
# Basic initialization
|
||||
session = AdvancedSQLiteSession(
|
||||
session_id="my_conversation",
|
||||
create_tables=True # Auto-create advanced tables
|
||||
)
|
||||
|
||||
# With persistent storage
|
||||
session = AdvancedSQLiteSession(
|
||||
session_id="user_123",
|
||||
db_path="path/to/conversations.db",
|
||||
create_tables=True
|
||||
)
|
||||
|
||||
# With custom logger
|
||||
import logging
|
||||
logger = logging.getLogger("my_app")
|
||||
session = AdvancedSQLiteSession(
|
||||
session_id="session_456",
|
||||
create_tables=True,
|
||||
logger=logger
|
||||
)
|
||||
```
|
||||
|
||||
### 매개변수
|
||||
|
||||
- `session_id` (str): 대화 세션의 고유 식별자
|
||||
- `db_path` (str | Path): SQLite 데이터베이스 파일 경로. 인메모리 저장소의 경우 기본값은 `:memory:`
|
||||
- `create_tables` (bool): 고급 테이블을 자동으로 생성할지 여부. 기본값은 `False`
|
||||
- `logger` (logging.Logger | None): 세션용 사용자 지정 로거. 기본값은 모듈 로거
|
||||
|
||||
## 사용량 추적
|
||||
|
||||
AdvancedSQLiteSession은 대화 턴별 토큰 사용량 데이터를 저장하여 상세한 사용량 분석을 제공합니다. **이는 각 에이전트 실행 후 `store_run_usage` 메서드가 호출되는지에 전적으로 의존합니다.**
|
||||
|
||||
### 사용량 데이터 저장
|
||||
|
||||
```python
|
||||
# After each agent run, store the usage data
|
||||
result = await Runner.run(agent, "Hello", session=session)
|
||||
await session.store_run_usage(result)
|
||||
|
||||
# This stores:
|
||||
# - Total tokens used
|
||||
# - Input/output token breakdown
|
||||
# - Request count
|
||||
# - Detailed JSON token information (if available)
|
||||
```
|
||||
|
||||
### 사용량 통계 조회
|
||||
|
||||
```python
|
||||
# Get session-level usage (all branches)
|
||||
session_usage = await session.get_session_usage()
|
||||
if session_usage:
|
||||
print(f"Total requests: {session_usage['requests']}")
|
||||
print(f"Total tokens: {session_usage['total_tokens']}")
|
||||
print(f"Input tokens: {session_usage['input_tokens']}")
|
||||
print(f"Output tokens: {session_usage['output_tokens']}")
|
||||
print(f"Total turns: {session_usage['total_turns']}")
|
||||
|
||||
# Get usage for specific branch
|
||||
branch_usage = await session.get_session_usage(branch_id="main")
|
||||
|
||||
# Get usage by turn
|
||||
turn_usage = await session.get_turn_usage()
|
||||
for turn_data in turn_usage:
|
||||
print(f"Turn {turn_data['user_turn_number']}: {turn_data['total_tokens']} tokens")
|
||||
if turn_data['input_tokens_details']:
|
||||
print(f" Input details: {turn_data['input_tokens_details']}")
|
||||
if turn_data['output_tokens_details']:
|
||||
print(f" Output details: {turn_data['output_tokens_details']}")
|
||||
|
||||
# Get usage for specific turn
|
||||
turn_2_usage = await session.get_turn_usage(user_turn_number=2)
|
||||
```
|
||||
|
||||
## 대화 분기
|
||||
|
||||
AdvancedSQLiteSession의 핵심 기능 중 하나는 모든 사용자 메시지에서 대화 분기를 생성하여 대체 대화 경로를 탐색할 수 있는 기능입니다.
|
||||
|
||||
### 분기 생성
|
||||
|
||||
```python
|
||||
# Get available turns for branching
|
||||
turns = await session.get_conversation_turns()
|
||||
for turn in turns:
|
||||
print(f"Turn {turn['turn']}: {turn['content']}")
|
||||
print(f"Can branch: {turn['can_branch']}")
|
||||
|
||||
# Create a branch from turn 2
|
||||
branch_id = await session.create_branch_from_turn(2)
|
||||
print(f"Created branch: {branch_id}")
|
||||
|
||||
# Create a branch with custom name
|
||||
branch_id = await session.create_branch_from_turn(
|
||||
2,
|
||||
branch_name="alternative_path"
|
||||
)
|
||||
|
||||
# Create branch by searching for content
|
||||
branch_id = await session.create_branch_from_content(
|
||||
"weather",
|
||||
branch_name="weather_focus"
|
||||
)
|
||||
```
|
||||
|
||||
### 분기 관리
|
||||
|
||||
```python
|
||||
# List all branches
|
||||
branches = await session.list_branches()
|
||||
for branch in branches:
|
||||
current = " (current)" if branch["is_current"] else ""
|
||||
print(f"{branch['branch_id']}: {branch['user_turns']} turns, {branch['message_count']} messages{current}")
|
||||
|
||||
# Switch between branches
|
||||
await session.switch_to_branch("main")
|
||||
await session.switch_to_branch(branch_id)
|
||||
|
||||
# Delete a branch
|
||||
await session.delete_branch(branch_id, force=True) # force=True allows deleting current branch
|
||||
```
|
||||
|
||||
### 분기 워크플로 예제
|
||||
|
||||
```python
|
||||
# Original conversation
|
||||
result = await Runner.run(agent, "What's the capital of France?", session=session)
|
||||
await session.store_run_usage(result)
|
||||
|
||||
result = await Runner.run(agent, "What's the weather like there?", session=session)
|
||||
await session.store_run_usage(result)
|
||||
|
||||
# Create branch from turn 2 (weather question)
|
||||
branch_id = await session.create_branch_from_turn(2, "weather_focus")
|
||||
|
||||
# Continue in new branch with different question
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
"What are the main tourist attractions in Paris?",
|
||||
session=session
|
||||
)
|
||||
await session.store_run_usage(result)
|
||||
|
||||
# Switch back to main branch
|
||||
await session.switch_to_branch("main")
|
||||
|
||||
# Continue original conversation
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
"How expensive is it to visit?",
|
||||
session=session
|
||||
)
|
||||
await session.store_run_usage(result)
|
||||
```
|
||||
|
||||
## 구조화된 쿼리
|
||||
|
||||
AdvancedSQLiteSession은 대화 구조와 내용을 분석하기 위한 여러 메서드를 제공합니다.
|
||||
|
||||
### 대화 분석
|
||||
|
||||
```python
|
||||
# Get conversation organized by turns
|
||||
conversation_by_turns = await session.get_conversation_by_turns()
|
||||
for turn_num, items in conversation_by_turns.items():
|
||||
print(f"Turn {turn_num}: {len(items)} items")
|
||||
for item in items:
|
||||
if item["tool_name"]:
|
||||
print(f" - {item['type']} (tool: {item['tool_name']})")
|
||||
else:
|
||||
print(f" - {item['type']}")
|
||||
|
||||
# Get tool usage statistics
|
||||
tool_usage = await session.get_tool_usage()
|
||||
for tool_name, count, turn in tool_usage:
|
||||
print(f"{tool_name}: used {count} times in turn {turn}")
|
||||
|
||||
# Find turns by content
|
||||
matching_turns = await session.find_turns_by_content("weather")
|
||||
for turn in matching_turns:
|
||||
print(f"Turn {turn['turn']}: {turn['content']}")
|
||||
```
|
||||
|
||||
### 메시지 구조
|
||||
|
||||
세션은 다음을 포함한 메시지 구조를 자동으로 추적합니다.
|
||||
|
||||
- 메시지 유형(사용자, 어시스턴트, tool_call 등)
|
||||
- 도구 호출의 도구 이름
|
||||
- 턴 번호 및 시퀀스 번호
|
||||
- 분기 연결
|
||||
- 타임스탬프
|
||||
|
||||
## 데이터베이스 스키마
|
||||
|
||||
AdvancedSQLiteSession은 두 개의 추가 테이블로 기본 SQLite 스키마를 확장합니다.
|
||||
|
||||
### message_structure 테이블
|
||||
|
||||
```sql
|
||||
CREATE TABLE message_structure (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
session_id TEXT NOT NULL,
|
||||
message_id INTEGER NOT NULL,
|
||||
branch_id TEXT NOT NULL DEFAULT 'main',
|
||||
message_type TEXT NOT NULL,
|
||||
sequence_number INTEGER NOT NULL,
|
||||
user_turn_number INTEGER,
|
||||
branch_turn_number INTEGER,
|
||||
tool_name TEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (session_id) REFERENCES agent_sessions(session_id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (message_id) REFERENCES agent_messages(id) ON DELETE CASCADE
|
||||
);
|
||||
```
|
||||
|
||||
### turn_usage 테이블
|
||||
|
||||
```sql
|
||||
CREATE TABLE turn_usage (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
session_id TEXT NOT NULL,
|
||||
branch_id TEXT NOT NULL DEFAULT 'main',
|
||||
user_turn_number INTEGER NOT NULL,
|
||||
requests INTEGER DEFAULT 0,
|
||||
input_tokens INTEGER DEFAULT 0,
|
||||
output_tokens INTEGER DEFAULT 0,
|
||||
total_tokens INTEGER DEFAULT 0,
|
||||
input_tokens_details JSON,
|
||||
output_tokens_details JSON,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (session_id) REFERENCES agent_sessions(session_id) ON DELETE CASCADE,
|
||||
UNIQUE(session_id, branch_id, user_turn_number)
|
||||
);
|
||||
```
|
||||
|
||||
## 전체 예제
|
||||
|
||||
모든 기능을 종합적으로 보여 주는 [전체 예제](https://github.com/openai/openai-agents-python/tree/main/examples/memory/advanced_sqlite_session_example.py)를 확인하세요.
|
||||
|
||||
|
||||
## API 참조
|
||||
|
||||
- [`AdvancedSQLiteSession`][agents.extensions.memory.advanced_sqlite_session.AdvancedSQLiteSession] - 메인 클래스
|
||||
- [`Session`][agents.memory.session.Session] - 기본 세션 프로토콜
|
||||
@@ -0,0 +1,179 @@
|
||||
---
|
||||
search:
|
||||
exclude: true
|
||||
---
|
||||
# 암호화된 세션
|
||||
|
||||
`EncryptedSession`은 모든 세션 구현에 투명한 암호화를 제공하여, 오래된 항목의 자동 만료와 함께 대화 데이터를 보호합니다.
|
||||
|
||||
## 기능
|
||||
|
||||
- **투명한 암호화**: 모든 세션을 Fernet 암호화로 래핑합니다
|
||||
- **세션별 키**: HKDF 키 파생을 사용하여 세션마다 고유한 암호화를 적용합니다
|
||||
- **자동 만료**: TTL이 만료되면 오래된 항목을 조용히 건너뜁니다
|
||||
- **드롭인 대체**: 기존의 모든 세션 구현과 함께 작동합니다
|
||||
|
||||
## 설치
|
||||
|
||||
암호화된 세션에는 `encrypt` extra가 필요합니다:
|
||||
|
||||
```bash
|
||||
pip install openai-agents[encrypt]
|
||||
```
|
||||
|
||||
## 빠른 시작
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
from agents import Agent, Runner
|
||||
from agents.extensions.memory import EncryptedSession, SQLAlchemySession
|
||||
|
||||
async def main():
|
||||
agent = Agent("Assistant")
|
||||
|
||||
# Create underlying session
|
||||
underlying_session = SQLAlchemySession.from_url(
|
||||
"user-123",
|
||||
url="sqlite+aiosqlite:///:memory:",
|
||||
create_tables=True
|
||||
)
|
||||
|
||||
# Wrap with encryption
|
||||
session = EncryptedSession(
|
||||
session_id="user-123",
|
||||
underlying_session=underlying_session,
|
||||
encryption_key="your-secret-key-here",
|
||||
ttl=600 # 10 minutes
|
||||
)
|
||||
|
||||
result = await Runner.run(agent, "Hello", session=session)
|
||||
print(result.final_output)
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
## 구성
|
||||
|
||||
### 암호화 키
|
||||
|
||||
암호화 키는 Fernet 키이거나 임의의 문자열일 수 있습니다:
|
||||
|
||||
```python
|
||||
from agents.extensions.memory import EncryptedSession
|
||||
|
||||
# Using a Fernet key (base64-encoded)
|
||||
session = EncryptedSession(
|
||||
session_id="user-123",
|
||||
underlying_session=underlying_session,
|
||||
encryption_key="your-fernet-key-here",
|
||||
ttl=600
|
||||
)
|
||||
|
||||
# Using a raw string (will be derived to a key)
|
||||
session = EncryptedSession(
|
||||
session_id="user-123",
|
||||
underlying_session=underlying_session,
|
||||
encryption_key="my-secret-password",
|
||||
ttl=600
|
||||
)
|
||||
```
|
||||
|
||||
### TTL(time to live)
|
||||
|
||||
암호화된 항목이 유효한 기간을 설정합니다:
|
||||
|
||||
```python
|
||||
# Items expire after 1 hour
|
||||
session = EncryptedSession(
|
||||
session_id="user-123",
|
||||
underlying_session=underlying_session,
|
||||
encryption_key="secret",
|
||||
ttl=3600 # 1 hour in seconds
|
||||
)
|
||||
|
||||
# Items expire after 1 day
|
||||
session = EncryptedSession(
|
||||
session_id="user-123",
|
||||
underlying_session=underlying_session,
|
||||
encryption_key="secret",
|
||||
ttl=86400 # 24 hours in seconds
|
||||
)
|
||||
```
|
||||
|
||||
## 다양한 세션 유형과 함께 사용
|
||||
|
||||
### SQLite 세션과 함께 사용
|
||||
|
||||
```python
|
||||
from agents import SQLiteSession
|
||||
from agents.extensions.memory import EncryptedSession
|
||||
|
||||
# Create encrypted SQLite session
|
||||
underlying = SQLiteSession("user-123", "conversations.db")
|
||||
|
||||
session = EncryptedSession(
|
||||
session_id="user-123",
|
||||
underlying_session=underlying,
|
||||
encryption_key="secret-key"
|
||||
)
|
||||
```
|
||||
|
||||
### SQLAlchemy 세션과 함께 사용
|
||||
|
||||
```python
|
||||
from agents.extensions.memory import EncryptedSession, SQLAlchemySession
|
||||
|
||||
# Create encrypted SQLAlchemy session
|
||||
underlying = SQLAlchemySession.from_url(
|
||||
"user-123",
|
||||
url="postgresql+asyncpg://user:pass@localhost/db",
|
||||
create_tables=True
|
||||
)
|
||||
|
||||
session = EncryptedSession(
|
||||
session_id="user-123",
|
||||
underlying_session=underlying,
|
||||
encryption_key="secret-key"
|
||||
)
|
||||
```
|
||||
|
||||
!!! warning "고급 세션 기능"
|
||||
|
||||
`EncryptedSession`을 `AdvancedSQLiteSession` 같은 고급 세션 구현과 함께 사용할 때는 다음 사항에 유의하세요:
|
||||
|
||||
- `find_turns_by_content()` 같은 메서드는 메시지 콘텐츠가 암호화되어 있으므로 효과적으로 작동하지 않습니다
|
||||
- 콘텐츠 기반 검색은 암호화된 데이터에 대해 동작하므로 효과가 제한됩니다
|
||||
|
||||
|
||||
|
||||
## 키 파생
|
||||
|
||||
EncryptedSession은 HKDF(HMAC-based Key Derivation Function)를 사용하여 세션별로 고유한 암호화 키를 파생합니다:
|
||||
|
||||
- **마스터 키**: 제공한 암호화 키
|
||||
- **세션 솔트**: 세션 ID
|
||||
- **정보 문자열**: `"agents.session-store.hkdf.v1"`
|
||||
- **출력**: 32바이트 Fernet 키
|
||||
|
||||
이를 통해 다음이 보장됩니다:
|
||||
- 각 세션에는 고유한 암호화 키가 있습니다
|
||||
- 마스터 키 없이는 키를 파생할 수 없습니다
|
||||
- 서로 다른 세션 간에는 세션 데이터를 복호화할 수 없습니다
|
||||
|
||||
## 자동 만료
|
||||
|
||||
항목이 TTL을 초과하면 조회 중 자동으로 건너뜁니다:
|
||||
|
||||
```python
|
||||
# Items older than TTL are silently ignored
|
||||
items = await session.get_items() # Only returns non-expired items
|
||||
|
||||
# Expired items don't affect session behavior
|
||||
result = await Runner.run(agent, "Continue conversation", session=session)
|
||||
```
|
||||
|
||||
## API 참조
|
||||
|
||||
- [`EncryptedSession`][agents.extensions.memory.encrypt_session.EncryptedSession] - 기본 클래스
|
||||
- [`Session`][agents.memory.session.Session] - 기본 세션 프로토콜
|
||||
@@ -0,0 +1,711 @@
|
||||
---
|
||||
search:
|
||||
exclude: true
|
||||
---
|
||||
# 세션
|
||||
|
||||
Agents SDK는 여러 에이전트 실행 간 대화 기록을 자동으로 유지하는 기본 제공 세션 메모리를 제공하여, 턴 사이에 `.to_input_list()` 를 수동으로 처리할 필요를 없애줍니다.
|
||||
|
||||
세션은 특정 세션의 대화 기록을 저장하므로, 명시적인 수동 메모리 관리 없이도 에이전트가 컨텍스트를 유지할 수 있습니다. 이는 에이전트가 이전 상호작용을 기억해야 하는 채팅 애플리케이션이나 멀티턴 대화를 구축할 때 특히 유용합니다.
|
||||
|
||||
SDK가 클라이언트 측 메모리를 관리해 주기를 원할 때 세션을 사용하세요. 세션은 동일한 실행에서 `conversation_id`, `previous_response_id` 또는 `auto_previous_response_id` 와 함께 사용할 수 없습니다. OpenAI 서버 관리형 이어가기를 원한다면 세션을 그 위에 겹쳐 사용하지 말고 해당 메커니즘 중 하나를 선택하세요.
|
||||
|
||||
## 빠른 시작
|
||||
|
||||
```python
|
||||
from agents import Agent, Runner, SQLiteSession
|
||||
|
||||
# Create agent
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
instructions="Reply very concisely.",
|
||||
)
|
||||
|
||||
# Create a session instance with a session ID
|
||||
session = SQLiteSession("conversation_123")
|
||||
|
||||
# First turn
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
"What city is the Golden Gate Bridge in?",
|
||||
session=session
|
||||
)
|
||||
print(result.final_output) # "San Francisco"
|
||||
|
||||
# Second turn - agent automatically remembers previous context
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
"What state is it in?",
|
||||
session=session
|
||||
)
|
||||
print(result.final_output) # "California"
|
||||
|
||||
# Also works with synchronous runner
|
||||
result = Runner.run_sync(
|
||||
agent,
|
||||
"What's the population?",
|
||||
session=session
|
||||
)
|
||||
print(result.final_output) # "Approximately 39 million"
|
||||
```
|
||||
|
||||
## 동일한 세션으로 인터럽트된 실행 재개
|
||||
|
||||
승인을 위해 실행이 일시 중지되는 경우, 재개된 턴이 동일한 저장된 대화 기록을 이어가도록 같은 세션 인스턴스(또는 같은 기반 저장소를 가리키는 다른 세션 인스턴스)로 재개하세요.
|
||||
|
||||
```python
|
||||
result = await Runner.run(agent, "Delete temporary files that are no longer needed.", session=session)
|
||||
|
||||
if result.interruptions:
|
||||
state = result.to_state()
|
||||
for interruption in result.interruptions:
|
||||
state.approve(interruption)
|
||||
result = await Runner.run(agent, state, session=session)
|
||||
```
|
||||
|
||||
## 핵심 세션 동작
|
||||
|
||||
세션 메모리가 활성화되면 다음과 같이 동작합니다.
|
||||
|
||||
1. **각 실행 전**: 러너가 세션의 대화 기록을 자동으로 조회하여 입력 항목 앞에 추가합니다.
|
||||
2. **각 실행 후**: 실행 중 생성된 모든 새 항목(사용자 입력, 어시스턴트 응답, 도구 호출 등)이 세션에 자동으로 저장됩니다.
|
||||
3. **컨텍스트 보존**: 동일한 세션으로 이어지는 각 실행에는 전체 대화 기록이 포함되어 에이전트가 컨텍스트를 유지할 수 있습니다.
|
||||
|
||||
이를 통해 `.to_input_list()` 를 수동으로 호출하고 실행 간 대화 상태를 관리할 필요가 없어집니다.
|
||||
|
||||
## 기록과 새 입력 병합 제어
|
||||
|
||||
세션을 전달하면 러너는 일반적으로 모델 입력을 다음과 같이 준비합니다.
|
||||
|
||||
1. 세션 기록(`session.get_items(...)` 에서 조회)
|
||||
2. 새 턴 입력
|
||||
|
||||
모델 호출 전에 이 병합 단계를 사용자 지정하려면 [`RunConfig.session_input_callback`][agents.run.RunConfig.session_input_callback] 을 사용하세요. 콜백은 두 개의 목록을 받습니다.
|
||||
|
||||
- `history`: 조회된 세션 기록(이미 입력 항목 형식으로 정규화됨)
|
||||
- `new_input`: 현재 턴의 새 입력 항목
|
||||
|
||||
모델에 전송할 최종 입력 항목 목록을 반환하세요.
|
||||
|
||||
콜백은 두 목록의 복사본을 받으므로 안전하게 변경할 수 있습니다. 반환된 목록은 해당 턴의 모델 입력을 제어하지만, SDK는 여전히 새 턴에 속한 항목만 지속 저장합니다. 따라서 이전 기록을 재정렬하거나 필터링해도 이전 세션 항목이 새 입력으로 다시 저장되지는 않습니다.
|
||||
|
||||
```python
|
||||
from agents import Agent, RunConfig, Runner, SQLiteSession
|
||||
|
||||
|
||||
def keep_recent_history(history, new_input):
|
||||
# Keep only the last 10 history items, then append the new turn.
|
||||
return history[-10:] + new_input
|
||||
|
||||
|
||||
agent = Agent(name="Assistant")
|
||||
session = SQLiteSession("conversation_123")
|
||||
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
"Continue from the latest updates only.",
|
||||
session=session,
|
||||
run_config=RunConfig(session_input_callback=keep_recent_history),
|
||||
)
|
||||
```
|
||||
|
||||
세션이 항목을 저장하는 방식은 바꾸지 않으면서 기록에 대한 사용자 지정 가지치기, 재정렬 또는 선택적 포함이 필요할 때 사용하세요. 모델 호출 직전에 더 늦은 최종 처리 단계가 필요하다면 [에이전트 실행 가이드](../running_agents.md)의 [`call_model_input_filter`][agents.run.RunConfig.call_model_input_filter] 를 사용하세요.
|
||||
|
||||
## 조회 기록 제한
|
||||
|
||||
각 실행 전에 가져올 기록의 양을 제어하려면 [`SessionSettings`][agents.memory.SessionSettings] 를 사용하세요.
|
||||
|
||||
- `SessionSettings(limit=None)` (기본값): 사용 가능한 모든 세션 항목 조회
|
||||
- `SessionSettings(limit=N)`: 가장 최근 `N` 개 항목만 조회
|
||||
|
||||
[`RunConfig.session_settings`][agents.run.RunConfig.session_settings] 를 통해 실행별로 이를 적용할 수 있습니다.
|
||||
|
||||
```python
|
||||
from agents import Agent, RunConfig, Runner, SessionSettings, SQLiteSession
|
||||
|
||||
agent = Agent(name="Assistant")
|
||||
session = SQLiteSession("conversation_123")
|
||||
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
"Summarize our recent discussion.",
|
||||
session=session,
|
||||
run_config=RunConfig(session_settings=SessionSettings(limit=50)),
|
||||
)
|
||||
```
|
||||
|
||||
세션 구현이 기본 세션 설정을 제공하는 경우, `RunConfig.session_settings` 는 해당 실행에 대해 `None` 이 아닌 값을 재정의합니다. 이는 긴 대화에서 세션의 기본 동작은 변경하지 않으면서 조회 크기를 제한하고 싶을 때 유용합니다.
|
||||
|
||||
## 메모리 작업
|
||||
|
||||
### 기본 작업
|
||||
|
||||
세션은 대화 기록 관리를 위한 여러 작업을 지원합니다.
|
||||
|
||||
```python
|
||||
from agents import SQLiteSession
|
||||
|
||||
session = SQLiteSession("user_123", "conversations.db")
|
||||
|
||||
# Get all items in a session
|
||||
items = await session.get_items()
|
||||
|
||||
# Add new items to a session
|
||||
new_items = [
|
||||
{"role": "user", "content": "Hello"},
|
||||
{"role": "assistant", "content": "Hi there!"}
|
||||
]
|
||||
await session.add_items(new_items)
|
||||
|
||||
# Remove and return the most recent item
|
||||
last_item = await session.pop_item()
|
||||
print(last_item) # {"role": "assistant", "content": "Hi there!"}
|
||||
|
||||
# Clear all items from a session
|
||||
await session.clear_session()
|
||||
```
|
||||
|
||||
### 수정을 위한 pop_item 사용
|
||||
|
||||
`pop_item` 메서드는 대화의 마지막 항목을 되돌리거나 수정하고 싶을 때 특히 유용합니다.
|
||||
|
||||
```python
|
||||
from agents import Agent, Runner, SQLiteSession
|
||||
|
||||
agent = Agent(name="Assistant")
|
||||
session = SQLiteSession("correction_example")
|
||||
|
||||
# Initial conversation
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
"What's 2 + 2?",
|
||||
session=session
|
||||
)
|
||||
print(f"Agent: {result.final_output}")
|
||||
|
||||
# User wants to correct their question
|
||||
assistant_item = await session.pop_item() # Remove agent's response
|
||||
user_item = await session.pop_item() # Remove user's question
|
||||
|
||||
# Ask a corrected question
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
"What's 2 + 3?",
|
||||
session=session
|
||||
)
|
||||
print(f"Agent: {result.final_output}")
|
||||
```
|
||||
|
||||
## 기본 제공 세션 구현
|
||||
|
||||
SDK는 다양한 사용 사례를 위한 여러 세션 구현을 제공합니다.
|
||||
|
||||
### 기본 제공 세션 구현 선택
|
||||
|
||||
아래의 상세 예제를 읽기 전에 시작점을 고르는 데 이 표를 사용하세요.
|
||||
|
||||
| 세션 유형 | 적합한 용도 | 참고 |
|
||||
| --- | --- | --- |
|
||||
| `SQLiteSession` | 로컬 개발 및 간단한 앱 | 기본 제공, 경량, 파일 기반 또는 인메모리 |
|
||||
| `AsyncSQLiteSession` | `aiosqlite` 기반 비동기 SQLite | 비동기 드라이버를 지원하는 확장 백엔드 |
|
||||
| `RedisSession` | 여러 워커/서비스 간 공유 메모리 | 저지연 분산 배포에 적합 |
|
||||
| `SQLAlchemySession` | 기존 데이터베이스를 사용하는 프로덕션 앱 | SQLAlchemy가 지원하는 데이터베이스와 함께 동작 |
|
||||
| `MongoDBSession` | 이미 MongoDB를 사용하거나 다중 프로세스 스토리지가 필요한 앱 | 비동기 pymongo; 순서 보장을 위한 원자적 시퀀스 카운터 |
|
||||
| `DaprSession` | Dapr 사이드카를 사용하는 클라우드 네이티브 배포 | 여러 상태 저장소와 TTL 및 일관성 제어 지원 |
|
||||
| `OpenAIConversationsSession` | OpenAI의 서버 관리형 스토리지 | OpenAI Conversations API 기반 기록 |
|
||||
| `OpenAIResponsesCompactionSession` | 자동 압축이 필요한 긴 대화 | 다른 세션 백엔드를 감싸는 래퍼 |
|
||||
| `AdvancedSQLiteSession` | SQLite와 분기/분석 | 더 많은 기능 세트; 전용 페이지 참조 |
|
||||
| `EncryptedSession` | 다른 세션 위에 암호화 + TTL 적용 | 래퍼; 먼저 하위 백엔드 선택 |
|
||||
|
||||
일부 구현에는 추가 세부 정보를 담은 전용 페이지가 있으며, 해당 하위 섹션에 링크되어 있습니다.
|
||||
|
||||
ChatKit용 Python 서버를 구현하는 경우, ChatKit의 스레드 및 항목 지속성을 위해 `chatkit.store.Store` 구현을 사용하세요. `SQLAlchemySession` 같은 Agents SDK 세션은 SDK 측 대화 기록을 관리하지만, ChatKit의 스토어를 그대로 대체할 수 있는 것은 아닙니다. [`chatkit-python` 의 ChatKit 데이터 스토어 구현 가이드](https://github.com/openai/chatkit-python/blob/main/docs/guides/respond-to-user-message.md#implement-your-chatkit-data-store)를 참조하세요.
|
||||
|
||||
### OpenAI Conversations API 세션
|
||||
|
||||
`OpenAIConversationsSession` 을 통해 [OpenAI의 Conversations API](https://platform.openai.com/docs/api-reference/conversations)를 사용하세요.
|
||||
|
||||
```python
|
||||
from agents import Agent, Runner, OpenAIConversationsSession
|
||||
|
||||
# Create agent
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
instructions="Reply very concisely.",
|
||||
)
|
||||
|
||||
# Create a new conversation
|
||||
session = OpenAIConversationsSession()
|
||||
|
||||
# Optionally resume a previous conversation by passing a conversation ID
|
||||
# session = OpenAIConversationsSession(conversation_id="conv_123")
|
||||
|
||||
# Start conversation
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
"What city is the Golden Gate Bridge in?",
|
||||
session=session
|
||||
)
|
||||
print(result.final_output) # "San Francisco"
|
||||
|
||||
# Continue the conversation
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
"What state is it in?",
|
||||
session=session
|
||||
)
|
||||
print(result.final_output) # "California"
|
||||
```
|
||||
|
||||
### OpenAI Responses 압축 세션
|
||||
|
||||
Responses API(`responses.compact`)로 저장된 대화 기록을 압축하려면 `OpenAIResponsesCompactionSession` 을 사용하세요. 이 세션은 하위 세션을 감싸며, `should_trigger_compaction` 에 따라 각 턴 이후 자동으로 압축할 수 있습니다. `OpenAIConversationsSession` 을 이것으로 감싸지 마세요. 두 기능은 서로 다른 방식으로 기록을 관리합니다.
|
||||
|
||||
#### 일반적인 사용법(자동 압축)
|
||||
|
||||
```python
|
||||
from agents import Agent, Runner, SQLiteSession
|
||||
from agents.memory import OpenAIResponsesCompactionSession
|
||||
|
||||
underlying = SQLiteSession("conversation_123")
|
||||
session = OpenAIResponsesCompactionSession(
|
||||
session_id="conversation_123",
|
||||
underlying_session=underlying,
|
||||
)
|
||||
|
||||
agent = Agent(name="Assistant")
|
||||
result = await Runner.run(agent, "Hello", session=session)
|
||||
print(result.final_output)
|
||||
```
|
||||
|
||||
기본적으로 압축은 후보 임계값에 도달하면 각 턴 이후 실행됩니다.
|
||||
|
||||
`compaction_mode="previous_response_id"` 는 이미 Responses API 응답 ID로 턴을 체이닝하고 있을 때 가장 잘 동작합니다. `compaction_mode="input"` 은 대신 현재 세션 항목으로부터 압축 요청을 다시 구성합니다. 이는 응답 체인을 사용할 수 없거나 세션 내용을 신뢰할 수 있는 기준으로 삼고 싶을 때 유용합니다. 기본값인 `"auto"` 는 사용 가능한 가장 안전한 옵션을 선택합니다.
|
||||
|
||||
에이전트가 `ModelSettings(store=False)` 로 실행되는 경우, Responses API는 나중에 조회할 수 있도록 마지막 응답을 보관하지 않습니다. 이 무상태 구성에서는 기본 `"auto"` 모드가 `previous_response_id` 에 의존하지 않고 입력 기반 압축으로 폴백합니다. 전체 예제는 [`examples/memory/compaction_session_stateless_example.py`](https://github.com/openai/openai-agents-python/tree/main/examples/memory/compaction_session_stateless_example.py)를 참조하세요.
|
||||
|
||||
#### 스트리밍을 차단할 수 있는 자동 압축
|
||||
|
||||
압축은 세션 기록을 지우고 다시 쓰므로, SDK는 실행이 완료된 것으로 간주하기 전에 압축이 끝나기를 기다립니다. 스트리밍 모드에서는 압축 작업이 무거운 경우 마지막 출력 토큰 이후에도 `run.stream_events()` 가 몇 초 동안 열린 상태로 남아 있을 수 있습니다.
|
||||
|
||||
저지연 스트리밍이나 빠른 턴 전환을 원한다면 자동 압축을 비활성화하고 턴 사이(또는 유휴 시간)에 직접 `run_compaction()` 을 호출하세요. 자체 기준에 따라 언제 압축을 강제로 실행할지 결정할 수 있습니다.
|
||||
|
||||
```python
|
||||
from agents import Agent, Runner, SQLiteSession
|
||||
from agents.memory import OpenAIResponsesCompactionSession
|
||||
|
||||
underlying = SQLiteSession("conversation_123")
|
||||
session = OpenAIResponsesCompactionSession(
|
||||
session_id="conversation_123",
|
||||
underlying_session=underlying,
|
||||
# Disable triggering the auto compaction
|
||||
should_trigger_compaction=lambda _: False,
|
||||
)
|
||||
|
||||
agent = Agent(name="Assistant")
|
||||
result = await Runner.run(agent, "Hello", session=session)
|
||||
|
||||
# Decide when to compact (e.g., on idle, every N turns, or size thresholds).
|
||||
await session.run_compaction({"force": True})
|
||||
```
|
||||
|
||||
### SQLite 세션
|
||||
|
||||
SQLite를 사용하는 기본 경량 세션 구현입니다.
|
||||
|
||||
```python
|
||||
from agents import SQLiteSession
|
||||
|
||||
# In-memory database (lost when process ends)
|
||||
session = SQLiteSession("user_123")
|
||||
|
||||
# Persistent file-based database
|
||||
session = SQLiteSession("user_123", "conversations.db")
|
||||
|
||||
# Use the session
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
"Hello",
|
||||
session=session
|
||||
)
|
||||
```
|
||||
|
||||
### 비동기 SQLite 세션
|
||||
|
||||
`aiosqlite` 기반 SQLite 지속성이 필요할 때 `AsyncSQLiteSession` 을 사용하세요.
|
||||
|
||||
```bash
|
||||
pip install aiosqlite
|
||||
```
|
||||
|
||||
```python
|
||||
from agents import Agent, Runner
|
||||
from agents.extensions.memory import AsyncSQLiteSession
|
||||
|
||||
agent = Agent(name="Assistant")
|
||||
session = AsyncSQLiteSession("user_123", db_path="conversations.db")
|
||||
result = await Runner.run(agent, "Hello", session=session)
|
||||
```
|
||||
|
||||
### Redis 세션
|
||||
|
||||
여러 워커 또는 서비스 간 공유 세션 메모리에는 `RedisSession` 을 사용하세요.
|
||||
|
||||
```bash
|
||||
pip install openai-agents[redis]
|
||||
```
|
||||
|
||||
```python
|
||||
from agents import Agent, Runner
|
||||
from agents.extensions.memory import RedisSession
|
||||
|
||||
agent = Agent(name="Assistant")
|
||||
session = RedisSession.from_url(
|
||||
"user_123",
|
||||
url="redis://localhost:6379/0",
|
||||
)
|
||||
result = await Runner.run(agent, "Hello", session=session)
|
||||
```
|
||||
|
||||
### SQLAlchemy 세션
|
||||
|
||||
SQLAlchemy가 지원하는 모든 데이터베이스를 사용하는 프로덕션 환경에 적합한 Agents SDK 세션 지속성입니다.
|
||||
|
||||
```python
|
||||
from agents.extensions.memory import SQLAlchemySession
|
||||
|
||||
# Using database URL
|
||||
session = SQLAlchemySession.from_url(
|
||||
"user_123",
|
||||
url="postgresql+asyncpg://user:pass@localhost/db",
|
||||
create_tables=True
|
||||
)
|
||||
|
||||
# Using existing engine
|
||||
from sqlalchemy.ext.asyncio import create_async_engine
|
||||
engine = create_async_engine("postgresql+asyncpg://user:pass@localhost/db")
|
||||
session = SQLAlchemySession("user_123", engine=engine, create_tables=True)
|
||||
```
|
||||
|
||||
자세한 문서는 [SQLAlchemy 세션](sqlalchemy_session.md)을 참조하세요.
|
||||
|
||||
### Dapr 세션
|
||||
|
||||
이미 Dapr 사이드카를 실행 중이거나 에이전트 코드를 변경하지 않고 여러 상태 저장소 백엔드 간에 이동할 수 있는 세션 스토리지를 원할 때 `DaprSession` 을 사용하세요.
|
||||
|
||||
```bash
|
||||
pip install openai-agents[dapr]
|
||||
```
|
||||
|
||||
```python
|
||||
from agents import Agent, Runner
|
||||
from agents.extensions.memory import DaprSession
|
||||
|
||||
agent = Agent(name="Assistant")
|
||||
|
||||
async with DaprSession.from_address(
|
||||
"user_123",
|
||||
state_store_name="statestore",
|
||||
dapr_address="localhost:50001",
|
||||
) as session:
|
||||
result = await Runner.run(agent, "Hello", session=session)
|
||||
print(result.final_output)
|
||||
```
|
||||
|
||||
참고:
|
||||
|
||||
- `from_address(...)` 는 Dapr 클라이언트를 생성하고 수명 주기를 관리합니다. 앱에서 이미 Dapr 클라이언트를 관리하고 있다면 `dapr_client=...` 로 `DaprSession(...)` 을 직접 생성하세요.
|
||||
- 기반 상태 저장소가 TTL을 지원하는 경우 오래된 세션 데이터가 자동으로 만료되도록 `ttl=...` 을 전달하세요.
|
||||
- 더 강한 쓰기 후 읽기 보장이 필요하면 `consistency=DAPR_CONSISTENCY_STRONG` 을 전달하세요.
|
||||
- Dapr Python SDK는 HTTP 사이드카 엔드포인트도 확인합니다. 로컬 개발에서는 `dapr_address` 에서 사용하는 gRPC 포트뿐 아니라 `--dapr-http-port 3500` 도 함께 지정해 Dapr를 시작하세요.
|
||||
- 로컬 컴포넌트와 문제 해결을 포함한 전체 설정 안내는 [`examples/memory/dapr_session_example.py`](https://github.com/openai/openai-agents-python/tree/main/examples/memory/dapr_session_example.py)를 참조하세요.
|
||||
|
||||
|
||||
### MongoDB 세션
|
||||
|
||||
이미 MongoDB를 사용하거나 수평 확장이 가능한 다중 프로세스 세션 스토리지가 필요한 애플리케이션에는 `MongoDBSession` 을 사용하세요.
|
||||
|
||||
```bash
|
||||
pip install openai-agents[mongodb]
|
||||
```
|
||||
|
||||
```python
|
||||
from agents import Agent, Runner
|
||||
from agents.extensions.memory import MongoDBSession
|
||||
|
||||
agent = Agent(name="Assistant")
|
||||
|
||||
# Create from URI — owns the client and closes it when session.close() is called
|
||||
session = MongoDBSession.from_uri(
|
||||
"user-123",
|
||||
uri="mongodb://localhost:27017",
|
||||
database="agents",
|
||||
)
|
||||
result = await Runner.run(agent, "Hello", session=session)
|
||||
print(result.final_output)
|
||||
await session.close()
|
||||
```
|
||||
|
||||
참고:
|
||||
|
||||
- `from_uri(...)` 는 `AsyncMongoClient` 를 생성하고 수명 주기를 관리하며, `session.close()` 시 닫습니다. 애플리케이션에서 이미 클라이언트를 관리하고 있다면 `client=...` 로 `MongoDBSession(...)` 을 직접 생성하세요. 이 경우 `session.close()` 는 아무 작업도 하지 않으며 수명 주기는 호출자에게 남아 있습니다.
|
||||
- 다른 변경 없이 `mongodb+srv://user:password@cluster.example.mongodb.net` URI를 `from_uri(...)` 에 전달하여 [MongoDB Atlas](https://www.mongodb.com/products/platform)에 연결하세요.
|
||||
- 두 개의 컬렉션이 사용되며, 두 이름 모두 `sessions_collection=` (기본값 `agent_sessions`) 및 `messages_collection=` (기본값 `agent_messages`) 로 구성할 수 있습니다. 인덱스는 처음 사용할 때 자동으로 생성됩니다. 각 메시지 문서는 단조 증가하는 `seq` 카운터를 포함하여 동시 작성자와 프로세스 간 순서를 보존합니다.
|
||||
- 첫 실행 전에 연결을 확인하려면 `await session.ping()` 을 사용하세요.
|
||||
|
||||
### 고급 SQLite 세션
|
||||
|
||||
대화 분기, 사용량 분석, 구조화된 쿼리를 지원하는 향상된 SQLite 세션입니다.
|
||||
|
||||
```python
|
||||
from agents.extensions.memory import AdvancedSQLiteSession
|
||||
|
||||
# Create with advanced features
|
||||
session = AdvancedSQLiteSession(
|
||||
session_id="user_123",
|
||||
db_path="conversations.db",
|
||||
create_tables=True
|
||||
)
|
||||
|
||||
# Automatic usage tracking
|
||||
result = await Runner.run(agent, "Hello", session=session)
|
||||
await session.store_run_usage(result) # Track token usage
|
||||
|
||||
# Conversation branching
|
||||
await session.create_branch_from_turn(2) # Branch from turn 2
|
||||
```
|
||||
|
||||
자세한 문서는 [고급 SQLite 세션](advanced_sqlite_session.md)을 참조하세요.
|
||||
|
||||
### 암호화된 세션
|
||||
|
||||
모든 세션 구현을 위한 투명한 암호화 래퍼입니다.
|
||||
|
||||
```python
|
||||
from agents.extensions.memory import EncryptedSession, SQLAlchemySession
|
||||
|
||||
# Create underlying session
|
||||
underlying_session = SQLAlchemySession.from_url(
|
||||
"user_123",
|
||||
url="sqlite+aiosqlite:///conversations.db",
|
||||
create_tables=True
|
||||
)
|
||||
|
||||
# Wrap with encryption and TTL
|
||||
session = EncryptedSession(
|
||||
session_id="user_123",
|
||||
underlying_session=underlying_session,
|
||||
encryption_key="your-secret-key",
|
||||
ttl=600 # 10 minutes
|
||||
)
|
||||
|
||||
result = await Runner.run(agent, "Hello", session=session)
|
||||
```
|
||||
|
||||
자세한 문서는 [암호화된 세션](encrypted_session.md)을 참조하세요.
|
||||
|
||||
### 기타 세션 유형
|
||||
|
||||
기본 제공 옵션이 몇 가지 더 있습니다. `examples/memory/` 및 `extensions/memory/` 아래의 소스 코드를 참조하세요.
|
||||
|
||||
## 운영 패턴
|
||||
|
||||
### 세션 ID 명명
|
||||
|
||||
대화를 정리하는 데 도움이 되는 의미 있는 세션 ID를 사용하세요.
|
||||
|
||||
- 사용자 기반: `"user_12345"`
|
||||
- 스레드 기반: `"thread_abc123"`
|
||||
- 컨텍스트 기반: `"support_ticket_456"`
|
||||
|
||||
### 메모리 지속성
|
||||
|
||||
- 임시 대화에는 인메모리 SQLite(`SQLiteSession("session_id")`) 사용
|
||||
- 지속 대화에는 파일 기반 SQLite(`SQLiteSession("session_id", "path/to/db.sqlite")`) 사용
|
||||
- `aiosqlite` 기반 구현이 필요할 때는 비동기 SQLite(`AsyncSQLiteSession("session_id", db_path="...")`) 사용
|
||||
- 공유 저지연 세션 메모리에는 Redis 기반 세션(`RedisSession.from_url("session_id", url="redis://...")`) 사용
|
||||
- SQLAlchemy가 지원하는 기존 데이터베이스가 있는 프로덕션 시스템에는 SQLAlchemy 기반 세션(`SQLAlchemySession("session_id", engine=engine, create_tables=True)`) 사용
|
||||
- 이미 MongoDB를 사용하거나 다중 프로세스, 수평 확장 가능한 세션 스토리지가 필요한 애플리케이션에는 MongoDB 세션(`MongoDBSession.from_uri("session_id", uri="mongodb://localhost:27017")`) 사용
|
||||
- 기본 제공 텔레메트리, 트레이싱, 데이터 격리와 함께 30개 이상의 데이터베이스 백엔드를 지원하는 프로덕션 클라우드 네이티브 배포에는 Dapr 상태 저장소 세션(`DaprSession.from_address("session_id", state_store_name="statestore", dapr_address="localhost:50001")`) 사용
|
||||
- OpenAI Conversations API에 기록을 저장하고 싶다면 OpenAI가 호스팅하는 스토리지(`OpenAIConversationsSession()`) 사용
|
||||
- 투명한 암호화와 TTL 기반 만료로 모든 세션을 감싸려면 암호화된 세션(`EncryptedSession(session_id, underlying_session, encryption_key)`) 사용
|
||||
- 더 고급 사용 사례에는 다른 프로덕션 시스템(예: Django)을 위한 사용자 지정 세션 백엔드 구현 고려
|
||||
|
||||
### 여러 세션
|
||||
|
||||
```python
|
||||
from agents import Agent, Runner, SQLiteSession
|
||||
|
||||
agent = Agent(name="Assistant")
|
||||
|
||||
# Different sessions maintain separate conversation histories
|
||||
session_1 = SQLiteSession("user_123", "conversations.db")
|
||||
session_2 = SQLiteSession("user_456", "conversations.db")
|
||||
|
||||
result1 = await Runner.run(
|
||||
agent,
|
||||
"Help me with my account",
|
||||
session=session_1
|
||||
)
|
||||
result2 = await Runner.run(
|
||||
agent,
|
||||
"What are my charges?",
|
||||
session=session_2
|
||||
)
|
||||
```
|
||||
|
||||
### 세션 공유
|
||||
|
||||
```python
|
||||
# Different agents can share the same session
|
||||
support_agent = Agent(name="Support")
|
||||
billing_agent = Agent(name="Billing")
|
||||
session = SQLiteSession("user_123")
|
||||
|
||||
# Both agents will see the same conversation history
|
||||
result1 = await Runner.run(
|
||||
support_agent,
|
||||
"Help me with my account",
|
||||
session=session
|
||||
)
|
||||
result2 = await Runner.run(
|
||||
billing_agent,
|
||||
"What are my charges?",
|
||||
session=session
|
||||
)
|
||||
```
|
||||
|
||||
## 전체 예제
|
||||
|
||||
세션 메모리가 동작하는 방식을 보여주는 전체 예제입니다.
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
from agents import Agent, Runner, SQLiteSession
|
||||
|
||||
|
||||
async def main():
|
||||
# Create an agent
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
instructions="Reply very concisely.",
|
||||
)
|
||||
|
||||
# Create a session instance that will persist across runs
|
||||
session = SQLiteSession("conversation_123", "conversation_history.db")
|
||||
|
||||
print("=== Sessions Example ===")
|
||||
print("The agent will remember previous messages automatically.\n")
|
||||
|
||||
# First turn
|
||||
print("First turn:")
|
||||
print("User: What city is the Golden Gate Bridge in?")
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
"What city is the Golden Gate Bridge in?",
|
||||
session=session
|
||||
)
|
||||
print(f"Assistant: {result.final_output}")
|
||||
print()
|
||||
|
||||
# Second turn - the agent will remember the previous conversation
|
||||
print("Second turn:")
|
||||
print("User: What state is it in?")
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
"What state is it in?",
|
||||
session=session
|
||||
)
|
||||
print(f"Assistant: {result.final_output}")
|
||||
print()
|
||||
|
||||
# Third turn - continuing the conversation
|
||||
print("Third turn:")
|
||||
print("User: What's the population of that state?")
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
"What's the population of that state?",
|
||||
session=session
|
||||
)
|
||||
print(f"Assistant: {result.final_output}")
|
||||
print()
|
||||
|
||||
print("=== Conversation Complete ===")
|
||||
print("Notice how the agent remembered the context from previous turns!")
|
||||
print("Sessions automatically handles conversation history.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
## 사용자 지정 세션 구현
|
||||
|
||||
[`Session`][agents.memory.session.Session] 프로토콜을 따르는 클래스를 만들어 자체 세션 메모리를 구현할 수 있습니다.
|
||||
|
||||
```python
|
||||
from agents.memory.session import SessionABC
|
||||
from agents.items import TResponseInputItem
|
||||
from typing import List
|
||||
|
||||
class MyCustomSession(SessionABC):
|
||||
"""Custom session implementation following the Session protocol."""
|
||||
|
||||
def __init__(self, session_id: str):
|
||||
self.session_id = session_id
|
||||
# Your initialization here
|
||||
|
||||
async def get_items(self, limit: int | None = None) -> List[TResponseInputItem]:
|
||||
"""Retrieve conversation history for this session."""
|
||||
# Your implementation here
|
||||
pass
|
||||
|
||||
async def add_items(self, items: List[TResponseInputItem]) -> None:
|
||||
"""Store new items for this session."""
|
||||
# Your implementation here
|
||||
pass
|
||||
|
||||
async def pop_item(self) -> TResponseInputItem | None:
|
||||
"""Remove and return the most recent item from this session."""
|
||||
# Your implementation here
|
||||
pass
|
||||
|
||||
async def clear_session(self) -> None:
|
||||
"""Clear all items for this session."""
|
||||
# Your implementation here
|
||||
pass
|
||||
|
||||
# Use your custom session
|
||||
agent = Agent(name="Assistant")
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
"Hello",
|
||||
session=MyCustomSession("my_session")
|
||||
)
|
||||
```
|
||||
|
||||
## 커뮤니티 세션 구현
|
||||
|
||||
커뮤니티에서 추가 세션 구현을 개발했습니다.
|
||||
|
||||
| 패키지 | 설명 |
|
||||
|---------|-------------|
|
||||
| [openai-django-sessions](https://pypi.org/project/openai-django-sessions/) | Django가 지원하는 모든 데이터베이스(PostgreSQL, MySQL, SQLite 등)를 위한 Django ORM 기반 세션 |
|
||||
|
||||
세션 구현을 만들었다면 이곳에 추가할 수 있도록 문서 PR을 자유롭게 제출해 주세요!
|
||||
|
||||
## API 참조
|
||||
|
||||
자세한 API 문서는 다음을 참조하세요.
|
||||
|
||||
- [`Session`][agents.memory.session.Session] - 프로토콜 인터페이스
|
||||
- [`OpenAIConversationsSession`][agents.memory.OpenAIConversationsSession] - OpenAI Conversations API 구현
|
||||
- [`OpenAIResponsesCompactionSession`][agents.memory.openai_responses_compaction_session.OpenAIResponsesCompactionSession] - Responses API 압축 래퍼
|
||||
- [`SQLiteSession`][agents.memory.sqlite_session.SQLiteSession] - 기본 SQLite 구현
|
||||
- [`AsyncSQLiteSession`][agents.extensions.memory.async_sqlite_session.AsyncSQLiteSession] - `aiosqlite` 기반 비동기 SQLite 구현
|
||||
- [`RedisSession`][agents.extensions.memory.redis_session.RedisSession] - Redis 기반 세션 구현
|
||||
- [`SQLAlchemySession`][agents.extensions.memory.sqlalchemy_session.SQLAlchemySession] - SQLAlchemy 기반 구현
|
||||
- [`MongoDBSession`][agents.extensions.memory.mongodb_session.MongoDBSession] - MongoDB 기반 세션 구현
|
||||
- [`DaprSession`][agents.extensions.memory.dapr_session.DaprSession] - Dapr 상태 저장소 구현
|
||||
- [`AdvancedSQLiteSession`][agents.extensions.memory.advanced_sqlite_session.AdvancedSQLiteSession] - 분기와 분석을 지원하는 향상된 SQLite
|
||||
- [`EncryptedSession`][agents.extensions.memory.encrypt_session.EncryptedSession] - 모든 세션을 위한 암호화 래퍼
|
||||
@@ -0,0 +1,80 @@
|
||||
---
|
||||
search:
|
||||
exclude: true
|
||||
---
|
||||
# SQLAlchemy 세션
|
||||
|
||||
`SQLAlchemySession`은 SQLAlchemy를 사용해 프로덕션 환경에서 사용할 수 있는 세션 구현을 제공하며, 세션 저장소로 SQLAlchemy가 지원하는 모든 데이터베이스(PostgreSQL, MySQL, SQLite 등)를 사용할 수 있습니다.
|
||||
|
||||
## 설치
|
||||
|
||||
SQLAlchemy 세션에는 `sqlalchemy` extra가 필요합니다.
|
||||
|
||||
```bash
|
||||
pip install openai-agents[sqlalchemy]
|
||||
```
|
||||
|
||||
## 빠른 시작
|
||||
|
||||
### 데이터베이스 URL 사용
|
||||
|
||||
가장 간단하게 시작하는 방법입니다.
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
from agents import Agent, Runner
|
||||
from agents.extensions.memory import SQLAlchemySession
|
||||
|
||||
async def main():
|
||||
agent = Agent("Assistant")
|
||||
|
||||
# Create session using database URL
|
||||
session = SQLAlchemySession.from_url(
|
||||
"user-123",
|
||||
url="sqlite+aiosqlite:///:memory:",
|
||||
create_tables=True
|
||||
)
|
||||
|
||||
result = await Runner.run(agent, "Hello", session=session)
|
||||
print(result.final_output)
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
### 기존 엔진 사용
|
||||
|
||||
기존 SQLAlchemy 엔진이 있는 애플리케이션의 경우:
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
from agents import Agent, Runner
|
||||
from agents.extensions.memory import SQLAlchemySession
|
||||
from sqlalchemy.ext.asyncio import create_async_engine
|
||||
|
||||
async def main():
|
||||
# Create your database engine
|
||||
engine = create_async_engine("postgresql+asyncpg://user:pass@localhost/db")
|
||||
|
||||
agent = Agent("Assistant")
|
||||
session = SQLAlchemySession(
|
||||
"user-456",
|
||||
engine=engine,
|
||||
create_tables=True
|
||||
)
|
||||
|
||||
result = await Runner.run(agent, "Hello", session=session)
|
||||
print(result.final_output)
|
||||
|
||||
# Clean up
|
||||
await engine.dispose()
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
|
||||
## API 참조
|
||||
|
||||
- [`SQLAlchemySession`][agents.extensions.memory.sqlalchemy_session.SQLAlchemySession] - 주요 클래스
|
||||
- [`Session`][agents.memory.session.Session] - 기본 세션 프로토콜
|
||||
@@ -0,0 +1,145 @@
|
||||
---
|
||||
search:
|
||||
exclude: true
|
||||
---
|
||||
# 스트리밍
|
||||
|
||||
스트리밍을 사용하면 에이전트 실행이 진행되는 동안 업데이트를 구독할 수 있습니다. 이는 최종 사용자에게 진행 상황 업데이트와 부분 응답을 보여주는 데 유용할 수 있습니다.
|
||||
|
||||
스트리밍하려면 [`Runner.run_streamed()`][agents.run.Runner.run_streamed]를 호출하면 되며, 이는 [`RunResultStreaming`][agents.result.RunResultStreaming]을 반환합니다. `result.stream_events()`를 호출하면 아래에 설명된 [`StreamEvent`][agents.stream_events.StreamEvent] 객체의 비동기 스트림을 얻을 수 있습니다.
|
||||
|
||||
비동기 이터레이터가 종료될 때까지 `result.stream_events()`를 계속 소비하세요. 스트리밍 실행은 이터레이터가 끝나기 전까지 완료되지 않으며, 세션 영속화, 승인 기록 관리, 히스토리 압축과 같은 후처리는 마지막으로 보이는 토큰이 도착한 뒤에 완료될 수 있습니다. 루프가 종료되면 `result.is_complete`는 최종 실행 상태를 반영합니다.
|
||||
|
||||
## 원문 응답 이벤트
|
||||
|
||||
[`RawResponsesStreamEvent`][agents.stream_events.RawResponsesStreamEvent]는 LLM에서 직접 전달되는 원문 이벤트입니다. OpenAI Responses API 형식이므로 각 이벤트에는 `response.created`, `response.output_text.delta` 등과 같은 타입과 데이터가 있습니다. 이러한 이벤트는 응답 메시지가 생성되는 즉시 사용자에게 스트리밍하려는 경우 유용합니다.
|
||||
|
||||
컴퓨터 도구 원문 이벤트는 저장된 결과와 동일하게 preview-vs-GA 구분을 유지합니다. Preview 흐름은 하나의 `action`이 있는 `computer_call` 항목을 스트리밍하는 반면, `gpt-5.5`는 배치된 `actions[]`가 있는 `computer_call` 항목을 스트리밍할 수 있습니다. 상위 수준의 [`RunItemStreamEvent`][agents.stream_events.RunItemStreamEvent] 표면은 이를 위해 컴퓨터 전용의 특별한 이벤트 이름을 추가하지 않습니다. 두 형태 모두 여전히 `tool_called`로 표면화되며, 스크린샷 결과는 `computer_call_output` 항목을 래핑한 `tool_output`으로 반환됩니다.
|
||||
|
||||
예를 들어, 다음은 LLM이 생성한 텍스트를 토큰 단위로 출력합니다.
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
from openai.types.responses import ResponseTextDeltaEvent
|
||||
from agents import Agent, Runner
|
||||
|
||||
async def main():
|
||||
agent = Agent(
|
||||
name="Joker",
|
||||
instructions="You are a helpful assistant.",
|
||||
)
|
||||
|
||||
result = Runner.run_streamed(agent, input="Please tell me 5 jokes.")
|
||||
async for event in result.stream_events():
|
||||
if event.type == "raw_response_event" and isinstance(event.data, ResponseTextDeltaEvent):
|
||||
print(event.data.delta, end="", flush=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
## 스트리밍 및 승인
|
||||
|
||||
스트리밍은 도구 승인을 위해 일시 중지되는 실행과 호환됩니다. 도구에 승인이 필요한 경우 `result.stream_events()`가 종료되고 대기 중인 승인은 [`RunResultStreaming.interruptions`][agents.result.RunResultStreaming.interruptions]에 노출됩니다. `result.to_state()`를 사용해 결과를 [`RunState`][agents.run_state.RunState]로 변환하고, 인터럽션(중단 처리)을 승인하거나 거부한 다음 `Runner.run_streamed(...)`로 재개하세요.
|
||||
|
||||
```python
|
||||
result = Runner.run_streamed(agent, "Delete temporary files if they are no longer needed.")
|
||||
async for _event in result.stream_events():
|
||||
pass
|
||||
|
||||
if result.interruptions:
|
||||
state = result.to_state()
|
||||
for interruption in result.interruptions:
|
||||
state.approve(interruption)
|
||||
result = Runner.run_streamed(agent, state)
|
||||
async for _event in result.stream_events():
|
||||
pass
|
||||
```
|
||||
|
||||
전체 일시 중지/재개 과정을 보려면 [휴먼인더루프 (HITL) 가이드](human_in_the_loop.md)를 참조하세요.
|
||||
|
||||
## 현재 턴 이후 스트리밍 취소
|
||||
|
||||
스트리밍 실행을 중간에 중지해야 하는 경우 [`result.cancel()`][agents.result.RunResultStreaming.cancel]을 호출하세요. 기본적으로 이는 실행을 즉시 중지합니다. 중지하기 전에 현재 턴이 깔끔하게 끝나도록 하려면 대신 `result.cancel(mode="after_turn")`을 호출하세요.
|
||||
|
||||
스트리밍된 실행은 `result.stream_events()`가 종료되기 전까지 완료되지 않습니다. 마지막으로 보이는 토큰 이후에도 SDK가 여전히 세션 항목을 영속화하거나, 승인 상태를 최종화하거나, 히스토리를 압축하고 있을 수 있습니다.
|
||||
|
||||
[`result.to_input_list(mode="normalized")`][agents.result.RunResultBase.to_input_list]에서 수동으로 계속 진행하고 있고, `cancel(mode="after_turn")`가 도구 턴 이후에 중지되는 경우, 즉시 새 사용자 턴을 추가하는 대신 해당 정규화된 입력으로 `result.last_agent`를 다시 실행하여 완료되지 않은 턴을 계속 진행하세요.
|
||||
- 스트리밍된 실행이 도구 승인 때문에 중지된 경우 이를 새 턴으로 취급하지 마세요. 스트림 소비를 완료하고, `result.interruptions`를 검사한 뒤, `result.to_state()`에서 재개하세요.
|
||||
- 다음 모델 호출 전에 가져온 세션 히스토리와 새 사용자 입력이 병합되는 방식을 사용자 지정하려면 [`RunConfig.session_input_callback`][agents.run.RunConfig.session_input_callback]을 사용하세요. 여기에서 새 턴 항목을 다시 작성하면, 다시 작성된 버전이 해당 턴에 대해 영속화됩니다.
|
||||
|
||||
## 실행 항목 이벤트 및 에이전트 이벤트
|
||||
|
||||
[`RunItemStreamEvent`][agents.stream_events.RunItemStreamEvent]는 더 높은 수준의 이벤트입니다. 항목이 완전히 생성되었을 때 알려줍니다. 이를 통해 각 토큰 대신 "메시지 생성됨", "도구 실행됨" 등의 수준에서 진행 상황 업데이트를 푸시할 수 있습니다. 마찬가지로 [`AgentUpdatedStreamEvent`][agents.stream_events.AgentUpdatedStreamEvent]는 현재 에이전트가 변경될 때(예: 핸드오프의 결과) 업데이트를 제공합니다.
|
||||
|
||||
### 실행 항목 이벤트 이름
|
||||
|
||||
`RunItemStreamEvent.name`은 고정된 의미론적 이벤트 이름 집합을 사용합니다.
|
||||
|
||||
- `message_output_created`
|
||||
- `handoff_requested`
|
||||
- `handoff_occured`
|
||||
- `tool_called`
|
||||
- `tool_search_called`
|
||||
- `tool_search_output_created`
|
||||
- `tool_output`
|
||||
- `reasoning_item_created`
|
||||
- `mcp_approval_requested`
|
||||
- `mcp_approval_response`
|
||||
- `mcp_list_tools`
|
||||
|
||||
`handoff_occured`는 이전 버전과의 호환성을 위해 의도적으로 철자가 틀리게 작성되었습니다.
|
||||
|
||||
호스티드 툴 검색을 사용하면 모델이 도구 검색 요청을 발행할 때 `tool_search_called`가 발생하고, Responses API가 로드된 하위 집합을 반환할 때 `tool_search_output_created`가 발생합니다.
|
||||
|
||||
예를 들어, 다음은 원문 이벤트를 무시하고 사용자에게 업데이트를 스트리밍합니다.
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
import random
|
||||
from agents import Agent, ItemHelpers, Runner, function_tool
|
||||
|
||||
@function_tool
|
||||
def how_many_jokes() -> int:
|
||||
return random.randint(1, 10)
|
||||
|
||||
|
||||
async def main():
|
||||
agent = Agent(
|
||||
name="Joker",
|
||||
instructions="First call the `how_many_jokes` tool, then tell that many jokes.",
|
||||
tools=[how_many_jokes],
|
||||
)
|
||||
|
||||
result = Runner.run_streamed(
|
||||
agent,
|
||||
input="Hello",
|
||||
)
|
||||
print("=== Run starting ===")
|
||||
|
||||
async for event in result.stream_events():
|
||||
# We'll ignore the raw responses event deltas
|
||||
if event.type == "raw_response_event":
|
||||
continue
|
||||
# When the agent updates, print that
|
||||
elif event.type == "agent_updated_stream_event":
|
||||
print(f"Agent updated: {event.new_agent.name}")
|
||||
continue
|
||||
# When items are generated, print them
|
||||
elif event.type == "run_item_stream_event":
|
||||
if event.item.type == "tool_call_item":
|
||||
print("-- Tool was called")
|
||||
elif event.item.type == "tool_call_output_item":
|
||||
print(f"-- Tool output: {event.item.output}")
|
||||
elif event.item.type == "message_output_item":
|
||||
print(f"-- Message output:\n {ItemHelpers.text_message_output(event.item)}")
|
||||
else:
|
||||
pass # Ignore other event types
|
||||
|
||||
print("=== Run complete ===")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
```
|
||||
@@ -0,0 +1,834 @@
|
||||
---
|
||||
search:
|
||||
exclude: true
|
||||
---
|
||||
# 도구
|
||||
|
||||
도구를 사용하면 에이전트가 데이터 가져오기, 코드 실행, 외부 API 호출, 컴퓨터 사용 등의 작업을 수행할 수 있습니다. SDK는 다음 다섯 가지 카테고리를 지원합니다.
|
||||
|
||||
- OpenAI 호스티드 툴: OpenAI 서버에서 모델과 함께 실행됩니다.
|
||||
- 로컬/런타임 실행 도구: `ComputerTool`과 `ApplyPatchTool`은 항상 사용자의 환경에서 실행되며, `ShellTool`은 로컬 또는 호스팅된 컨테이너에서 실행할 수 있습니다.
|
||||
- Function Calling: 모든 Python 함수를 도구로 래핑합니다.
|
||||
- Agents as tools: 전체 핸드오프 없이 에이전트를 호출 가능한 도구로 노출합니다.
|
||||
- 실험적 기능: Codex 도구: 도구 호출을 통해 작업 공간 범위의 Codex 작업을 실행합니다.
|
||||
|
||||
## 도구 유형 선택
|
||||
|
||||
이 페이지를 카탈로그로 활용한 다음, 사용자가 제어하는 런타임에 해당하는 섹션으로 이동하세요.
|
||||
|
||||
| 원하는 작업 | 시작 위치 |
|
||||
| --- | --- |
|
||||
| OpenAI 관리형 도구 사용(웹 검색, 파일 검색, Code Interpreter, 호스팅된 MCP, 이미지 생성) | [호스티드 툴](#hosted-tools) |
|
||||
| 도구 검색을 사용하여 대규모 도구 집합을 런타임까지 지연 | [호스티드 툴 검색](#hosted-tool-search) |
|
||||
| 자체 프로세스 또는 환경에서 도구 실행 | [로컬 런타임 도구](#local-runtime-tools) |
|
||||
| Python 함수를 도구로 래핑 | [함수 도구](#function-tools) |
|
||||
| 핸드오프 없이 한 에이전트가 다른 에이전트를 호출하도록 설정 | [Agents as tools](#agents-as-tools) |
|
||||
| 에이전트에서 작업 공간 범위의 Codex 작업 실행 | [실험적 기능: Codex 도구](#experimental-codex-tool) |
|
||||
|
||||
## 호스티드 툴
|
||||
|
||||
OpenAI는 [`OpenAIResponsesModel`][agents.models.openai_responses.OpenAIResponsesModel]을 사용할 때 몇 가지 기본 제공 도구를 제공합니다.
|
||||
|
||||
- [`WebSearchTool`][agents.tool.WebSearchTool]을 사용하면 에이전트가 웹을 검색할 수 있습니다.
|
||||
- [`FileSearchTool`][agents.tool.FileSearchTool]을 사용하면 OpenAI 벡터 스토어에서 정보를 검색할 수 있습니다.
|
||||
- [`CodeInterpreterTool`][agents.tool.CodeInterpreterTool]을 사용하면 LLM이 샌드박스 환경에서 코드를 실행할 수 있습니다.
|
||||
- [`HostedMCPTool`][agents.tool.HostedMCPTool]은 원격 MCP 서버의 도구를 모델에 노출합니다.
|
||||
- [`ImageGenerationTool`][agents.tool.ImageGenerationTool]은 프롬프트에서 이미지를 생성합니다.
|
||||
- [`ToolSearchTool`][agents.tool.ToolSearchTool]을 사용하면 모델이 지연된 도구, 네임스페이스 또는 호스팅된 MCP 서버를 필요할 때 로드할 수 있습니다.
|
||||
|
||||
고급 호스팅 검색 옵션:
|
||||
|
||||
- `FileSearchTool`은 `vector_store_ids`와 `max_num_results` 외에도 `filters`, `ranking_options`, `include_search_results`를 지원합니다.
|
||||
- `WebSearchTool`은 `filters`, `user_location`, `search_context_size`를 지원합니다.
|
||||
|
||||
```python
|
||||
from agents import Agent, FileSearchTool, Runner, WebSearchTool
|
||||
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
tools=[
|
||||
WebSearchTool(),
|
||||
FileSearchTool(
|
||||
max_num_results=3,
|
||||
vector_store_ids=["VECTOR_STORE_ID"],
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
async def main():
|
||||
result = await Runner.run(agent, "Which coffee shop should I go to, taking into account my preferences and the weather today in SF?")
|
||||
print(result.final_output)
|
||||
```
|
||||
|
||||
### 호스티드 툴 검색
|
||||
|
||||
도구 검색을 사용하면 OpenAI Responses 모델이 대규모 도구 집합의 로드를 런타임까지 지연하여 현재 턴에 필요한 일부만 로드할 수 있습니다. 함수 도구, 네임스페이스 그룹 또는 호스팅된 MCP 서버가 많고 모든 도구를 미리 노출하지 않으면서 도구 스키마 토큰을 줄이려는 경우에 유용합니다.
|
||||
|
||||
에이전트를 빌드할 때 후보 도구가 이미 정해져 있다면 호스티드 툴 검색부터 사용하세요. 애플리케이션에서 로드할 항목을 동적으로 결정해야 하는 경우 Responses API는 클라이언트 실행형 도구 검색도 지원하지만, 표준 `Runner`는 이 모드를 자동으로 실행하지 않습니다.
|
||||
|
||||
```python
|
||||
from typing import Annotated
|
||||
|
||||
from agents import Agent, Runner, ToolSearchTool, function_tool, tool_namespace
|
||||
|
||||
|
||||
@function_tool(defer_loading=True)
|
||||
def get_customer_profile(
|
||||
customer_id: Annotated[str, "The customer ID to look up."],
|
||||
) -> str:
|
||||
"""Fetch a CRM customer profile."""
|
||||
return f"profile for {customer_id}"
|
||||
|
||||
|
||||
@function_tool(defer_loading=True)
|
||||
def list_open_orders(
|
||||
customer_id: Annotated[str, "The customer ID to look up."],
|
||||
) -> str:
|
||||
"""List open orders for a customer."""
|
||||
return f"open orders for {customer_id}"
|
||||
|
||||
|
||||
crm_tools = tool_namespace(
|
||||
name="crm",
|
||||
description="CRM tools for customer lookups.",
|
||||
tools=[get_customer_profile, list_open_orders],
|
||||
)
|
||||
|
||||
|
||||
agent = Agent(
|
||||
name="Operations assistant",
|
||||
model="gpt-5.6-sol",
|
||||
instructions="Load the crm namespace before using CRM tools.",
|
||||
tools=[*crm_tools, ToolSearchTool()],
|
||||
)
|
||||
|
||||
result = await Runner.run(agent, "Look up customer_42 and list their open orders.")
|
||||
print(result.final_output)
|
||||
```
|
||||
|
||||
알아둘 사항:
|
||||
|
||||
- 호스티드 툴 검색은 OpenAI Responses 모델에서만 사용할 수 있습니다. 현재 Python SDK 지원에는 `openai>=2.25.0`이 필요합니다.
|
||||
- 에이전트에 지연 로딩 대상을 구성할 때 `ToolSearchTool()`을 정확히 하나 추가하세요.
|
||||
- 검색 가능한 대상에는 `@function_tool(defer_loading=True)`, `tool_namespace(name=..., description=..., tools=[...])`, `HostedMCPTool(tool_config={..., "defer_loading": True})`가 포함됩니다.
|
||||
- 지연 로딩 함수 도구는 `ToolSearchTool()`과 함께 사용해야 합니다. 네임스페이스만 사용하는 구성에서도 모델이 필요할 때 적절한 그룹을 로드하도록 `ToolSearchTool()`을 사용할 수 있습니다.
|
||||
- `tool_namespace()`는 `FunctionTool` 인스턴스를 공유 네임스페이스 이름과 설명 아래에 그룹화합니다. 일반적으로 `crm`, `billing`, `shipping`처럼 관련 도구가 많은 경우에 가장 적합합니다.
|
||||
- OpenAI의 공식 모범 사례 지침은 [가능하면 네임스페이스 사용](https://developers.openai.com/api/docs/guides/tools-tool-search#use-namespaces-where-possible)입니다.
|
||||
- 가능하면 개별적으로 지연된 함수를 많이 사용하는 대신 네임스페이스 또는 호스팅된 MCP 서버를 사용하세요. 일반적으로 모델에 더 나은 상위 수준 검색 대상을 제공하고 토큰도 더 많이 절약합니다.
|
||||
- 네임스페이스에는 즉시 사용 가능한 도구와 지연된 도구를 함께 포함할 수 있습니다. `defer_loading=True`가 없는 도구는 즉시 호출할 수 있으며, 같은 네임스페이스의 지연된 도구는 도구 검색을 통해 로드됩니다.
|
||||
- 경험상 각 네임스페이스는 비교적 작게 유지하며, 이상적으로는 함수 수를 10개 미만으로 유지하세요.
|
||||
- 이름이 지정된 `tool_choice`는 네임스페이스 이름 자체나 지연 전용 도구를 대상으로 지정할 수 없습니다. `auto`, `required` 또는 실제 최상위 호출 가능 도구 이름을 사용하세요.
|
||||
- `ToolSearchTool(execution="client")`는 수동 Responses 오케스트레이션용입니다. 모델이 클라이언트 실행형 `tool_search_call`을 내보내면 표준 `Runner`는 이를 대신 실행하지 않고 오류를 발생시킵니다.
|
||||
- 도구 검색 활동은 [`RunResult.new_items`](results.md#new-items)와 [`RunItemStreamEvent`](streaming.md#run-item-event-names)에 전용 항목 및 이벤트 유형으로 표시됩니다.
|
||||
- 네임스페이스 로딩과 최상위 지연 도구를 모두 다루는 실행 가능한 전체 코드 예제는 `examples/tools/tool_search.py`를 참고하세요.
|
||||
- 공식 플랫폼 가이드: [도구 검색](https://developers.openai.com/api/docs/guides/tools-tool-search)
|
||||
|
||||
### 호스팅된 컨테이너 셸 및 스킬
|
||||
|
||||
`ShellTool`은 OpenAI 호스팅 컨테이너 실행도 지원합니다. 로컬 런타임 대신 관리형 컨테이너에서 모델이 셸 명령을 실행하도록 하려면 이 모드를 사용하세요.
|
||||
|
||||
```python
|
||||
from agents import Agent, Runner, ShellTool, ShellToolSkillReference
|
||||
|
||||
csv_skill: ShellToolSkillReference = {
|
||||
"type": "skill_reference",
|
||||
"skill_id": "skill_698bbe879adc81918725cbc69dcae7960bc5613dadaed377",
|
||||
"version": "1",
|
||||
}
|
||||
|
||||
agent = Agent(
|
||||
name="Container shell agent",
|
||||
model="gpt-5.6-sol",
|
||||
instructions="Use the mounted skill when helpful.",
|
||||
tools=[
|
||||
ShellTool(
|
||||
environment={
|
||||
"type": "container_auto",
|
||||
"network_policy": {"type": "disabled"},
|
||||
"skills": [csv_skill],
|
||||
}
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
"Use the configured skill to analyze CSV files in /mnt/data and summarize totals by region.",
|
||||
)
|
||||
print(result.final_output)
|
||||
```
|
||||
|
||||
이후 실행에서 기존 컨테이너를 재사용하려면 `environment={"type": "container_reference", "container_id": "cntr_..."}`를 설정하세요.
|
||||
|
||||
알아둘 사항:
|
||||
|
||||
- 호스팅된 셸은 Responses API 셸 도구를 통해 사용할 수 있습니다.
|
||||
- `container_auto`는 요청에 사용할 컨테이너를 프로비저닝하고, `container_reference`는 기존 컨테이너를 재사용합니다.
|
||||
- `container_auto`에는 `file_ids`와 `memory_limit`도 포함할 수 있습니다.
|
||||
- `environment.skills`는 스킬 참조와 인라인 스킬 번들을 허용합니다.
|
||||
- 호스팅된 환경에서는 `ShellTool`에 `executor`, `needs_approval`, `on_approval`을 설정하지 마세요.
|
||||
- `network_policy`는 `disabled` 및 `allowlist` 모드를 지원합니다.
|
||||
- 허용 목록 모드에서는 `network_policy.domain_secrets`가 이름을 기준으로 도메인 범위의 보안 비밀을 주입할 수 있습니다.
|
||||
- 전체 코드 예제는 `examples/tools/container_shell_skill_reference.py`와 `examples/tools/container_shell_inline_skill.py`를 참고하세요.
|
||||
- OpenAI 플랫폼 가이드: [셸](https://platform.openai.com/docs/guides/tools-shell) 및 [스킬](https://platform.openai.com/docs/guides/tools-skills)
|
||||
|
||||
## 로컬 런타임 도구
|
||||
|
||||
로컬 런타임 도구는 모델 응답 자체의 외부에서 실행됩니다. 모델은 여전히 도구를 호출할 시점을 결정하지만, 실제 작업은 애플리케이션 또는 구성된 실행 환경에서 수행합니다.
|
||||
|
||||
`ComputerTool`과 `ApplyPatchTool`에는 항상 사용자가 제공하는 로컬 구현이 필요합니다. `ShellTool`은 두 모드를 모두 지원합니다. 관리형 실행이 필요하면 위의 호스팅된 컨테이너 구성을 사용하고, 자체 프로세스에서 명령을 실행하려면 아래의 로컬 런타임 구성을 사용하세요.
|
||||
|
||||
로컬 런타임 도구에는 사용자가 구현을 제공해야 합니다.
|
||||
|
||||
- [`ComputerTool`][agents.tool.ComputerTool]: GUI/브라우저 자동화를 활성화하려면 [`Computer`][agents.computer.Computer] 또는 [`AsyncComputer`][agents.computer.AsyncComputer] 인터페이스를 구현합니다.
|
||||
- [`ShellTool`][agents.tool.ShellTool]: 로컬 실행과 호스팅된 컨테이너 실행을 모두 지원하는 최신 셸 도구입니다.
|
||||
- [`LocalShellTool`][agents.tool.LocalShellTool]: 기존 로컬 셸 통합입니다.
|
||||
- [`ApplyPatchTool`][agents.tool.ApplyPatchTool]: 로컬에서 diff를 적용하려면 [`ApplyPatchEditor`][agents.editor.ApplyPatchEditor]를 구현합니다.
|
||||
- 로컬 셸 스킬은 `ShellTool(environment={"type": "local", "skills": [...]})`과 함께 사용할 수 있습니다.
|
||||
|
||||
### ComputerTool과 Responses 컴퓨터 도구
|
||||
|
||||
`ComputerTool`은 여전히 로컬 하네스입니다. 사용자가 [`Computer`][agents.computer.Computer] 또는 [`AsyncComputer`][agents.computer.AsyncComputer] 구현을 제공하면 SDK가 해당 하네스를 OpenAI Responses API의 컴퓨터 인터페이스에 매핑합니다.
|
||||
|
||||
명시적인 [`gpt-5.5`](https://developers.openai.com/api/docs/models/gpt-5.5) 요청의 경우 SDK는 GA 기본 제공 도구 페이로드 `{"type": "computer"}`를 전송합니다. 이전 `computer-use-preview` 모델은 프리뷰 페이로드 `{"type": "computer_use_preview", "environment": ..., "display_width": ..., "display_height": ...}`를 계속 사용합니다. 이는 OpenAI의 [컴퓨터 사용 가이드](https://developers.openai.com/api/docs/guides/tools-computer-use/)에 설명된 플랫폼 마이그레이션을 반영합니다.
|
||||
|
||||
- 모델: `computer-use-preview` -> `gpt-5.5`
|
||||
- 도구 선택자: `computer_use_preview` -> `computer`
|
||||
- 컴퓨터 호출 형식: `computer_call`당 하나의 `action` -> `computer_call`의 일괄 처리된 `actions[]`
|
||||
- 잘림: 프리뷰 경로에서는 `ModelSettings(truncation="auto")` 필요 -> GA 경로에서는 불필요
|
||||
|
||||
SDK는 실제 Responses 요청의 유효 모델을 기준으로 해당 전송 형식을 선택합니다. 프롬프트 템플릿을 사용하며 프롬프트가 모델을 지정하기 때문에 요청에서 `model`을 생략하는 경우, `model="gpt-5.5"`를 명시적으로 유지하거나 `ModelSettings(tool_choice="computer")` 또는 `ModelSettings(tool_choice="computer_use")`로 GA 선택자를 강제하지 않으면 SDK는 프리뷰 호환 컴퓨터 페이로드를 유지합니다.
|
||||
|
||||
[`ComputerTool`][agents.tool.ComputerTool]이 있으면 `tool_choice="computer"`, `"computer_use"`, `"computer_use_preview"`가 모두 허용되며 유효 요청 모델과 일치하는 기본 제공 선택자로 정규화됩니다. `ComputerTool`이 없으면 이러한 문자열은 여전히 일반 함수 이름처럼 동작합니다.
|
||||
|
||||
이 차이는 `ComputerTool`이 [`ComputerProvider`][agents.tool.ComputerProvider] 팩토리를 기반으로 할 때 중요합니다. GA `computer` 페이로드는 직렬화 시점에 `environment`나 화면 크기가 필요하지 않으므로 확인되지 않은 팩토리도 사용할 수 있습니다. 프리뷰 호환 직렬화에는 SDK가 `environment`, `display_width`, `display_height`를 전송할 수 있도록 확인된 `Computer` 또는 `AsyncComputer` 인스턴스가 여전히 필요합니다.
|
||||
|
||||
런타임에서는 두 경로 모두 동일한 로컬 하네스를 사용합니다. 프리뷰 응답은 단일 `action`이 포함된 `computer_call` 항목을 내보냅니다. `gpt-5.5`는 일괄 처리된 `actions[]`를 내보낼 수 있으며, SDK는 `computer_call_output` 스크린샷 항목을 생성하기 전에 이를 순서대로 실행합니다. 실행 가능한 Playwright 기반 하네스는 `examples/tools/computer_use.py`를 참고하세요.
|
||||
|
||||
```python
|
||||
from agents import Agent, ApplyPatchTool, ShellTool
|
||||
from agents.computer import AsyncComputer
|
||||
from agents.editor import ApplyPatchResult, ApplyPatchOperation, ApplyPatchEditor
|
||||
|
||||
|
||||
class NoopComputer(AsyncComputer):
|
||||
environment = "browser"
|
||||
dimensions = (1024, 768)
|
||||
async def screenshot(self): return ""
|
||||
async def click(self, x, y, button): ...
|
||||
async def double_click(self, x, y): ...
|
||||
async def scroll(self, x, y, scroll_x, scroll_y): ...
|
||||
async def type(self, text): ...
|
||||
async def wait(self): ...
|
||||
async def move(self, x, y): ...
|
||||
async def keypress(self, keys): ...
|
||||
async def drag(self, path): ...
|
||||
|
||||
|
||||
class NoopEditor(ApplyPatchEditor):
|
||||
async def create_file(self, op: ApplyPatchOperation): return ApplyPatchResult(status="completed")
|
||||
async def update_file(self, op: ApplyPatchOperation): return ApplyPatchResult(status="completed")
|
||||
async def delete_file(self, op: ApplyPatchOperation): return ApplyPatchResult(status="completed")
|
||||
|
||||
|
||||
async def run_shell(request):
|
||||
return "shell output"
|
||||
|
||||
|
||||
agent = Agent(
|
||||
name="Local tools agent",
|
||||
tools=[
|
||||
ShellTool(executor=run_shell),
|
||||
ApplyPatchTool(editor=NoopEditor()),
|
||||
# ComputerTool expects a Computer/AsyncComputer implementation; omitted here for brevity.
|
||||
],
|
||||
)
|
||||
```
|
||||
|
||||
## 함수 도구
|
||||
|
||||
모든 Python 함수를 도구로 사용할 수 있습니다. Agents SDK가 도구를 자동으로 설정합니다.
|
||||
|
||||
- 도구 이름은 Python 함수의 이름이 됩니다. 또는 이름을 직접 지정할 수 있습니다.
|
||||
- 도구 설명은 함수의 docstring에서 가져옵니다. 또는 설명을 직접 지정할 수 있습니다.
|
||||
- 함수 입력의 스키마는 함수 인수에서 자동으로 생성됩니다.
|
||||
- 비활성화하지 않는 한 각 입력의 설명은 함수의 docstring에서 가져옵니다.
|
||||
|
||||
Python의 `inspect` 모듈을 사용하여 함수 시그니처를 추출하고, [`griffe`](https://mkdocstrings.github.io/griffe/)를 사용하여 docstring을 파싱하며, `pydantic`을 사용하여 스키마를 생성합니다.
|
||||
|
||||
OpenAI Responses 모델을 사용할 때 `@function_tool(defer_loading=True)`는 `ToolSearchTool()`이 로드할 때까지 함수 도구를 숨깁니다. [`tool_namespace()`][agents.tool.tool_namespace]를 사용하여 관련 함수 도구를 그룹화할 수도 있습니다. 전체 설정 및 제약 조건은 [호스티드 툴 검색](#hosted-tool-search)을 참고하세요.
|
||||
|
||||
```python
|
||||
import json
|
||||
|
||||
from typing_extensions import TypedDict, Any
|
||||
|
||||
from agents import Agent, FunctionTool, RunContextWrapper, function_tool
|
||||
|
||||
|
||||
class Location(TypedDict):
|
||||
lat: float
|
||||
long: float
|
||||
|
||||
@function_tool # (1)!
|
||||
async def fetch_weather(location: Location) -> str:
|
||||
# (2)!
|
||||
"""Fetch the weather for a given location.
|
||||
|
||||
Args:
|
||||
location: The location to fetch the weather for.
|
||||
"""
|
||||
# In real life, we'd fetch the weather from a weather API
|
||||
return "sunny"
|
||||
|
||||
|
||||
@function_tool(name_override="fetch_data") # (3)!
|
||||
def read_file(ctx: RunContextWrapper[Any], path: str, directory: str | None = None) -> str:
|
||||
"""Read the contents of a file.
|
||||
|
||||
Args:
|
||||
path: The path to the file to read.
|
||||
directory: The directory to read the file from.
|
||||
"""
|
||||
# In real life, we'd read the file from the file system
|
||||
return "<file contents>"
|
||||
|
||||
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
tools=[fetch_weather, read_file], # (4)!
|
||||
)
|
||||
|
||||
for tool in agent.tools:
|
||||
if isinstance(tool, FunctionTool):
|
||||
print(tool.name)
|
||||
print(tool.description)
|
||||
print(json.dumps(tool.params_json_schema, indent=2))
|
||||
print()
|
||||
|
||||
```
|
||||
|
||||
1. 모든 Python 유형을 함수 인수로 사용할 수 있으며, 함수는 동기 또는 비동기일 수 있습니다.
|
||||
2. Docstring이 있으면 설명과 인수 설명을 가져오는 데 사용됩니다.
|
||||
3. 함수는 선택적으로 `context`를 받을 수 있으며, 이 경우 반드시 첫 번째 인수여야 합니다. 도구 이름, 설명, 사용할 docstring 스타일 등의 재정의 값도 설정할 수 있습니다.
|
||||
4. 데코레이트된 함수를 도구 목록에 전달할 수 있습니다.
|
||||
|
||||
??? note "출력 펼쳐 보기"
|
||||
|
||||
```
|
||||
fetch_weather
|
||||
Fetch the weather for a given location.
|
||||
{
|
||||
"$defs": {
|
||||
"Location": {
|
||||
"properties": {
|
||||
"lat": {
|
||||
"title": "Lat",
|
||||
"type": "number"
|
||||
},
|
||||
"long": {
|
||||
"title": "Long",
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"lat",
|
||||
"long"
|
||||
],
|
||||
"title": "Location",
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"location": {
|
||||
"$ref": "#/$defs/Location",
|
||||
"description": "The location to fetch the weather for."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"location"
|
||||
],
|
||||
"title": "fetch_weather_args",
|
||||
"type": "object"
|
||||
}
|
||||
|
||||
fetch_data
|
||||
Read the contents of a file.
|
||||
{
|
||||
"properties": {
|
||||
"path": {
|
||||
"description": "The path to the file to read.",
|
||||
"title": "Path",
|
||||
"type": "string"
|
||||
},
|
||||
"directory": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"description": "The directory to read the file from.",
|
||||
"title": "Directory"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"path"
|
||||
],
|
||||
"title": "fetch_data_args",
|
||||
"type": "object"
|
||||
}
|
||||
```
|
||||
|
||||
### 함수 도구에서 이미지 또는 파일 반환
|
||||
|
||||
텍스트 출력뿐 아니라 하나 이상의 이미지나 파일을 함수 도구의 출력으로 반환할 수 있습니다. 이를 위해 다음 중 하나를 반환할 수 있습니다.
|
||||
|
||||
- 이미지: [`ToolOutputImage`][agents.tool.ToolOutputImage] 또는 TypedDict 버전인 [`ToolOutputImageDict`][agents.tool.ToolOutputImageDict]
|
||||
- 파일: [`ToolOutputFileContent`][agents.tool.ToolOutputFileContent] 또는 TypedDict 버전인 [`ToolOutputFileContentDict`][agents.tool.ToolOutputFileContentDict]
|
||||
- 텍스트: 문자열, 문자열로 변환 가능한 객체 또는 [`ToolOutputText`][agents.tool.ToolOutputText] 또는 TypedDict 버전인 [`ToolOutputTextDict`][agents.tool.ToolOutputTextDict]
|
||||
|
||||
### 사용자 지정 함수 도구
|
||||
|
||||
Python 함수를 도구로 사용하고 싶지 않은 경우도 있습니다. 원한다면 [`FunctionTool`][agents.tool.FunctionTool]을 직접 생성할 수 있습니다. 다음 항목을 제공해야 합니다.
|
||||
|
||||
- `name`
|
||||
- `description`
|
||||
- 인수의 JSON 스키마인 `params_json_schema`
|
||||
- [`ToolContext`][agents.tool_context.ToolContext]와 JSON 문자열 형식의 인수를 받아 도구 출력(예: 텍스트, 구조화된 도구 출력 객체 또는 출력 목록)을 반환하는 비동기 함수인 `on_invoke_tool`
|
||||
|
||||
```python
|
||||
from typing import Any
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from agents import RunContextWrapper, FunctionTool
|
||||
|
||||
|
||||
|
||||
def do_some_work(data: str) -> str:
|
||||
return "done"
|
||||
|
||||
|
||||
class FunctionArgs(BaseModel):
|
||||
username: str
|
||||
age: int
|
||||
|
||||
|
||||
async def run_function(ctx: RunContextWrapper[Any], args: str) -> str:
|
||||
parsed = FunctionArgs.model_validate_json(args)
|
||||
return do_some_work(data=f"{parsed.username} is {parsed.age} years old")
|
||||
|
||||
|
||||
tool = FunctionTool(
|
||||
name="process_user",
|
||||
description="Processes extracted user data",
|
||||
params_json_schema=FunctionArgs.model_json_schema(),
|
||||
on_invoke_tool=run_function,
|
||||
)
|
||||
```
|
||||
|
||||
### 자동 인수 및 docstring 파싱
|
||||
|
||||
앞서 설명한 것처럼 함수 시그니처를 자동으로 파싱하여 도구의 스키마를 추출하고, docstring을 파싱하여 도구 및 개별 인수의 설명을 추출합니다. 관련 참고 사항은 다음과 같습니다.
|
||||
|
||||
1. 시그니처 파싱은 `inspect` 모듈을 통해 수행됩니다. 타입 어노테이션을 사용하여 인수 유형을 파악하고, 전체 스키마를 나타내는 Pydantic 모델을 동적으로 빌드합니다. Python 기본 유형, Pydantic 모델, TypedDict 등을 포함한 대부분의 유형을 지원합니다.
|
||||
2. `griffe`를 사용하여 docstring을 파싱합니다. 지원되는 docstring 형식은 `google`, `sphinx`, `numpy`입니다. docstring 형식을 자동으로 감지하려고 시도하지만 이는 최선형 방식이며, `function_tool`을 호출할 때 형식을 명시적으로 설정할 수 있습니다. `use_docstring_info`를 `False`로 설정하여 docstring 파싱을 비활성화할 수도 있습니다.
|
||||
|
||||
스키마 추출 코드는 [`agents.function_schema`][]에 있습니다.
|
||||
|
||||
### Pydantic Field를 통한 인수 제약 및 설명
|
||||
|
||||
Pydantic의 [`Field`](https://docs.pydantic.dev/latest/concepts/fields/)를 사용하여 도구 인수에 제약 조건(예: 숫자의 최솟값/최댓값, 문자열의 길이 또는 패턴)과 설명을 추가할 수 있습니다. Pydantic과 마찬가지로 기본값 기반 형식(`arg: int = Field(..., ge=1)`)과 `Annotated` 형식(`arg: Annotated[int, Field(..., ge=1)]`)을 모두 지원합니다. 생성된 JSON 스키마와 유효성 검사에는 이러한 제약 조건이 포함됩니다.
|
||||
|
||||
```python
|
||||
from typing import Annotated
|
||||
from pydantic import Field
|
||||
from agents import function_tool
|
||||
|
||||
# Default-based form
|
||||
@function_tool
|
||||
def score_a(score: int = Field(..., ge=0, le=100, description="Score from 0 to 100")) -> str:
|
||||
return f"Score recorded: {score}"
|
||||
|
||||
# Annotated form
|
||||
@function_tool
|
||||
def score_b(score: Annotated[int, Field(..., ge=0, le=100, description="Score from 0 to 100")]) -> str:
|
||||
return f"Score recorded: {score}"
|
||||
```
|
||||
|
||||
### 함수 도구 타임아웃
|
||||
|
||||
`@function_tool(timeout=...)`을 사용하여 비동기 함수 도구에 호출별 타임아웃을 설정할 수 있습니다.
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
from agents import Agent, Runner, function_tool
|
||||
|
||||
|
||||
@function_tool(timeout=2.0)
|
||||
async def slow_lookup(query: str) -> str:
|
||||
await asyncio.sleep(10)
|
||||
return f"Result for {query}"
|
||||
|
||||
|
||||
agent = Agent(
|
||||
name="Timeout demo",
|
||||
instructions="Use tools when helpful.",
|
||||
tools=[slow_lookup],
|
||||
)
|
||||
```
|
||||
|
||||
타임아웃에 도달하면 기본 동작은 `timeout_behavior="error_as_result"`이며, 모델에 표시되는 타임아웃 메시지(예: `Tool 'slow_lookup' timed out after 2 seconds.`)를 전송합니다.
|
||||
|
||||
타임아웃 처리를 제어할 수 있습니다.
|
||||
|
||||
- `timeout_behavior="error_as_result"`(기본값): 모델이 복구할 수 있도록 타임아웃 메시지를 반환합니다.
|
||||
- `timeout_behavior="raise_exception"`: [`ToolTimeoutError`][agents.exceptions.ToolTimeoutError]를 발생시키고 실행을 실패 처리합니다.
|
||||
- `timeout_error_function=...`: `error_as_result`를 사용할 때 타임아웃 메시지를 사용자 지정합니다.
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
from agents import Agent, Runner, ToolTimeoutError, function_tool
|
||||
|
||||
|
||||
@function_tool(timeout=1.5, timeout_behavior="raise_exception")
|
||||
async def slow_tool() -> str:
|
||||
await asyncio.sleep(5)
|
||||
return "done"
|
||||
|
||||
|
||||
agent = Agent(name="Timeout hard-fail", tools=[slow_tool])
|
||||
|
||||
try:
|
||||
await Runner.run(agent, "Run the tool")
|
||||
except ToolTimeoutError as e:
|
||||
print(f"{e.tool_name} timed out in {e.timeout_seconds} seconds")
|
||||
```
|
||||
|
||||
!!! note
|
||||
|
||||
타임아웃 구성은 비동기 `@function_tool` 핸들러에서만 지원됩니다.
|
||||
|
||||
### 함수 도구의 오류 처리
|
||||
|
||||
`@function_tool`을 통해 함수 도구를 생성할 때 `failure_error_function`을 전달할 수 있습니다. 이는 도구 호출이 비정상 종료될 경우 LLM에 오류 응답을 제공하는 함수입니다.
|
||||
|
||||
- 기본적으로, 즉 아무것도 전달하지 않으면 오류가 발생했음을 LLM에 알리는 `default_tool_error_function`을 실행합니다.
|
||||
- 자체 오류 함수를 전달하면 해당 함수를 대신 실행하고 응답을 LLM에 전송합니다.
|
||||
- 명시적으로 `None`을 전달하면 모든 도구 호출 오류가 다시 발생하므로 직접 처리해야 합니다. 모델이 잘못된 JSON을 생성한 경우 `ModelBehaviorError`, 코드가 비정상 종료된 경우 `UserError` 등이 발생할 수 있습니다.
|
||||
|
||||
```python
|
||||
from agents import function_tool, RunContextWrapper
|
||||
from typing import Any
|
||||
|
||||
def my_custom_error_function(context: RunContextWrapper[Any], error: Exception) -> str:
|
||||
"""A custom function to provide a user-friendly error message."""
|
||||
print(f"A tool call failed with the following error: {error}")
|
||||
return "An internal server error occurred. Please try again later."
|
||||
|
||||
@function_tool(failure_error_function=my_custom_error_function)
|
||||
def get_user_profile(user_id: str) -> str:
|
||||
"""Fetches a user profile from a mock API.
|
||||
This function demonstrates a 'flaky' or failing API call.
|
||||
"""
|
||||
if user_id == "user_123":
|
||||
return "User profile for user_123 successfully retrieved."
|
||||
else:
|
||||
raise ValueError(f"Could not retrieve profile for user_id: {user_id}. API returned an error.")
|
||||
|
||||
```
|
||||
|
||||
`FunctionTool` 객체를 수동으로 생성하는 경우 `on_invoke_tool` 함수 내부에서 오류를 처리해야 합니다.
|
||||
|
||||
## Agents as tools
|
||||
|
||||
일부 워크플로에서는 제어를 핸드오프하는 대신 중앙 에이전트가 전문 에이전트 네트워크를 오케스트레이션하도록 할 수 있습니다. 에이전트를 도구로 모델링하여 이를 구현할 수 있습니다.
|
||||
|
||||
```python
|
||||
from agents import Agent, Runner
|
||||
import asyncio
|
||||
|
||||
spanish_agent = Agent(
|
||||
name="Spanish agent",
|
||||
instructions="You translate the user's message to Spanish",
|
||||
)
|
||||
|
||||
french_agent = Agent(
|
||||
name="French agent",
|
||||
instructions="You translate the user's message to French",
|
||||
)
|
||||
|
||||
orchestrator_agent = Agent(
|
||||
name="orchestrator_agent",
|
||||
instructions=(
|
||||
"You are a translation agent. You use the tools given to you to translate. "
|
||||
"If asked for multiple translations, you call the relevant tools."
|
||||
),
|
||||
tools=[
|
||||
spanish_agent.as_tool(
|
||||
tool_name="translate_to_spanish",
|
||||
tool_description="Translate the user's message to Spanish",
|
||||
),
|
||||
french_agent.as_tool(
|
||||
tool_name="translate_to_french",
|
||||
tool_description="Translate the user's message to French",
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
async def main():
|
||||
result = await Runner.run(orchestrator_agent, input="Say 'Hello, how are you?' in Spanish.")
|
||||
print(result.final_output)
|
||||
```
|
||||
|
||||
### 도구 에이전트 사용자 지정
|
||||
|
||||
`agent.as_tool` 함수는 에이전트를 도구로 쉽게 변환할 수 있는 편의 메서드입니다. `max_turns`, `run_config`, `hooks`, `previous_response_id`, `conversation_id`, `session`, `needs_approval` 등의 일반적인 런타임 옵션을 지원합니다. 또한 `parameters`, `input_builder`, `include_input_schema`를 사용하는 구조화된 입력도 지원합니다.
|
||||
|
||||
상태 옵션은 도구 호출로 시작되는 중첩 에이전트 실행을 구성합니다. 상위 실행의 대화 상태는 자동으로 상속되지 않습니다. 상위 실행과 중첩 실행 간에 클라이언트 관리형 기록을 공유하려면 동일한 `session`을 양쪽에 명시적으로 전달하세요. `Runner.run`과 마찬가지로 중첩 실행에는 하나의 상태 전략을 선택하세요. 클라이언트 관리형 `session` 또는 `previous_response_id`나 `conversation_id`를 통한 서버 관리형 이어가기 중 하나를 사용합니다.
|
||||
|
||||
```python
|
||||
@function_tool
|
||||
async def run_my_agent() -> str:
|
||||
"""A tool that runs the agent with custom configs"""
|
||||
|
||||
agent = Agent(name="My agent", instructions="...")
|
||||
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
input="...",
|
||||
max_turns=5,
|
||||
run_config=...
|
||||
)
|
||||
|
||||
return str(result.final_output)
|
||||
```
|
||||
|
||||
### 도구 에이전트의 구조화된 입력
|
||||
|
||||
기본적으로 `Agent.as_tool()`은 단일 문자열 입력(`{"input": "..."}`)을 기대하지만, `parameters`에 Pydantic 모델 또는 dataclass 유형을 전달하여 구조화된 스키마를 노출할 수 있습니다.
|
||||
|
||||
추가 옵션:
|
||||
|
||||
- `include_input_schema=True`는 생성된 중첩 입력에 전체 JSON 스키마를 포함합니다.
|
||||
- `input_builder=...`를 사용하면 구조화된 도구 인수를 중첩 에이전트 입력으로 변환하는 방식을 완전히 사용자 지정할 수 있습니다.
|
||||
- `RunContextWrapper.tool_input`에는 중첩 실행 컨텍스트 내부에서 파싱된 구조화 페이로드가 포함됩니다.
|
||||
|
||||
```python
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class TranslationInput(BaseModel):
|
||||
text: str = Field(description="Text to translate.")
|
||||
source: str = Field(description="Source language.")
|
||||
target: str = Field(description="Target language.")
|
||||
|
||||
|
||||
translator_tool = translator_agent.as_tool(
|
||||
tool_name="translate_text",
|
||||
tool_description="Translate text between languages.",
|
||||
parameters=TranslationInput,
|
||||
include_input_schema=True,
|
||||
)
|
||||
```
|
||||
|
||||
실행 가능한 전체 코드 예제는 `examples/agent_patterns/agents_as_tools_structured.py`를 참고하세요.
|
||||
|
||||
### 도구 에이전트의 승인 게이트
|
||||
|
||||
`Agent.as_tool(..., needs_approval=...)`은 `function_tool`과 동일한 승인 흐름을 사용합니다. 승인이 필요하면 실행이 일시 중지되고 보류 중인 항목이 `result.interruptions`에 표시됩니다. 그런 다음 `result.to_state()`를 사용하고 `state.approve(...)` 또는 `state.reject(...)`를 호출한 후 실행을 재개하세요. 전체 일시 중지/재개 패턴은 [휴먼인더루프 (HITL) 가이드](human_in_the_loop.md)를 참고하세요.
|
||||
|
||||
### 사용자 지정 출력 추출
|
||||
|
||||
경우에 따라 도구 에이전트의 출력을 중앙 에이전트에 반환하기 전에 수정할 수 있습니다. 다음과 같은 경우에 유용합니다.
|
||||
|
||||
- 하위 에이전트의 채팅 기록에서 특정 정보(예: JSON 페이로드)를 추출
|
||||
- 에이전트의 최종 답변을 변환하거나 형식 변경(예: Markdown을 일반 텍스트 또는 CSV로 변환)
|
||||
- 출력의 유효성을 검사하거나 에이전트 응답이 없거나 형식이 잘못된 경우 대체 값 제공
|
||||
|
||||
`as_tool` 메서드에 `custom_output_extractor` 인수를 제공하여 이를 수행할 수 있습니다.
|
||||
|
||||
```python
|
||||
async def extract_json_payload(run_result: RunResult) -> str:
|
||||
# Scan the agent’s outputs in reverse order until we find a JSON-like message from a tool call.
|
||||
for item in reversed(run_result.new_items):
|
||||
if isinstance(item, ToolCallOutputItem) and item.output.strip().startswith("{"):
|
||||
return item.output.strip()
|
||||
# Fallback to an empty JSON object if nothing was found
|
||||
return "{}"
|
||||
|
||||
|
||||
json_tool = data_agent.as_tool(
|
||||
tool_name="get_data_json",
|
||||
tool_description="Run the data agent and return only its JSON payload",
|
||||
custom_output_extractor=extract_json_payload,
|
||||
)
|
||||
```
|
||||
|
||||
사용자 지정 추출기 내부에서 중첩된 [`RunResult`][agents.result.RunResult]는 [`agent_tool_invocation`][agents.result.RunResultBase.agent_tool_invocation]도 노출합니다. 중첩된 결과를 후처리하면서 외부 도구 이름, 호출 ID 또는 원문 인수가 필요할 때 유용합니다. [결과 가이드](results.md#agent-as-tool-metadata)를 참고하세요.
|
||||
|
||||
### 중첩 에이전트 실행 스트리밍
|
||||
|
||||
`as_tool`에 `on_stream` 콜백을 전달하면 중첩 에이전트가 내보내는 스트리밍 이벤트를 수신하면서도 스트림이 완료된 후 최종 출력을 반환할 수 있습니다.
|
||||
|
||||
```python
|
||||
from agents import AgentToolStreamEvent
|
||||
|
||||
|
||||
async def handle_stream(event: AgentToolStreamEvent) -> None:
|
||||
# Inspect the underlying StreamEvent along with agent metadata.
|
||||
print(f"[stream] {event['agent'].name} :: {event['event'].type}")
|
||||
|
||||
|
||||
billing_agent_tool = billing_agent.as_tool(
|
||||
tool_name="billing_helper",
|
||||
tool_description="Answer billing questions.",
|
||||
on_stream=handle_stream, # Can be sync or async.
|
||||
)
|
||||
```
|
||||
|
||||
예상 동작:
|
||||
|
||||
- 이벤트 유형은 `StreamEvent["type"]`의 `raw_response_event`, `run_item_stream_event`, `agent_updated_stream_event`와 동일합니다.
|
||||
- `on_stream`을 제공하면 중첩 에이전트가 자동으로 스트리밍 모드에서 실행되고, 최종 출력을 반환하기 전에 스트림이 모두 처리됩니다.
|
||||
- 핸들러는 동기 또는 비동기일 수 있으며, 각 이벤트는 도착하는 순서대로 전달됩니다.
|
||||
- 모델 도구 호출을 통해 도구가 호출되면 `tool_call`이 존재합니다. 직접 호출에서는 `None`일 수 있습니다.
|
||||
- 실행 가능한 전체 샘플은 `examples/agent_patterns/agents_as_tools_streaming.py`를 참고하세요.
|
||||
|
||||
### 조건부 도구 활성화
|
||||
|
||||
`is_enabled` 매개변수를 사용하여 런타임에 에이전트 도구를 조건부로 활성화하거나 비활성화할 수 있습니다. 이를 통해 컨텍스트, 사용자 기본 설정 또는 런타임 조건에 따라 LLM에서 사용할 수 있는 도구를 동적으로 필터링할 수 있습니다.
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
from agents import Agent, AgentBase, Runner, RunContextWrapper
|
||||
from pydantic import BaseModel
|
||||
|
||||
class LanguageContext(BaseModel):
|
||||
language_preference: str = "french_spanish"
|
||||
|
||||
def french_enabled(ctx: RunContextWrapper[LanguageContext], agent: AgentBase) -> bool:
|
||||
"""Enable French for French+Spanish preference."""
|
||||
return ctx.context.language_preference == "french_spanish"
|
||||
|
||||
# Create specialized agents
|
||||
spanish_agent = Agent(
|
||||
name="spanish_agent",
|
||||
instructions="You respond in Spanish. Always reply to the user's question in Spanish.",
|
||||
)
|
||||
|
||||
french_agent = Agent(
|
||||
name="french_agent",
|
||||
instructions="You respond in French. Always reply to the user's question in French.",
|
||||
)
|
||||
|
||||
# Create orchestrator with conditional tools
|
||||
orchestrator = Agent(
|
||||
name="orchestrator",
|
||||
instructions=(
|
||||
"You are a multilingual assistant. You use the tools given to you to respond to users. "
|
||||
"You must call ALL available tools to provide responses in different languages. "
|
||||
"You never respond in languages yourself, you always use the provided tools."
|
||||
),
|
||||
tools=[
|
||||
spanish_agent.as_tool(
|
||||
tool_name="respond_spanish",
|
||||
tool_description="Respond to the user's question in Spanish",
|
||||
is_enabled=True, # Always enabled
|
||||
),
|
||||
french_agent.as_tool(
|
||||
tool_name="respond_french",
|
||||
tool_description="Respond to the user's question in French",
|
||||
is_enabled=french_enabled,
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
async def main():
|
||||
context = RunContextWrapper(LanguageContext(language_preference="french_spanish"))
|
||||
result = await Runner.run(orchestrator, "How are you?", context=context.context)
|
||||
print(result.final_output)
|
||||
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
`is_enabled` 매개변수는 다음을 허용합니다.
|
||||
|
||||
- **불리언 값**: `True`(항상 활성화) 또는 `False`(항상 비활성화)
|
||||
- **호출 가능 함수**: `(context, agent)`를 받아 불리언 값을 반환하는 함수
|
||||
- **비동기 함수**: 복잡한 조건부 로직을 위한 비동기 함수
|
||||
|
||||
비활성화된 도구는 런타임에 LLM에서 완전히 숨겨지므로 다음과 같은 용도로 유용합니다.
|
||||
|
||||
- 사용자 권한에 따른 기능 게이팅
|
||||
- 환경별 도구 가용성(개발 환경과 프로덕션 환경)
|
||||
- 서로 다른 도구 구성에 대한 A/B 테스트
|
||||
- 런타임 상태에 따른 동적 도구 필터링
|
||||
|
||||
## 실험적 기능: Codex 도구
|
||||
|
||||
`codex_tool`은 Codex CLI를 래핑하여 에이전트가 도구 호출 중에 작업 공간 범위의 작업(셸, 파일 편집, MCP 도구)을 실행할 수 있게 합니다. 이 기능은 실험적이며 변경될 수 있습니다.
|
||||
|
||||
기본 에이전트가 현재 실행을 벗어나지 않고 범위가 제한된 작업 공간 작업을 Codex에 위임하도록 하려면 이 도구를 사용하세요. 기본 도구 이름은 `codex`입니다. 사용자 지정 이름을 설정하는 경우 이름은 `codex`이거나 `codex_`로 시작해야 합니다. 에이전트에 여러 Codex 도구가 포함된 경우 각 도구는 고유한 이름을 사용해야 합니다.
|
||||
|
||||
```python
|
||||
from agents import Agent
|
||||
from agents.extensions.experimental.codex import ThreadOptions, TurnOptions, codex_tool
|
||||
|
||||
agent = Agent(
|
||||
name="Codex Agent",
|
||||
instructions="Use the codex tool to inspect the workspace and answer the question.",
|
||||
tools=[
|
||||
codex_tool(
|
||||
sandbox_mode="workspace-write",
|
||||
working_directory="/path/to/repo",
|
||||
default_thread_options=ThreadOptions(
|
||||
model="gpt-5.5",
|
||||
model_reasoning_effort="low",
|
||||
network_access_enabled=True,
|
||||
web_search_mode="disabled",
|
||||
approval_policy="never",
|
||||
),
|
||||
default_turn_options=TurnOptions(
|
||||
idle_timeout_seconds=60,
|
||||
),
|
||||
persist_session=True,
|
||||
)
|
||||
],
|
||||
)
|
||||
```
|
||||
|
||||
다음 옵션 그룹부터 시작하세요.
|
||||
|
||||
- 실행 범위: `sandbox_mode`와 `working_directory`는 Codex가 작업할 수 있는 위치를 정의합니다. 두 옵션을 함께 사용하고 작업 디렉터리가 Git 저장소 내부에 없으면 `skip_git_repo_check=True`를 설정하세요.
|
||||
- 스레드 기본값: `default_thread_options=ThreadOptions(...)`는 모델, 추론 노력 수준, 승인 정책, 추가 디렉터리, 네트워크 액세스, 웹 검색 모드를 구성합니다. 기존 `web_search_enabled`보다 `web_search_mode`를 우선 사용하세요.
|
||||
- 턴 기본값: `default_turn_options=TurnOptions(...)`는 `idle_timeout_seconds` 및 선택적 취소 `signal`과 같은 턴별 동작을 구성합니다.
|
||||
- 도구 I/O: 도구 호출에는 `{ "type": "text", "text": ... }` 또는 `{ "type": "local_image", "path": ... }` 형식의 `inputs` 항목이 하나 이상 포함되어야 합니다. `output_schema`를 사용하면 구조화된 Codex 응답을 요구할 수 있습니다.
|
||||
|
||||
스레드 재사용과 지속성은 별도의 제어 항목입니다.
|
||||
|
||||
- `persist_session=True`는 동일한 도구 인스턴스를 반복 호출할 때 하나의 Codex 스레드를 재사용합니다.
|
||||
- `use_run_context_thread_id=True`는 동일한 변경 가능 컨텍스트 객체를 공유하는 여러 실행에서 실행 컨텍스트에 스레드 ID를 저장하고 재사용합니다.
|
||||
- 스레드 ID의 우선순위는 호출별 `thread_id`, 실행 컨텍스트 스레드 ID(활성화된 경우), 구성된 `thread_id` 옵션 순입니다.
|
||||
- 기본 실행 컨텍스트 키는 `name="codex"`일 때 `codex_thread_id`이고, `name="codex_<suffix>"`일 때 `codex_thread_id_<suffix>`입니다. `run_context_thread_id_key`를 사용하여 재정의할 수 있습니다.
|
||||
|
||||
런타임 구성:
|
||||
|
||||
- 인증: `CODEX_API_KEY`(권장) 또는 `OPENAI_API_KEY`를 설정하거나 `codex_options={"api_key": "..."}`를 전달합니다.
|
||||
- 런타임: `codex_options.base_url`은 CLI 기본 URL을 재정의합니다.
|
||||
- 바이너리 확인: CLI 경로를 고정하려면 `codex_options.codex_path_override` 또는 `CODEX_PATH`를 설정합니다. 그렇지 않으면 SDK는 `PATH`에서 `codex`를 찾은 다음 번들로 제공되는 벤더 바이너리를 대신 사용합니다.
|
||||
- 환경: `codex_options.env`는 하위 프로세스 환경을 완전히 제어합니다. 이 옵션이 제공되면 하위 프로세스는 `os.environ`을 상속하지 않습니다.
|
||||
- 스트림 제한: `codex_options.codex_subprocess_stream_limit_bytes` 또는 `OPENAI_AGENTS_CODEX_SUBPROCESS_STREAM_LIMIT_BYTES`는 stdout/stderr 리더 제한을 제어합니다. 유효 범위는 `65536`~`67108864`이며 기본값은 `8388608`입니다.
|
||||
- 스트리밍: `on_stream`은 스레드/턴 수명 주기 이벤트와 항목 이벤트(`reasoning`, `command_execution`, `mcp_tool_call`, `file_change`, `web_search`, `todo_list`, `error` 항목 업데이트)를 수신합니다.
|
||||
- 출력: 결과에는 `response`, `usage`, `thread_id`가 포함되며, 사용량은 `RunContextWrapper.usage`에 추가됩니다.
|
||||
|
||||
참조:
|
||||
|
||||
- [Codex 도구 API 레퍼런스](ref/extensions/experimental/codex/codex_tool.md)
|
||||
- [ThreadOptions 레퍼런스](ref/extensions/experimental/codex/thread_options.md)
|
||||
- [TurnOptions 레퍼런스](ref/extensions/experimental/codex/turn_options.md)
|
||||
- 실행 가능한 전체 샘플은 `examples/tools/codex.py`와 `examples/tools/codex_same_thread.py`를 참고하세요.
|
||||
@@ -0,0 +1,223 @@
|
||||
---
|
||||
search:
|
||||
exclude: true
|
||||
---
|
||||
# 트레이싱
|
||||
|
||||
Agents SDK에는 기본 제공 트레이싱이 포함되어 있으며, 에이전트 실행 중 발생하는 이벤트의 포괄적인 기록을 수집합니다: LLM 생성, 도구 호출, 핸드오프, 가드레일, 그리고 발생하는 사용자 지정 이벤트까지 포함됩니다. [Traces 대시보드](https://platform.openai.com/traces)를 사용하면 개발 중과 프로덕션에서 워크플로를 디버그하고, 시각화하고, 모니터링할 수 있습니다.
|
||||
|
||||
!!!note
|
||||
|
||||
트레이싱은 기본적으로 활성화되어 있습니다. 다음 세 가지 일반적인 방법으로 비활성화할 수 있습니다:
|
||||
|
||||
1. 환경 변수 `OPENAI_AGENTS_DISABLE_TRACING=1`을 설정하여 트레이싱을 전역적으로 비활성화할 수 있습니다
|
||||
2. 코드에서 [`set_tracing_disabled(True)`][agents.set_tracing_disabled]를 사용하여 트레이싱을 전역적으로 비활성화할 수 있습니다
|
||||
3. [`agents.run.RunConfig.tracing_disabled`][]를 `True`로 설정하여 단일 실행에 대해 트레이싱을 비활성화할 수 있습니다
|
||||
|
||||
***제로 데이터 보존(Zero Data Retention, ZDR) 정책에 따라 OpenAI API를 사용하는 조직에서는 트레이싱을 사용할 수 없습니다.***
|
||||
|
||||
## 트레이스와 스팬
|
||||
|
||||
- **트레이스**는 "워크플로"의 단일 엔드투엔드 작업을 나타냅니다. 트레이스는 스팬으로 구성됩니다. 트레이스에는 다음 속성이 있습니다:
|
||||
- `workflow_name`: 논리적 워크플로 또는 앱입니다. 예: "코드 생성" 또는 "고객 서비스".
|
||||
- `trace_id`: 트레이스의 고유 ID입니다. 전달하지 않으면 자동으로 생성됩니다. 형식은 `trace_<32_alphanumeric>`이어야 합니다.
|
||||
- `group_id`: 선택적 그룹 ID로, 동일한 대화의 여러 트레이스를 연결하는 데 사용합니다. 예를 들어 채팅 스레드 ID를 사용할 수 있습니다.
|
||||
- `disabled`: True인 경우 트레이스가 기록되지 않습니다.
|
||||
- `metadata`: 트레이스의 선택적 메타데이터입니다.
|
||||
- **스팬**은 시작 시간과 종료 시간이 있는 작업을 나타냅니다. 스팬에는 다음이 있습니다:
|
||||
- `started_at` 및 `ended_at` 타임스탬프.
|
||||
- `trace_id`, 소속된 트레이스를 나타냅니다
|
||||
- `parent_id`, 이 스팬의 부모 스팬을 가리킵니다(있는 경우)
|
||||
- `span_data`, 스팬에 대한 정보입니다. 예를 들어 `AgentSpanData`에는 에이전트에 대한 정보가 포함되고, `GenerationSpanData`에는 LLM 생성에 대한 정보가 포함되는 식입니다.
|
||||
|
||||
## 기본 트레이싱
|
||||
|
||||
기본적으로 SDK는 다음을 트레이싱합니다:
|
||||
|
||||
- 전체 `Runner.{run, run_sync, run_streamed}()`가 `trace()`로 래핑됩니다.
|
||||
- 에이전트가 실행될 때마다 `agent_span()`로 래핑됩니다
|
||||
- LLM 생성은 `generation_span()`로 래핑됩니다
|
||||
- 함수 도구 호출은 각각 `function_span()`로 래핑됩니다
|
||||
- 가드레일은 `guardrail_span()`로 래핑됩니다
|
||||
- 핸드오프는 `handoff_span()`로 래핑됩니다
|
||||
- 오디오 입력(음성-텍스트 변환)은 `transcription_span()`로 래핑됩니다
|
||||
- 오디오 출력(텍스트-음성 변환)은 `speech_span()`로 래핑됩니다
|
||||
- 관련 오디오 스팬은 `speech_group_span()` 아래에 부모-자식 관계로 배치될 수 있습니다
|
||||
|
||||
기본적으로 트레이스 이름은 "Agent workflow"입니다. `trace`를 사용하는 경우 이 이름을 설정할 수 있으며, [`RunConfig`][agents.run.RunConfig]로 이름과 기타 속성을 구성할 수도 있습니다.
|
||||
|
||||
또한 트레이스를 다른 대상으로 푸시하도록 [사용자 지정 트레이싱 프로세서](#custom-tracing-processors)를 설정할 수 있습니다(대체 대상 또는 보조 대상으로).
|
||||
|
||||
## 장기 실행 워커와 즉시 내보내기
|
||||
|
||||
기본 [`BatchTraceProcessor`][agents.tracing.processors.BatchTraceProcessor]는 몇 초마다 백그라운드에서 트레이스를 내보내거나, 인메모리 큐가 크기 기준에 도달하면 더 빨리 내보내며, 프로세스 종료 시 최종 플러시도 수행합니다. Celery, RQ, Dramatiq 또는 FastAPI 백그라운드 작업과 같은 장기 실행 워커에서는 일반적으로 추가 코드 없이 트레이스가 자동으로 내보내지지만, 각 작업이 끝난 직후 Traces 대시보드에 바로 표시되지 않을 수 있습니다.
|
||||
|
||||
작업 단위가 끝날 때 즉시 전달 보장이 필요하다면, 트레이스 컨텍스트가 종료된 후 [`flush_traces()`][agents.tracing.flush_traces]를 호출하세요.
|
||||
|
||||
```python
|
||||
from agents import Runner, flush_traces, trace
|
||||
|
||||
|
||||
@celery_app.task
|
||||
def run_agent_task(prompt: str):
|
||||
try:
|
||||
with trace("celery_task"):
|
||||
result = Runner.run_sync(agent, prompt)
|
||||
return result.final_output
|
||||
finally:
|
||||
flush_traces()
|
||||
```
|
||||
|
||||
```python
|
||||
from fastapi import BackgroundTasks, FastAPI
|
||||
from agents import Runner, flush_traces, trace
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
def process_in_background(prompt: str) -> None:
|
||||
try:
|
||||
with trace("background_job"):
|
||||
Runner.run_sync(agent, prompt)
|
||||
finally:
|
||||
flush_traces()
|
||||
|
||||
|
||||
@app.post("/run")
|
||||
async def run(prompt: str, background_tasks: BackgroundTasks):
|
||||
background_tasks.add_task(process_in_background, prompt)
|
||||
return {"status": "queued"}
|
||||
```
|
||||
|
||||
[`flush_traces()`][agents.tracing.flush_traces]는 현재 버퍼링된 트레이스와 스팬이 내보내질 때까지 차단하므로, 부분적으로 구성된 트레이스를 플러시하지 않도록 `trace()`가 닫힌 후 호출하세요. 기본 내보내기 지연 시간이 허용 가능하다면 이 호출은 생략할 수 있습니다.
|
||||
|
||||
## 상위 수준 트레이스
|
||||
|
||||
때로는 `run()`에 대한 여러 호출을 단일 트레이스의 일부로 만들고 싶을 수 있습니다. 전체 코드를 `trace()`로 래핑하면 됩니다.
|
||||
|
||||
```python
|
||||
from agents import Agent, Runner, trace
|
||||
|
||||
async def main():
|
||||
agent = Agent(name="Joke generator", instructions="Tell funny jokes.")
|
||||
|
||||
with trace("Joke workflow"): # (1)!
|
||||
first_result = await Runner.run(agent, "Tell me a joke")
|
||||
second_result = await Runner.run(agent, f"Rate this joke: {first_result.final_output}")
|
||||
print(f"Joke: {first_result.final_output}")
|
||||
print(f"Rating: {second_result.final_output}")
|
||||
```
|
||||
|
||||
1. 두 `Runner.run` 호출이 `with trace()`로 래핑되어 있으므로, 개별 실행은 두 개의 트레이스를 생성하는 대신 전체 트레이스의 일부가 됩니다.
|
||||
|
||||
## 트레이스 생성
|
||||
|
||||
[`trace()`][agents.tracing.trace] 함수를 사용하여 트레이스를 생성할 수 있습니다. 트레이스는 시작되고 종료되어야 합니다. 이를 위한 두 가지 옵션이 있습니다:
|
||||
|
||||
1. **권장**: 트레이스를 컨텍스트 매니저로 사용합니다. 즉, `with trace(...) as my_trace`를 사용합니다. 이렇게 하면 적절한 시점에 트레이스가 자동으로 시작되고 종료됩니다.
|
||||
2. [`trace.start()`][agents.tracing.Trace.start]와 [`trace.finish()`][agents.tracing.Trace.finish]를 수동으로 호출할 수도 있습니다.
|
||||
|
||||
현재 트레이스는 Python [`contextvar`](https://docs.python.org/3/library/contextvars.html)를 통해 추적됩니다. 즉, 동시성 환경에서도 자동으로 작동합니다. 트레이스를 수동으로 시작/종료하는 경우, 현재 트레이스를 업데이트하려면 `start()`/`finish()`에 `mark_as_current` 및 `reset_current`를 전달해야 합니다.
|
||||
|
||||
## 스팬 생성
|
||||
|
||||
다양한 [`*_span()`][agents.tracing.create] 메서드를 사용하여 스팬을 생성할 수 있습니다. 일반적으로 스팬을 수동으로 생성할 필요는 없습니다. 사용자 지정 스팬 정보를 추적하기 위한 [`custom_span()`][agents.tracing.custom_span] 함수가 제공됩니다.
|
||||
|
||||
스팬은 자동으로 현재 트레이스의 일부가 되며, Python [`contextvar`](https://docs.python.org/3/library/contextvars.html)를 통해 추적되는 가장 가까운 현재 스팬 아래에 중첩됩니다.
|
||||
|
||||
## 민감한 데이터
|
||||
|
||||
일부 스팬은 민감할 가능성이 있는 데이터를 캡처할 수 있습니다.
|
||||
|
||||
`generation_span()`은 LLM 생성의 입력/출력을 저장하고, `function_span()`은 함수 호출의 입력/출력을 저장합니다. 여기에는 민감한 데이터가 포함될 수 있으므로, [`RunConfig.trace_include_sensitive_data`][agents.run.RunConfig.trace_include_sensitive_data]를 통해 해당 데이터 캡처를 비활성화할 수 있습니다.
|
||||
|
||||
마찬가지로 오디오 스팬에는 기본적으로 입력 및 출력 오디오에 대한 base64 인코딩된 PCM 데이터가 포함됩니다. [`VoicePipelineConfig.trace_include_sensitive_audio_data`][agents.voice.pipeline_config.VoicePipelineConfig.trace_include_sensitive_audio_data]를 구성하여 이 오디오 데이터 캡처를 비활성화할 수 있습니다.
|
||||
|
||||
기본적으로 `trace_include_sensitive_data`는 `True`입니다. 앱을 실행하기 전에 `OPENAI_AGENTS_TRACE_INCLUDE_SENSITIVE_DATA` 환경 변수를 `true/1` 또는 `false/0`로 내보내면 코드 없이 기본값을 설정할 수 있습니다.
|
||||
|
||||
## 사용자 지정 트레이싱 프로세서
|
||||
|
||||
트레이싱의 상위 수준 아키텍처는 다음과 같습니다:
|
||||
|
||||
- 초기화 시 전역 [`TraceProvider`][agents.tracing.setup.TraceProvider]가 생성되며, 이는 트레이스 생성을 담당합니다.
|
||||
- `TraceProvider`는 트레이스/스팬을 일괄적으로 [`BackendSpanExporter`][agents.tracing.processors.BackendSpanExporter]로 보내는 [`BatchTraceProcessor`][agents.tracing.processors.BatchTraceProcessor]로 구성됩니다. 이 익스포터는 스팬과 트레이스를 OpenAI 백엔드로 일괄 내보냅니다.
|
||||
|
||||
트레이스를 대체 또는 추가 백엔드로 보내거나 익스포터 동작을 수정하기 위해 이 기본 설정을 사용자 지정하려면 두 가지 옵션이 있습니다:
|
||||
|
||||
1. [`add_trace_processor()`][agents.tracing.add_trace_processor]를 사용하면 트레이스와 스팬이 준비되는 대로 수신할 **추가** 트레이싱 프로세서를 추가할 수 있습니다. 이를 통해 트레이스를 OpenAI 백엔드로 보내는 것에 더해 자체 처리를 수행할 수 있습니다.
|
||||
2. [`set_trace_processors()`][agents.tracing.set_trace_processors]를 사용하면 기본 프로세서를 자체 트레이싱 프로세서로 **교체**할 수 있습니다. 즉, 이를 수행하는 `TracingProcessor`를 포함하지 않는 한 트레이스는 OpenAI 백엔드로 전송되지 않습니다.
|
||||
|
||||
|
||||
## 비OpenAI 모델에서의 트레이싱
|
||||
|
||||
OpenAI API 키를 비OpenAI 모델과 함께 사용하면 트레이싱을 비활성화할 필요 없이 OpenAI Traces 대시보드에서 무료 트레이싱을 활성화할 수 있습니다. 어댑터 선택과 설정 시 주의 사항은 모델 가이드의 [서드파티 어댑터](models/index.md#third-party-adapters) 섹션을 참조하세요.
|
||||
|
||||
```python
|
||||
import os
|
||||
from agents import set_tracing_export_api_key, Agent, Runner
|
||||
from agents.extensions.models.any_llm_model import AnyLLMModel
|
||||
|
||||
tracing_api_key = os.environ["OPENAI_API_KEY"]
|
||||
set_tracing_export_api_key(tracing_api_key)
|
||||
|
||||
model = AnyLLMModel(
|
||||
model="your-provider/your-model-name",
|
||||
api_key="your-api-key",
|
||||
)
|
||||
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
model=model,
|
||||
)
|
||||
```
|
||||
|
||||
단일 실행에만 다른 트레이싱 키가 필요하다면, 전역 익스포터를 변경하는 대신 `RunConfig`를 통해 전달하세요.
|
||||
|
||||
```python
|
||||
from agents import Runner, RunConfig
|
||||
|
||||
await Runner.run(
|
||||
agent,
|
||||
input="Hello",
|
||||
run_config=RunConfig(tracing={"api_key": "sk-tracing-123"}),
|
||||
)
|
||||
```
|
||||
|
||||
## 추가 참고 사항
|
||||
- OpenAI Traces 대시보드에서 무료 트레이스를 확인하세요.
|
||||
|
||||
|
||||
## 에코시스템 통합
|
||||
|
||||
다음 커뮤니티 및 벤더 통합은 OpenAI Agents SDK 트레이싱 인터페이스를 지원합니다.
|
||||
|
||||
### 외부 트레이싱 프로세서 목록
|
||||
|
||||
- [Weights & Biases](https://weave-docs.wandb.ai/guides/integrations/openai_agents)
|
||||
- [Arize-Phoenix](https://docs.arize.com/phoenix/tracing/integrations-tracing/openai-agents-sdk)
|
||||
- [Future AGI](https://docs.futureagi.com/future-agi/products/observability/auto-instrumentation/openai_agents)
|
||||
- [MLflow (self-hosted/OSS)](https://mlflow.org/docs/latest/tracing/integrations/openai-agent)
|
||||
- [MLflow (Databricks hosted)](https://docs.databricks.com/aws/en/mlflow/mlflow-tracing#-automatic-tracing)
|
||||
- [Braintrust](https://braintrust.dev/docs/guides/traces/integrations#openai-agents-sdk)
|
||||
- [Pydantic Logfire](https://logfire.pydantic.dev/docs/integrations/llms/openai/#openai-agents)
|
||||
- [AgentOps](https://docs.agentops.ai/v1/integrations/agentssdk)
|
||||
- [Scorecard](https://docs.scorecard.io/docs/documentation/features/tracing#openai-agents-sdk-integration)
|
||||
- [Respan](https://respan.ai/docs/integrations/tracing/openai-agents-sdk)
|
||||
- [LangSmith](https://docs.smith.langchain.com/observability/how_to_guides/trace_with_openai_agents_sdk)
|
||||
- [Maxim AI](https://www.getmaxim.ai/docs/observe/integrations/openai-agents-sdk)
|
||||
- [Comet Opik](https://www.comet.com/docs/opik/tracing/integrations/openai_agents)
|
||||
- [Langfuse](https://langfuse.com/docs/integrations/openaiagentssdk/openai-agents)
|
||||
- [Langtrace](https://docs.langtrace.ai/supported-integrations/llm-frameworks/openai-agents-sdk)
|
||||
- [Okahu-Monocle](https://github.com/monocle2ai/monocle)
|
||||
- [Galileo](https://v2docs.galileo.ai/integrations/openai-agent-integration#openai-agent-integration)
|
||||
- [Portkey AI](https://portkey.ai/docs/integrations/agents/openai-agents)
|
||||
- [LangDB AI](https://docs.langdb.ai/getting-started/working-with-agent-frameworks/working-with-openai-agents-sdk)
|
||||
- [Agenta](https://docs.agenta.ai/observability/integrations/openai-agents)
|
||||
- [PostHog](https://posthog.com/docs/llm-analytics/installation/openai-agents)
|
||||
- [Traccia](https://traccia.ai/docs/integrations/openai-agents)
|
||||
- [PromptLayer](https://docs.promptlayer.com/languages/integrations#openai-agents-sdk)
|
||||
- [HoneyHive](https://docs.honeyhive.ai/v2/integrations/openai-agents)
|
||||
- [Asqav](https://www.asqav.com/docs/integrations#openai-agents)
|
||||
- [Datadog](https://docs.datadoghq.com/llm_observability/instrumentation/auto_instrumentation/?tab=python#openai-agents)
|
||||
- [Latitude](https://docs.latitude.so/telemetry/frameworks/openai-agents)
|
||||
- [DProvenanceKit](https://dprovenance.dev/openai-agents/)
|
||||
@@ -0,0 +1,90 @@
|
||||
---
|
||||
search:
|
||||
exclude: true
|
||||
---
|
||||
# 사용량
|
||||
|
||||
Agents SDK는 모든 실행의 토큰 사용량을 자동으로 추적합니다. 실행 컨텍스트에서 이를 접근하여 비용을 모니터링하고, 한도를 적용하거나, 분석 데이터를 기록할 수 있습니다.
|
||||
|
||||
## 추적 항목
|
||||
|
||||
- **requests**: 수행된 LLM API 호출 수
|
||||
- **input_tokens**: 전송된 총 입력 토큰 수
|
||||
- **output_tokens**: 수신된 총 출력 토큰 수
|
||||
- **total_tokens**: 입력 + 출력
|
||||
- **request_usage_entries**: 요청별 사용량 세부 내역 목록
|
||||
- **details**:
|
||||
- `input_tokens_details.cached_tokens`
|
||||
- `output_tokens_details.reasoning_tokens`
|
||||
|
||||
## 실행에서 사용량 접근
|
||||
|
||||
`Runner.run(...)` 이후에는 `result.context_wrapper.usage`를 통해 사용량에 접근합니다.
|
||||
|
||||
```python
|
||||
result = await Runner.run(agent, "What's the weather in Tokyo?")
|
||||
usage = result.context_wrapper.usage
|
||||
|
||||
print("Requests:", usage.requests)
|
||||
print("Input tokens:", usage.input_tokens)
|
||||
print("Output tokens:", usage.output_tokens)
|
||||
print("Total tokens:", usage.total_tokens)
|
||||
```
|
||||
|
||||
사용량은 실행 중 모든 모델 호출(도구 호출 및 핸드오프 포함)에 걸쳐 집계됩니다.
|
||||
|
||||
### 서드 파티 어댑터에서 사용량 활성화
|
||||
|
||||
사용량 보고는 서드 파티 어댑터와 공급자 백엔드마다 다릅니다. 어댑터 기반 모델을 사용하며 정확한 `result.context_wrapper.usage` 값이 필요한 경우:
|
||||
|
||||
- `AnyLLMModel`에서는 업스트림 공급자가 사용량을 반환하면 사용량이 자동으로 전파됩니다. 스트리밍 Chat Completions 백엔드의 경우 사용량 청크가 방출되기 전에 `ModelSettings(include_usage=True)`가 필요할 수 있습니다.
|
||||
- `LitellmModel`에서는 일부 공급자 백엔드가 기본적으로 사용량을 보고하지 않으므로 `ModelSettings(include_usage=True)`가 필요한 경우가 많습니다.
|
||||
|
||||
Models 가이드의 [서드 파티 어댑터](models/index.md#third-party-adapters) 섹션에서 어댑터별 참고 사항을 검토하고, 배포하려는 정확한 공급자 백엔드를 검증하세요.
|
||||
|
||||
## 요청별 사용량 추적
|
||||
|
||||
SDK는 `request_usage_entries`에서 각 API 요청의 사용량을 자동으로 추적하며, 이는 상세한 비용 계산과 컨텍스트 윈도우 사용량 모니터링에 유용합니다.
|
||||
|
||||
```python
|
||||
result = await Runner.run(agent, "What's the weather in Tokyo?")
|
||||
|
||||
for i, request in enumerate(result.context_wrapper.usage.request_usage_entries):
|
||||
print(f"Request {i + 1}: {request.input_tokens} in, {request.output_tokens} out")
|
||||
```
|
||||
|
||||
## 세션에서 사용량 접근
|
||||
|
||||
`Session`(예: `SQLiteSession`)을 사용하는 경우, `Runner.run(...)`을 호출할 때마다 해당 특정 실행의 사용량이 반환됩니다. 세션은 컨텍스트를 위해 대화 기록을 유지하지만, 각 실행의 사용량은 독립적입니다.
|
||||
|
||||
```python
|
||||
session = SQLiteSession("my_conversation")
|
||||
|
||||
first = await Runner.run(agent, "Hi!", session=session)
|
||||
print(first.context_wrapper.usage.total_tokens) # Usage for first run
|
||||
|
||||
second = await Runner.run(agent, "Can you elaborate?", session=session)
|
||||
print(second.context_wrapper.usage.total_tokens) # Usage for second run
|
||||
```
|
||||
|
||||
세션은 실행 간 대화 컨텍스트를 보존하지만, 각 `Runner.run()` 호출이 반환하는 사용량 지표는 해당 특정 실행만을 나타냅니다. 세션에서는 이전 메시지가 각 실행의 입력으로 다시 제공될 수 있으며, 이는 이후 턴의 입력 토큰 수에 영향을 줍니다.
|
||||
|
||||
## 훅에서 사용량 활용
|
||||
|
||||
`RunHooks`를 사용하는 경우, 각 훅에 전달되는 `context` 객체에는 `usage`가 포함됩니다. 이를 통해 주요 라이프사이클 시점에 사용량을 기록할 수 있습니다.
|
||||
|
||||
```python
|
||||
class MyHooks(RunHooks):
|
||||
async def on_agent_end(self, context: RunContextWrapper, agent: Agent, output: Any) -> None:
|
||||
u = context.usage
|
||||
print(f"{agent.name} → {u.requests} requests, {u.total_tokens} total tokens")
|
||||
```
|
||||
|
||||
## API 참조
|
||||
|
||||
자세한 API 문서는 다음을 참조하세요.
|
||||
|
||||
- [`Usage`][agents.usage.Usage] - 사용량 추적 데이터 구조
|
||||
- [`RequestUsage`][agents.usage.RequestUsage] - 요청별 사용량 세부 정보
|
||||
- [`RunContextWrapper`][agents.run.RunContextWrapper] - 실행 컨텍스트에서 사용량 접근
|
||||
- [`RunHooks`][agents.run.RunHooks] - 사용량 추적 라이프사이클에 훅 연결
|
||||
@@ -0,0 +1,107 @@
|
||||
---
|
||||
search:
|
||||
exclude: true
|
||||
---
|
||||
# 에이전트 시각화
|
||||
|
||||
에이전트 시각화를 사용하면 **Graphviz**로 에이전트와 그 관계를 구조화된 그래픽 표현으로 생성할 수 있습니다. 이는 애플리케이션 내에서 에이전트, 도구, 핸드오프가 어떻게 상호작용하는지 이해하는 데 유용합니다.
|
||||
|
||||
## 설치
|
||||
|
||||
선택적 `viz` 의존성 그룹을 설치합니다.
|
||||
|
||||
```bash
|
||||
pip install "openai-agents[viz]"
|
||||
```
|
||||
|
||||
## 그래프 생성
|
||||
|
||||
`draw_graph` 함수를 사용하여 에이전트 시각화를 생성할 수 있습니다. 이 함수는 다음과 같은 방향 그래프를 만듭니다.
|
||||
|
||||
- **에이전트**는 노란색 상자로 표시됩니다.
|
||||
- **MCP 서버**는 회색 상자로 표시됩니다.
|
||||
- **도구**는 초록색 타원으로 표시됩니다.
|
||||
- **핸드오프**는 한 에이전트에서 다른 에이전트로 향하는 방향성 간선입니다.
|
||||
|
||||
### 사용 예
|
||||
|
||||
```python
|
||||
import os
|
||||
|
||||
from agents import Agent, function_tool
|
||||
from agents.mcp.server import MCPServerStdio
|
||||
from agents.extensions.visualization import draw_graph
|
||||
|
||||
@function_tool
|
||||
def get_weather(city: str) -> str:
|
||||
return f"The weather in {city} is sunny."
|
||||
|
||||
spanish_agent = Agent(
|
||||
name="Spanish agent",
|
||||
instructions="You only speak Spanish.",
|
||||
)
|
||||
|
||||
english_agent = Agent(
|
||||
name="English agent",
|
||||
instructions="You only speak English",
|
||||
)
|
||||
|
||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
samples_dir = os.path.join(current_dir, "sample_files")
|
||||
mcp_server = MCPServerStdio(
|
||||
name="Filesystem Server, via npx",
|
||||
params={
|
||||
"command": "npx",
|
||||
"args": ["-y", "@modelcontextprotocol/server-filesystem", samples_dir],
|
||||
},
|
||||
)
|
||||
|
||||
triage_agent = Agent(
|
||||
name="Triage agent",
|
||||
instructions="Handoff to the appropriate agent based on the language of the request.",
|
||||
handoffs=[spanish_agent, english_agent],
|
||||
tools=[get_weather],
|
||||
mcp_servers=[mcp_server],
|
||||
)
|
||||
|
||||
draw_graph(triage_agent)
|
||||
```
|
||||
|
||||

|
||||
|
||||
이는 **트리아지 에이전트**의 구조와 하위 에이전트 및 도구와의 연결을 시각적으로 나타내는 그래프를 생성합니다.
|
||||
|
||||
|
||||
## 시각화 이해
|
||||
|
||||
생성된 그래프에는 다음이 포함됩니다.
|
||||
|
||||
- 진입점을 나타내는 **시작 노드**(`__start__`)
|
||||
- 노란색으로 채워진 **직사각형**으로 표시되는 에이전트
|
||||
- 초록색으로 채워진 **타원**으로 표시되는 도구
|
||||
- 회색으로 채워진 **직사각형**으로 표시되는 MCP 서버
|
||||
- 상호작용을 나타내는 방향성 간선:
|
||||
- 에이전트 간 핸드오프를 나타내는 **실선 화살표**
|
||||
- 도구 호출을 나타내는 **점선 화살표**
|
||||
- MCP 서버 호출을 나타내는 **파선 화살표**
|
||||
- 실행이 종료되는 위치를 나타내는 **종료 노드**(`__end__`)
|
||||
|
||||
**참고:** MCP 서버는 최신 버전의 `agents` 패키지에서 렌더링됩니다(**v0.2.8**에서 확인됨). 시각화에서 MCP 상자가 보이지 않는다면 최신 릴리스로 업그레이드하세요.
|
||||
|
||||
## 그래프 사용자 지정
|
||||
|
||||
### 그래프 표시
|
||||
기본적으로 `draw_graph`는 그래프를 인라인으로 표시합니다. 그래프를 별도 창에 표시하려면 다음과 같이 작성합니다.
|
||||
|
||||
```python
|
||||
draw_graph(triage_agent).view()
|
||||
```
|
||||
|
||||
### 그래프 저장
|
||||
기본적으로 `draw_graph`는 그래프를 인라인으로 표시합니다. 파일로 저장하려면 파일 이름을 지정합니다.
|
||||
|
||||
```python
|
||||
draw_graph(triage_agent, filename="agent_graph")
|
||||
```
|
||||
|
||||
그러면 작업 디렉터리에 `agent_graph.png`가 생성됩니다.
|
||||
@@ -0,0 +1,81 @@
|
||||
---
|
||||
search:
|
||||
exclude: true
|
||||
---
|
||||
# 파이프라인과 워크플로
|
||||
|
||||
[`VoicePipeline`][agents.voice.pipeline.VoicePipeline]은 에이전트 기반 워크플로를 음성 앱으로 쉽게 전환할 수 있게 해 주는 클래스입니다. 실행할 워크플로를 전달하면, 파이프라인이 입력 오디오 전사, 오디오 종료 감지, 적절한 시점에 워크플로 호출, 워크플로 출력을 다시 오디오로 변환하는 작업을 처리합니다.
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
%% Input
|
||||
A["🎤 Audio Input"]
|
||||
|
||||
%% Voice Pipeline
|
||||
subgraph Voice_Pipeline [Voice Pipeline]
|
||||
direction TB
|
||||
B["Transcribe (speech-to-text)"]
|
||||
C["Your Code"]:::highlight
|
||||
D["Text-to-speech"]
|
||||
B --> C --> D
|
||||
end
|
||||
|
||||
%% Output
|
||||
E["🎧 Audio Output"]
|
||||
|
||||
%% Flow
|
||||
A --> Voice_Pipeline
|
||||
Voice_Pipeline --> E
|
||||
|
||||
%% Custom styling
|
||||
classDef highlight fill:#ffcc66,stroke:#333,stroke-width:1px,font-weight:700;
|
||||
|
||||
```
|
||||
|
||||
## 파이프라인 구성
|
||||
|
||||
파이프라인을 만들 때 몇 가지를 설정할 수 있습니다.
|
||||
|
||||
1. [`workflow`][agents.voice.workflow.VoiceWorkflowBase]: 새 오디오가 전사될 때마다 실행되는 코드입니다.
|
||||
2. 사용되는 [`speech-to-text`][agents.voice.model.STTModel] 및 [`text-to-speech`][agents.voice.model.TTSModel] 모델
|
||||
3. [`config`][agents.voice.pipeline_config.VoicePipelineConfig]: 다음과 같은 항목을 구성할 수 있습니다.
|
||||
- 모델 이름을 모델에 매핑할 수 있는 모델 제공자
|
||||
- 트레이싱 비활성화 여부, 오디오 파일 업로드 여부, 워크플로 이름, 트레이스 ID 등을 포함한 트레이싱
|
||||
- 프롬프트, 언어, 사용되는 데이터 타입 등 TTS 및 STT 모델 설정
|
||||
|
||||
## 파이프라인 실행
|
||||
|
||||
[`run()`][agents.voice.pipeline.VoicePipeline.run] 메서드를 통해 파이프라인을 실행할 수 있으며, 이 메서드는 두 가지 형태의 오디오 입력을 전달할 수 있게 해 줍니다.
|
||||
|
||||
1. [`AudioInput`][agents.voice.input.AudioInput]은 완전한 오디오 입력이 있고 그에 대한 결과만 생성하려는 경우에 사용합니다. 화자가 말하기를 끝낸 시점을 감지할 필요가 없는 경우에 유용합니다. 예를 들어 사전 녹음된 오디오가 있거나, 사용자가 말하기를 끝낸 시점이 명확한 push-to-talk 앱에서 사용할 수 있습니다.
|
||||
2. [`StreamedAudioInput`][agents.voice.input.StreamedAudioInput]은 사용자가 말하기를 끝낸 시점을 감지해야 할 수 있는 경우에 사용합니다. 감지되는 대로 오디오 청크를 푸시할 수 있으며, 음성 파이프라인은 "활동 감지"라는 프로세스를 통해 적절한 시점에 에이전트 워크플로를 자동으로 실행합니다.
|
||||
|
||||
## 결과
|
||||
|
||||
음성 파이프라인 실행 결과는 [`StreamedAudioResult`][agents.voice.result.StreamedAudioResult]입니다. 이는 이벤트가 발생할 때 이를 스트리밍할 수 있게 해 주는 객체입니다. [`VoiceStreamEvent`][agents.voice.events.VoiceStreamEvent]에는 다음을 포함해 몇 가지 종류가 있습니다.
|
||||
|
||||
1. [`VoiceStreamEventAudio`][agents.voice.events.VoiceStreamEventAudio]: 오디오 청크를 포함합니다.
|
||||
2. [`VoiceStreamEventLifecycle`][agents.voice.events.VoiceStreamEventLifecycle]: 턴 시작 또는 종료와 같은 수명 주기 이벤트를 알려 줍니다.
|
||||
3. [`VoiceStreamEventError`][agents.voice.events.VoiceStreamEventError]: 오류 이벤트입니다.
|
||||
|
||||
```python
|
||||
|
||||
result = await pipeline.run(input)
|
||||
|
||||
async for event in result.stream():
|
||||
if event.type == "voice_stream_event_audio":
|
||||
# play audio
|
||||
pass
|
||||
elif event.type == "voice_stream_event_lifecycle":
|
||||
# lifecycle
|
||||
pass
|
||||
elif event.type == "voice_stream_event_error":
|
||||
# error
|
||||
pass
|
||||
```
|
||||
|
||||
## 모범 사례
|
||||
|
||||
### 인터럽션(중단 처리)
|
||||
|
||||
Agents SDK는 현재 [`StreamedAudioInput`][agents.voice.input.StreamedAudioInput]에 대한 내장 인터럽션(중단 처리) 처리를 제공하지 않습니다. 대신 감지된 각 턴은 워크플로의 별도 실행을 트리거합니다. 애플리케이션 내부에서 인터럽션(중단 처리)을 처리하려면 [`VoiceStreamEventLifecycle`][agents.voice.events.VoiceStreamEventLifecycle] 이벤트를 수신할 수 있습니다. `turn_started`는 새 턴이 전사되었고 처리가 시작됨을 나타냅니다. `turn_ended`는 해당 턴에 대한 모든 오디오가 디스패치된 후 트리거됩니다. 이러한 이벤트를 사용해 모델이 턴을 시작할 때 화자의 마이크를 음소거하고, 턴과 관련된 모든 오디오를 플러시한 후 음소거를 해제할 수 있습니다.
|
||||
@@ -0,0 +1,198 @@
|
||||
---
|
||||
search:
|
||||
exclude: true
|
||||
---
|
||||
# 빠른 시작
|
||||
|
||||
## 사전 요구 사항
|
||||
|
||||
Agents SDK의 기본 [빠른 시작 지침](../quickstart.md)을 따르고 가상 환경을 설정했는지 확인합니다. 그런 다음 SDK에서 선택적 음성 종속성을 설치합니다.
|
||||
|
||||
```bash
|
||||
pip install 'openai-agents[voice]'
|
||||
```
|
||||
|
||||
## 개념
|
||||
|
||||
알아야 할 주요 개념은 3단계 프로세스인 [`VoicePipeline`][agents.voice.pipeline.VoicePipeline]입니다.
|
||||
|
||||
1. 음성-텍스트 변환 모델을 실행하여 오디오를 텍스트로 변환합니다.
|
||||
2. 일반적으로 에이전트 워크플로인 코드를 실행하여 결과를 생성합니다.
|
||||
3. 텍스트-음성 변환 모델을 실행하여 결과 텍스트를 다시 오디오로 변환합니다.
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
%% Input
|
||||
A["🎤 Audio Input"]
|
||||
|
||||
%% Voice Pipeline
|
||||
subgraph Voice_Pipeline [Voice Pipeline]
|
||||
direction TB
|
||||
B["Transcribe (speech-to-text)"]
|
||||
C["Your Code"]:::highlight
|
||||
D["Text-to-speech"]
|
||||
B --> C --> D
|
||||
end
|
||||
|
||||
%% Output
|
||||
E["🎧 Audio Output"]
|
||||
|
||||
%% Flow
|
||||
A --> Voice_Pipeline
|
||||
Voice_Pipeline --> E
|
||||
|
||||
%% Custom styling
|
||||
classDef highlight fill:#ffcc66,stroke:#333,stroke-width:1px,font-weight:700;
|
||||
|
||||
```
|
||||
|
||||
## 에이전트
|
||||
|
||||
먼저 몇 가지 에이전트를 설정하겠습니다. 이 SDK로 에이전트를 만들어 본 적이 있다면 익숙할 것입니다. 몇 개의 에이전트와 하나의 핸드오프, 하나의 도구를 사용합니다.
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
import random
|
||||
|
||||
from agents import (
|
||||
Agent,
|
||||
function_tool,
|
||||
)
|
||||
from agents.extensions.handoff_prompt import prompt_with_handoff_instructions
|
||||
|
||||
|
||||
|
||||
@function_tool
|
||||
def get_weather(city: str) -> str:
|
||||
"""Get the weather for a given city."""
|
||||
print(f"[debug] get_weather called with city: {city}")
|
||||
choices = ["sunny", "cloudy", "rainy", "snowy"]
|
||||
return f"The weather in {city} is {random.choice(choices)}."
|
||||
|
||||
|
||||
spanish_agent = Agent(
|
||||
name="Spanish",
|
||||
handoff_description="A Spanish-speaking agent.",
|
||||
instructions=prompt_with_handoff_instructions(
|
||||
"You're speaking to a human, so be polite and concise. Speak in Spanish.",
|
||||
),
|
||||
model="gpt-5.6-sol",
|
||||
)
|
||||
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
instructions=prompt_with_handoff_instructions(
|
||||
"You're speaking to a human, so be polite and concise. If the user speaks in Spanish, hand off to the Spanish agent.",
|
||||
),
|
||||
model="gpt-5.6-sol",
|
||||
handoffs=[spanish_agent],
|
||||
tools=[get_weather],
|
||||
)
|
||||
```
|
||||
|
||||
## 음성 파이프라인
|
||||
|
||||
[`SingleAgentVoiceWorkflow`][agents.voice.workflow.SingleAgentVoiceWorkflow]를 워크플로로 사용하여 간단한 음성 파이프라인을 설정합니다.
|
||||
|
||||
```python
|
||||
from agents.voice import SingleAgentVoiceWorkflow, VoicePipeline
|
||||
pipeline = VoicePipeline(workflow=SingleAgentVoiceWorkflow(agent))
|
||||
```
|
||||
|
||||
## 파이프라인 실행
|
||||
|
||||
```python
|
||||
import numpy as np
|
||||
import sounddevice as sd
|
||||
from agents.voice import AudioInput
|
||||
|
||||
# For simplicity, we'll just create 3 seconds of silence
|
||||
# In reality, you'd get microphone data
|
||||
buffer = np.zeros(24000 * 3, dtype=np.int16)
|
||||
audio_input = AudioInput(buffer=buffer)
|
||||
|
||||
result = await pipeline.run(audio_input)
|
||||
|
||||
# Create an audio player using `sounddevice`
|
||||
player = sd.OutputStream(samplerate=24000, channels=1, dtype=np.int16)
|
||||
player.start()
|
||||
|
||||
# Play the audio stream as it comes in
|
||||
async for event in result.stream():
|
||||
if event.type == "voice_stream_event_audio":
|
||||
player.write(event.data)
|
||||
|
||||
```
|
||||
|
||||
## 전체 구성
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
import random
|
||||
|
||||
import numpy as np
|
||||
import sounddevice as sd
|
||||
|
||||
from agents import (
|
||||
Agent,
|
||||
function_tool,
|
||||
set_tracing_disabled,
|
||||
)
|
||||
from agents.voice import (
|
||||
AudioInput,
|
||||
SingleAgentVoiceWorkflow,
|
||||
VoicePipeline,
|
||||
)
|
||||
from agents.extensions.handoff_prompt import prompt_with_handoff_instructions
|
||||
|
||||
|
||||
@function_tool
|
||||
def get_weather(city: str) -> str:
|
||||
"""Get the weather for a given city."""
|
||||
print(f"[debug] get_weather called with city: {city}")
|
||||
choices = ["sunny", "cloudy", "rainy", "snowy"]
|
||||
return f"The weather in {city} is {random.choice(choices)}."
|
||||
|
||||
|
||||
spanish_agent = Agent(
|
||||
name="Spanish",
|
||||
handoff_description="A Spanish-speaking agent.",
|
||||
instructions=prompt_with_handoff_instructions(
|
||||
"You're speaking to a human, so be polite and concise. Speak in Spanish.",
|
||||
),
|
||||
model="gpt-5.6-sol",
|
||||
)
|
||||
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
instructions=prompt_with_handoff_instructions(
|
||||
"You're speaking to a human, so be polite and concise. If the user speaks in Spanish, hand off to the Spanish agent.",
|
||||
),
|
||||
model="gpt-5.6-sol",
|
||||
handoffs=[spanish_agent],
|
||||
tools=[get_weather],
|
||||
)
|
||||
|
||||
|
||||
async def main():
|
||||
pipeline = VoicePipeline(workflow=SingleAgentVoiceWorkflow(agent))
|
||||
buffer = np.zeros(24000 * 3, dtype=np.int16)
|
||||
audio_input = AudioInput(buffer=buffer)
|
||||
|
||||
result = await pipeline.run(audio_input)
|
||||
|
||||
# Create an audio player using `sounddevice`
|
||||
player = sd.OutputStream(samplerate=24000, channels=1, dtype=np.int16)
|
||||
player.start()
|
||||
|
||||
# Play the audio stream as it comes in
|
||||
async for event in result.stream():
|
||||
if event.type == "voice_stream_event_audio":
|
||||
player.write(event.data)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
이 예제를 실행하면 에이전트가 사용자에게 음성으로 응답합니다! 에이전트와 직접 대화할 수 있는 데모는 [examples/voice/static](https://github.com/openai/openai-agents-python/tree/main/examples/voice/static)의 코드 예제를 확인하세요.
|
||||
@@ -0,0 +1,18 @@
|
||||
---
|
||||
search:
|
||||
exclude: true
|
||||
---
|
||||
# 트레이싱
|
||||
|
||||
[에이전트가 트레이싱되는 방식](../tracing.md)과 마찬가지로, 음성 파이프라인도 자동으로 트레이싱됩니다.
|
||||
|
||||
기본적인 트레이싱 정보는 위의 트레이싱 문서를 참고할 수 있으며, 추가로 [`VoicePipelineConfig`][agents.voice.pipeline_config.VoicePipelineConfig]를 통해 파이프라인의 트레이싱을 구성할 수 있습니다.
|
||||
|
||||
트레이싱 관련 주요 필드는 다음과 같습니다.
|
||||
|
||||
- [`tracing_disabled`][agents.voice.pipeline_config.VoicePipelineConfig.tracing_disabled]: 트레이싱을 비활성화할지 여부를 제어합니다. 기본적으로 트레이싱은 활성화되어 있습니다.
|
||||
- [`trace_include_sensitive_data`][agents.voice.pipeline_config.VoicePipelineConfig.trace_include_sensitive_data]: 트레이스에 오디오 전사와 같은 잠재적으로 민감한 데이터를 포함할지 여부를 제어합니다. 이는 음성 파이프라인에만 해당하며, Workflow 내부에서 발생하는 다른 작업에는 적용되지 않습니다.
|
||||
- [`trace_include_sensitive_audio_data`][agents.voice.pipeline_config.VoicePipelineConfig.trace_include_sensitive_audio_data]: 트레이스에 오디오 데이터를 포함할지 여부를 제어합니다.
|
||||
- [`workflow_name`][agents.voice.pipeline_config.VoicePipelineConfig.workflow_name]: 트레이스 워크플로의 이름입니다.
|
||||
- [`group_id`][agents.voice.pipeline_config.VoicePipelineConfig.group_id]: 여러 트레이스를 연결할 수 있게 해 주는 트레이스의 `group_id`입니다.
|
||||
- [`trace_metadata`][agents.voice.pipeline_config.VoicePipelineConfig.trace_metadata]: 트레이스에 포함할 추가 메타데이터입니다.
|
||||
Reference in New Issue
Block a user