From 2d00318b5639a7d2b10695b0672b721a600b1aa7 Mon Sep 17 00:00:00 2001 From: Tyler Ryu Date: Tue, 19 Aug 2025 17:46:03 +0800 Subject: [PATCH] Korean translation --- docs/ko/agents.md | 248 ++++++++++++++++++++ docs/ko/config.md | 98 ++++++++ docs/ko/context.md | 82 +++++++ docs/ko/examples.md | 49 ++++ docs/ko/guardrails.md | 158 +++++++++++++ docs/ko/handoffs.md | 118 ++++++++++ docs/ko/index.md | 58 +++++ docs/ko/mcp.md | 191 +++++++++++++++ docs/ko/models/index.md | 151 ++++++++++++ docs/ko/models/litellm.md | 77 ++++++ docs/ko/multi_agent.md | 41 ++++ docs/ko/quickstart.md | 203 ++++++++++++++++ docs/ko/realtime/guide.md | 176 ++++++++++++++ docs/ko/realtime/quickstart.md | 179 ++++++++++++++ docs/ko/release.md | 32 +++ docs/ko/repl.md | 24 ++ docs/ko/results.md | 56 +++++ docs/ko/running_agents.md | 141 +++++++++++ docs/ko/sessions.md | 324 +++++++++++++++++++++++++ docs/ko/streaming.md | 91 ++++++++ docs/ko/tools.md | 415 +++++++++++++++++++++++++++++++++ docs/ko/tracing.md | 151 ++++++++++++ docs/ko/usage.md | 58 +++++ docs/ko/visualization.md | 105 +++++++++ docs/ko/voice/pipeline.md | 79 +++++++ docs/ko/voice/quickstart.md | 198 ++++++++++++++++ docs/ko/voice/tracing.md | 18 ++ docs/scripts/translate_docs.py | 168 ++++++++++++- mkdocs.yml | 43 +++- 29 files changed, 3726 insertions(+), 6 deletions(-) create mode 100644 docs/ko/agents.md create mode 100644 docs/ko/config.md create mode 100644 docs/ko/context.md create mode 100644 docs/ko/examples.md create mode 100644 docs/ko/guardrails.md create mode 100644 docs/ko/handoffs.md create mode 100644 docs/ko/index.md create mode 100644 docs/ko/mcp.md create mode 100644 docs/ko/models/index.md create mode 100644 docs/ko/models/litellm.md create mode 100644 docs/ko/multi_agent.md create mode 100644 docs/ko/quickstart.md create mode 100644 docs/ko/realtime/guide.md create mode 100644 docs/ko/realtime/quickstart.md create mode 100644 docs/ko/release.md create mode 100644 docs/ko/repl.md create mode 100644 docs/ko/results.md create mode 100644 docs/ko/running_agents.md create mode 100644 docs/ko/sessions.md create mode 100644 docs/ko/streaming.md create mode 100644 docs/ko/tools.md create mode 100644 docs/ko/tracing.md create mode 100644 docs/ko/usage.md create mode 100644 docs/ko/visualization.md create mode 100644 docs/ko/voice/pipeline.md create mode 100644 docs/ko/voice/quickstart.md create mode 100644 docs/ko/voice/tracing.md diff --git a/docs/ko/agents.md b/docs/ko/agents.md new file mode 100644 index 000000000..c44201380 --- /dev/null +++ b/docs/ko/agents.md @@ -0,0 +1,248 @@ +--- +search: + exclude: true +--- +# 에이전트 + +에이전트는 앱의 핵심 빌딩 블록입니다. 에이전트는 instructions 와 tools 로 구성된 대규모 언어 모델(LLM)입니다. + +## 기본 구성 + +에이전트를 구성할 때 가장 일반적으로 설정하는 속성은 다음과 같습니다: + +- `name`: 에이전트를 식별하는 필수 문자열 +- `instructions`: 개발자 메시지 또는 system prompt 로도 알려져 있음 +- `model`: 사용할 LLM 및 temperature, top_p 등 모델 튜닝 매개변수를 설정하는 선택적 `model_settings` +- `tools`: 에이전트가 작업을 수행하는 데 사용할 수 있는 도구 + +```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="o3-mini", + tools=[get_weather], +) +``` + +## 컨텍스트 + +에이전트는 `context` 타입에 대해 제네릭합니다. 컨텍스트는 의존성 주입 도구로, 여러분이 생성하여 `Runner.run()` 에 전달하는 객체이며, 모든 에이전트, 도구, 핸드오프 등에 전달되고 에이전트 실행을 위한 의존성과 상태를 담는 용도로 사용됩니다. 컨텍스트로는 임의의 Python 객체를 제공할 수 있습니다. + +```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/) 로 래핑할 수 있는 모든 타입(데이터클래스, 리스트, 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) 를 사용하도록 지시받습니다. + +## 핸드오프 + +핸드오프는 에이전트가 위임할 수 있는 하위 에이전트입니다. 핸드오프 목록을 제공하면, 에이전트는 관련성이 있을 경우 그들에게 위임할 수 있습니다. 이는 단일 작업에 특화된 모듈식 에이전트를 멀티 에이전트 오케스트레이션 하는 강력한 패턴입니다. 자세한 내용은 [핸드오프](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, handoff to the booking agent." + "If they ask about refunds, handoff 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, +) +``` + +## 라이프사이클 이벤트(hooks) + +때로는 에이전트의 라이프사이클을 관찰하고 싶을 수 있습니다. 예를 들어 이벤트를 로깅하거나 특정 이벤트가 발생할 때 데이터를 미리 가져오고 싶을 수 있습니다. `hooks` 속성으로 에이전트 라이프사이클에 훅을 걸 수 있습니다. [`AgentHooks`][agents.lifecycle.AgentHooks] 클래스를 상속하고, 관심 있는 메서드를 오버라이드하세요. + +## 가드레일 + +가드레일을 사용하면 에이전트가 실행되는 동안 사용자 입력에 대한 검사/검증을 병렬로 수행하고, 에이전트 출력이 생성된 후에도 검사할 수 있습니다. 예를 들어 사용자 입력과 에이전트 출력을 관련성 기준으로 스크리닝할 수 있습니다. 자세한 내용은 [가드레일](guardrails.md) 문서를 참조하세요. + +## 에이전트 복제/복사 + +에이전트의 `clone()` 메서드를 사용하면 에이전트를 복제하고, 원하는 속성을 선택적으로 변경할 수 있습니다. + +```python +pirate_agent = Agent( + name="Pirate", + instructions="Write like a pirate", + model="o3-mini", +) + +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` — 해당 특정 도구 사용을 강제 + +```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 이 또 다른 도구 호출을 생성하는 상황이 반복되며 발생합니다. \ No newline at end of file diff --git a/docs/ko/config.md b/docs/ko/config.md new file mode 100644 index 000000000..cc525e111 --- /dev/null +++ b/docs/ko/config.md @@ -0,0 +1,98 @@ +--- +search: + exclude: true +--- +# SDK 구성 + +## API 키와 클라이언트 + +기본적으로 SDK 는 가져오자마자 LLM 요청과 트레이싱을 위해 `OPENAI_API_KEY` 환경 변수를 찾습니다. 앱 시작 전에 해당 환경 변수를 설정할 수 없다면 [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 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 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-...") +``` + +[`set_tracing_disabled()`][agents.set_tracing_disabled] 함수를 사용해 트레이싱을 완전히 비활성화할 수도 있습니다. + +```python +from agents import set_tracing_disabled + +set_tracing_disabled(True) +``` + +## 디버그 로깅 + +SDK 에는 핸들러가 설정되지 않은 두 개의 Python 로거가 있습니다. 기본적으로 이는 경고와 오류는 `stdout` 으로 전송되지만, 그 외 로그는 억제된다는 의미입니다. + +자세한 로깅을 활성화하려면 [`enable_verbose_stdout_logging()`][agents.enable_verbose_stdout_logging] 함수를 사용하세요. + +```python +from agents import enable_verbose_stdout_logging + +enable_verbose_stdout_logging() +``` + +또는 핸들러, 필터, 포매터 등을 추가하여 로그를 커스터마이즈할 수 있습니다. 자세한 내용은 [Python logging guide](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()) +``` + +### 로그의 민감한 데이터 + +일부 로그에는 민감한 데이터(예: 사용자 데이터)가 포함될 수 있습니다. 이 데이터가 로깅되지 않도록 하려면 다음 환경 변수를 설정하세요. + +LLM 입력과 출력을 로깅하지 않으려면: + +```bash +export OPENAI_AGENTS_DONT_LOG_MODEL_DATA=1 +``` + +도구 입력과 출력을 로깅하지 않으려면: + +```bash +export OPENAI_AGENTS_DONT_LOG_TOOL_DATA=1 +``` \ No newline at end of file diff --git a/docs/ko/context.md b/docs/ko/context.md new file mode 100644 index 000000000..2b00c4f9c --- /dev/null +++ b/docs/ko/context.md @@ -0,0 +1,82 @@ +--- +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` 로 접근할 수 있습니다 + +가장 중요한 점: 특정 에이전트 실행에 참여하는 모든 에이전트, 도구 함수, 라이프사이클 등은 동일한 _타입_ 의 컨텍스트를 사용해야 합니다. + +컨텍스트는 다음과 같은 용도로 사용할 수 있습니다: + +- 실행에 대한 컨텍스트 데이터(예: 사용자 이름/uid 또는 사용자에 관한 기타 정보) +- 의존성(예: 로거 객체, 데이터 페처 등) +- 헬퍼 함수 + +!!! danger "주의" + + 컨텍스트 객체는 LLM 에게 **전송되지 않습니다**. 전적으로 로컬 객체이며 읽고, 쓰고, 메서드를 호출할 수 있습니다. + +```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. 에이전트는 도구를 올바르게 호출하여 나이를 가져옵니다 + +## 에이전트/LLM 컨텍스트 + +LLM 이 호출될 때 볼 수 있는 **유일한** 데이터는 대화 히스토리뿐입니다. 따라서 LLM 에 새 데이터를 제공하려면, 그 데이터가 히스토리에 포함되도록 해야 합니다. 이를 위한 방법은 다음과 같습니다: + +1. 에이전트 `instructions` 에 추가합니다. 이는 "시스템 프롬프트(system prompt)" 또는 "개발자 메시지"로도 알려져 있습니다. 시스템 프롬프트는 정적인 문자열일 수도 있고, 컨텍스트를 받아 문자열을 출력하는 동적 함수일 수도 있습니다. 사용자의 이름이나 현재 날짜처럼 항상 유용한 정보에 흔히 쓰이는 방법입니다 +2. `Runner.run` 함수를 호출할 때 `input` 에 추가합니다. 이는 `instructions` 방식과 유사하지만, [chain of command](https://cdn.openai.com/spec/model-spec-2024-05-08.html#follow-the-chain-of-command) 상에서 더 낮은 위치의 메시지를 사용할 수 있게 해줍니다 +3. 함수 도구로 노출합니다. 이는 _온디맨드_ 컨텍스트에 유용합니다. LLM 이 어떤 데이터가 필요할 때를 스스로 판단하고, 그 데이터를 가져오기 위해 도구를 호출할 수 있습니다 +4. 검색(리트리벌) 또는 웹 검색을 사용합니다. 이는 파일이나 데이터베이스에서 관련 데이터를 가져오는 특수 도구(검색/리트리벌) 또는 웹에서 데이터를 가져오는 도구(웹 검색)입니다. 이는 응답이 관련 컨텍스트 데이터에 기반하도록 "그라운딩"하는 데 유용합니다 \ No newline at end of file diff --git a/docs/ko/examples.md b/docs/ko/examples.md new file mode 100644 index 000000000..33573a349 --- /dev/null +++ b/docs/ko/examples.md @@ -0,0 +1,49 @@ +--- +search: + exclude: true +--- +# 코드 예제 + +[repo](https://github.com/openai/openai-agents-python/tree/main/examples)의 examples 섹션에서 SDK의 다양한 샘플 구현을 확인하세요. code examples는 서로 다른 패턴과 기능을 보여주는 여러 카테고리로 구성되어 있습니다. + + +## 카테고리 + +- **[에이전트 패턴 (agent_patterns)](https://github.com/openai/openai-agents-python/tree/main/examples/agent_patterns):** + 이 카테고리의 예제는 다음과 같은 일반적인 에이전트 설계 패턴을 보여줍니다 + + - 결정적 워크플로우 + - 도구로서의 에이전트 + - 에이전트 병렬 실행 + +- **[기초 (basic)](https://github.com/openai/openai-agents-python/tree/main/examples/basic):** + 이 code examples는 다음과 같은 SDK의 기본 기능을 보여줍니다 + + - 동적인 시스템 프롬프트 + - 스트리밍 출력 + - 라이프사이클 이벤트 + +- **[도구 코드 예제 (tool examples)](https://github.com/openai/openai-agents-python/tree/main/examples/tools):** + 웹 검색과 파일 검색 같은 OpenAI 호스트하는 도구를 구현하고, + 이를 에이전트에 통합하는 방법을 배워보세요. + +- **[모델 제공자 (model_providers)](https://github.com/openai/openai-agents-python/tree/main/examples/model_providers):** + SDK로 OpenAI가 아닌 모델을 사용하는 방법을 살펴보세요. + +- **[핸드오프 (handoffs)](https://github.com/openai/openai-agents-python/tree/main/examples/handoffs):** + 에이전트 핸드오프의 실용적인 code examples를 확인하세요. + +- **[MCP](https://github.com/openai/openai-agents-python/tree/main/examples/mcp):** + MCP로 에이전트를 구축하는 방법을 배워보세요. + +- **[고객 서비스 (customer_service)](https://github.com/openai/openai-agents-python/tree/main/examples/customer_service)** 및 **[리서치 봇 (research_bot)](https://github.com/openai/openai-agents-python/tree/main/examples/research_bot):** + 실제 애플리케이션을 보여주는 좀 더 완성된 두 가지 code examples + + - **customer_service**: 항공사 고객 서비스 시스템 예시 + - **research_bot**: 간단한 딥 리서치 클론 + +- **[음성 (voice)](https://github.com/openai/openai-agents-python/tree/main/examples/voice):** + TTS 및 STT 모델을 사용하는 음성 에이전트 code examples를 확인하세요. + +- **[실시간 (realtime)](https://github.com/openai/openai-agents-python/tree/main/examples/realtime):** + SDK를 사용해 실시간 경험을 구축하는 방법을 보여주는 code examples입니다. \ No newline at end of file diff --git a/docs/ko/guardrails.md b/docs/ko/guardrails.md new file mode 100644 index 000000000..ab49916b0 --- /dev/null +++ b/docs/ko/guardrails.md @@ -0,0 +1,158 @@ +--- +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 + + 입력 가드레일은 사용자 입력에서 실행되도록 설계되었으므로, 에이전트의 가드레일은 해당 에이전트가 *첫 번째* 에이전트일 때만 실행됩니다. `guardrails` 속성이 왜 에이전트에 있고 `Runner.run`에 전달되지 않는지 궁금할 수 있습니다. 이는 가드레일이 실제 에이전트와 연관되는 경향이 있기 때문입니다. 에이전트마다 서로 다른 가드레일을 실행하므로 코드를 같은 위치에 두는 것이 가독성에 유리합니다. + +## 출력 가드레일 + +출력 가드레일은 다음 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 + + 출력 가드레일은 최종 에이전트 출력에서 실행되도록 설계되었으므로, 에이전트의 가드레일은 해당 에이전트가 *마지막* 에이전트일 때만 실행됩니다. 입력 가드레일과 유사하게, 가드레일은 실제 에이전트와 연관되는 경향이 있으므로 코드를 같은 위치에 두는 것이 가독성에 유리합니다. + +## 트립와이어(tripwire) + +입력 또는 출력이 가드레일을 통과하지 못하는 경우, 가드레일은 트립와이어(tripwire)로 이를 신호할 수 있습니다. 트립와이어가 트리거된 가드레일을 확인하는 즉시 `{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. 이는 워크플로를 정의하는 실제 에이전트입니다 \ No newline at end of file diff --git a/docs/ko/handoffs.md b/docs/ko/handoffs.md new file mode 100644 index 000000000..bcc5e6467 --- /dev/null +++ b/docs/ko/handoffs.md @@ -0,0 +1,118 @@ +--- +search: + exclude: true +--- +# 핸드오프 + +핸드오프를 사용하면 한 에이전트가 다른 에이전트에게 작업을 위임할 수 있습니다. 이는 서로 다른 영역에 특화된 에이전트가 있는 시나리오에서 특히 유용합니다. 예를 들어, 고객 지원 앱에서는 주문 상태, 환불, FAQ 등 각 작업을 전담하는 에이전트를 둘 수 있습니다. + +핸드오프는 LLM 에게 도구로 표현됩니다. 예를 들어 `Refund Agent` 라는 에이전트로의 핸드오프가 있다면, 해당 도구는 `transfer_to_refund_agent` 라고 불립니다. + +## 핸드오프 생성 + +모든 에이전트에는 [`handoffs`][agents.agent.Agent.handoffs] 매개변수가 있으며, 여기에 `Agent` 를 직접 전달하거나, 핸드오프를 커스터마이즈하는 `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_` 으로 설정됩니다. 이를 오버라이드할 수 있습니다. +- `tool_description_override`: `Handoff.default_tool_description()` 의 기본 도구 설명을 오버라이드합니다 +- `on_handoff`: 핸드오프가 호출될 때 실행되는 콜백 함수입니다. 핸드오프가 호출됨을 아는 즉시 데이터 패칭을 시작하는 등의 용도로 유용합니다. 이 함수는 에이전트 컨텍스트를 전달받으며, 선택적으로 LLM 이 생성한 입력도 받을 수 있습니다. 입력 데이터는 `input_type` 매개변수에 의해 제어됩니다. +- `input_type`: 핸드오프가 기대하는 입력 타입(선택 사항) +- `input_filter`: 다음 에이전트가 받는 입력을 필터링할 수 있습니다. 아래 내용을 참고하세요. +- `is_enabled`: 핸드오프 활성화 여부입니다. 불리언 또는 불리언을 반환하는 함수가 될 수 있어, 런타임에 동적으로 핸드오프를 활성/비활성화할 수 있습니다. + +```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_filter`][agents.handoffs.Handoff.input_filter] 를 설정할 수 있습니다. 입력 필터는 [`HandoffInputData`][agents.handoffs.HandoffInputData] 로 기존 입력을 받아 새로운 `HandoffInputData` 를 반환해야 하는 함수입니다. + +일반적인 패턴(예: 이력에서 모든 도구 호출 제거)이 있으며, 이는 [`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} + .""", +) +``` \ No newline at end of file diff --git a/docs/ko/index.md b/docs/ko/index.md new file mode 100644 index 000000000..3abb96c25 --- /dev/null +++ b/docs/ko/index.md @@ -0,0 +1,58 @@ +--- +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 +- **핸드오프**: 에이전트가 특정 작업을 다른 에이전트에 위임할 수 있게 함 +- **가드레일**: 에이전트 입력과 출력의 유효성 검사를 활성화 +- **세션**: 에이전트 실행 간 대화 이력을 자동으로 관리 + +Python과 결합하면, 이 기본 구성 요소만으로도 도구와 에이전트 간의 복잡한 관계를 표현할 수 있으며, 가파른 학습 곡선 없이 실제 애플리케이션을 구축할 수 있습니다. 또한 SDK에는 **트레이싱**이 기본 제공되어 에이전트 플로우를 시각화하고 디버그할 수 있으며, 이를 평가하고 심지어 애플리케이션에 맞춰 모델을 파인튜닝할 수도 있습니다. + +## Agents SDK 사용 이유 + +SDK는 두 가지 설계 원칙을 따릅니다: + +1. 사용할 가치가 있을 만큼 충분한 기능을 제공하되, 빠르게 학습할 수 있도록 기본 구성 요소는 최소화 +2. 기본 설정만으로도 잘 작동하지만, 동작을 원하는 대로 세밀하게 커스터마이즈 가능 + +SDK의 주요 기능은 다음과 같습니다: + +- 에이전트 루프: 도구 호출, 결과를 LLM에 전달, LLM이 완료될 때까지 반복을 처리하는 내장 루프 +- Python 우선: 새로운 추상화를 배울 필요 없이 언어의 기본 기능으로 에이전트를 오케스트레이션하고 체이닝 +- 핸드오프: 다수의 에이전트를 조정하고 위임하는 강력한 기능 +- 가드레일: 에이전트와 병렬로 입력 검증과 점검을 실행하고, 실패 시 조기 중단 +- 세션: 에이전트 실행 간 대화 이력을 자동 관리하여 수동 상태 관리를 제거 +- 함수 도구: 어떤 Python 함수든 도구로 변환, 자동 스키마 생성과 Pydantic 기반 검증 제공 +- 트레이싱: 워크플로를 시각화, 디버그, 모니터링할 수 있으며 OpenAI의 평가, 파인튜닝, 증류 도구도 활용 가능 + +## 설치 + +```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-... +``` \ No newline at end of file diff --git a/docs/ko/mcp.md b/docs/ko/mcp.md new file mode 100644 index 000000000..797aff09f --- /dev/null +++ b/docs/ko/mcp.md @@ -0,0 +1,191 @@ +--- +search: + exclude: true +--- +# Model context protocol (MCP) + +[Model context protocol](https://modelcontextprotocol.io/introduction) (aka MCP)은 LLM에 도구와 컨텍스트를 제공하는 방법입니다. MCP 문서에서 인용: + +> MCP는 애플리케이션이 LLM에 컨텍스트를 제공하는 방식을 표준화한 개방형 프로토콜입니다. MCP를 AI 애플리케이션을 위한 USB-C 포트라고 생각해 보세요. USB-C가 다양한 주변기기와 액세서리에 기기를 연결하는 표준화된 방식을 제공하듯, MCP는 AI 모델을 서로 다른 데이터 소스와 도구에 연결하는 표준화된 방식을 제공합니다. + +Agents SDK 는 MCP를 지원합니다. 이를 통해 다양한 MCP 서버를 사용하여 에이전트에 도구와 프롬프트를 제공할 수 있습니다. + +## MCP 서버 + +현재 MCP 사양은 사용하는 전송 메커니즘에 따라 세 가지 유형의 서버를 정의합니다: + +1. **stdio** 서버는 애플리케이션의 하위 프로세스로 실행됩니다. 로컬에서 실행된다고 생각하시면 됩니다 +2. **HTTP over SSE** 서버는 원격으로 실행됩니다. URL을 통해 연결합니다 +3. **Streamable HTTP** 서버는 MCP 사양에 정의된 Streamable HTTP 전송을 사용하여 원격으로 실행됩니다 + +이들 서버에 연결하려면 [`MCPServerStdio`][agents.mcp.server.MCPServerStdio], [`MCPServerSse`][agents.mcp.server.MCPServerSse], [`MCPServerStreamableHttp`][agents.mcp.server.MCPServerStreamableHttp] 클래스를 사용할 수 있습니다. + +예를 들어, [공식 MCP filesystem 서버](https://www.npmjs.com/package/@modelcontextprotocol/server-filesystem)는 다음과 같이 사용할 수 있습니다. + +```python +from agents.run_context import RunContextWrapper + +async with MCPServerStdio( + params={ + "command": "npx", + "args": ["-y", "@modelcontextprotocol/server-filesystem", samples_dir], + } +) as server: + # Note: In practice, you typically add the server to an Agent + # and let the framework handle tool listing automatically. + # Direct calls to list_tools() require run_context and agent parameters. + run_context = RunContextWrapper(context=None) + agent = Agent(name="test", instructions="test") + tools = await server.list_tools(run_context, agent) +``` + +## MCP 서버 사용 + +MCP 서버는 에이전트에 추가할 수 있습니다. Agents SDK 는 에이전트가 실행될 때마다 MCP 서버에서 `list_tools()` 를 호출합니다. 이를 통해 LLM이 MCP 서버의 도구를 인지합니다. LLM이 MCP 서버의 도구를 호출하면, SDK가 해당 서버에서 `call_tool()` 을 호출합니다. + +```python + +agent=Agent( + name="Assistant", + instructions="Use the tools to achieve the task", + mcp_servers=[mcp_server_1, mcp_server_2] +) +``` + +## 도구 필터링 + +MCP 서버에서 도구 필터를 구성하여 에이전트에 어떤 도구를 제공할지 필터링할 수 있습니다. SDK는 정적 및 동적 도구 필터링을 모두 지원합니다. + +### 정적 도구 필터링 + +간단한 허용/차단 목록의 경우 정적 필터링을 사용할 수 있습니다: + +```python +from agents.mcp import create_static_tool_filter + +# Only expose specific tools from this server +server = MCPServerStdio( + params={ + "command": "npx", + "args": ["-y", "@modelcontextprotocol/server-filesystem", samples_dir], + }, + tool_filter=create_static_tool_filter( + allowed_tool_names=["read_file", "write_file"] + ) +) + +# Exclude specific tools from this server +server = MCPServerStdio( + params={ + "command": "npx", + "args": ["-y", "@modelcontextprotocol/server-filesystem", samples_dir], + }, + tool_filter=create_static_tool_filter( + blocked_tool_names=["delete_file"] + ) +) + +``` + +**`allowed_tool_names` 와 `blocked_tool_names` 가 모두 구성된 경우 처리 순서는 다음과 같습니다:** +1. 먼저 `allowed_tool_names` (허용 목록)를 적용 - 지정된 도구만 유지 +2. 그다음 `blocked_tool_names` (차단 목록)를 적용 - 남은 도구 중 지정된 도구 제외 + +예를 들어 `allowed_tool_names=["read_file", "write_file", "delete_file"]` 와 `blocked_tool_names=["delete_file"]` 를 구성하면 `read_file` 과 `write_file` 도구만 제공됩니다. + +### 동적 도구 필터링 + +더 복잡한 필터링 로직이 필요하면 함수로 동적 필터를 사용할 수 있습니다: + +```python +from agents.mcp import ToolFilterContext + +# Simple synchronous filter +def custom_filter(context: ToolFilterContext, tool) -> bool: + """Example of a custom tool filter.""" + # Filter logic based on tool name patterns + return tool.name.startswith("allowed_prefix") + +# Context-aware filter +def context_aware_filter(context: ToolFilterContext, tool) -> bool: + """Filter tools based on context information.""" + # Access agent information + agent_name = context.agent.name + + # Access server information + server_name = context.server_name + + # Implement your custom filtering logic here + return some_filtering_logic(agent_name, server_name, tool) + +# Asynchronous filter +async def async_filter(context: ToolFilterContext, tool) -> bool: + """Example of an asynchronous filter.""" + # Perform async operations if needed + result = await some_async_check(context, tool) + return result + +server = MCPServerStdio( + params={ + "command": "npx", + "args": ["-y", "@modelcontextprotocol/server-filesystem", samples_dir], + }, + tool_filter=custom_filter # or context_aware_filter or async_filter +) +``` + +`ToolFilterContext` 는 다음에 접근할 수 있습니다: +- `run_context`: 현재 실행 컨텍스트 +- `agent`: 도구를 요청한 에이전트 +- `server_name`: MCP 서버 이름 + +## 프롬프트 + +MCP 서버는 에이전트 instructions 를 동적으로 생성하는 데 사용할 수 있는 프롬프트를 제공할 수도 있습니다. 이를 통해 매개변수로 사용자화할 수 있는 재사용 가능한 instructions 템플릿을 만들 수 있습니다. + +### 프롬프트 사용 + +프롬프트를 지원하는 MCP 서버는 두 가지 핵심 메서드를 제공합니다: + +- `list_prompts()`: 서버에서 사용 가능한 모든 프롬프트 나열 +- `get_prompt(name, arguments)`: 선택적 매개변수와 함께 특정 프롬프트 가져오기 + +```python +# List available prompts +prompts_result = await server.list_prompts() +for prompt in prompts_result.prompts: + print(f"Prompt: {prompt.name} - {prompt.description}") + +# Get a specific prompt with parameters +prompt_result = await server.get_prompt( + "generate_code_review_instructions", + {"focus": "security vulnerabilities", "language": "python"} +) +instructions = prompt_result.messages[0].content.text + +# Use the prompt-generated instructions with an Agent +agent = Agent( + name="Code Reviewer", + instructions=instructions, # Instructions from MCP prompt + mcp_servers=[server] +) +``` + +## 캐싱 + +에이전트가 실행될 때마다 MCP 서버에서 `list_tools()` 를 호출합니다. 특히 서버가 원격인 경우 지연 시간이 늘어날 수 있습니다. 도구 목록을 자동으로 캐시하려면 [`MCPServerStdio`][agents.mcp.server.MCPServerStdio], [`MCPServerSse`][agents.mcp.server.MCPServerSse], [`MCPServerStreamableHttp`][agents.mcp.server.MCPServerStreamableHttp] 에 `cache_tools_list=True` 를 전달하면 됩니다. 도구 목록이 변경되지 않는다고 확신할 때만 사용하세요. + +캐시를 무효화하려면 서버에서 `invalidate_tools_cache()` 를 호출하면 됩니다. + +## 엔드 투 엔드 code examples + +작동하는 전체 code examples 는 [examples/mcp](https://github.com/openai/openai-agents-python/tree/main/examples/mcp)에서 확인하세요. + +## 트레이싱 + +[Tracing](./tracing.md)은 다음을 포함하여 MCP 작업을 자동으로 캡처합니다: + +1. 도구 목록을 가져오기 위한 MCP 서버 호출 +2. 함수 호출에 대한 MCP 관련 정보 + +![MCP 트레이싱 스크린샷](../assets/images/mcp-tracing.jpg) \ No newline at end of file diff --git a/docs/ko/models/index.md b/docs/ko/models/index.md new file mode 100644 index 000000000..76f6ea127 --- /dev/null +++ b/docs/ko/models/index.md @@ -0,0 +1,151 @@ +--- +search: + exclude: true +--- +# 모델 + +Agents SDK 는 OpenAI 모델을 다음 두 가지 방식으로 바로 지원합니다: + +- **권장**: [`OpenAIResponsesModel`][agents.models.openai_responses.OpenAIResponsesModel] — 새로운 [Responses API](https://platform.openai.com/docs/api-reference/responses)를 사용해 OpenAI API 를 호출 +- [`OpenAIChatCompletionsModel`][agents.models.openai_chatcompletions.OpenAIChatCompletionsModel] — [Chat Completions API](https://platform.openai.com/docs/api-reference/chat)를 사용해 OpenAI API 를 호출 + +## 비 OpenAI 모델 + +[LiteLLM 통합](./litellm.md)을 통해 대부분의 비 OpenAI 모델을 사용할 수 있습니다. 먼저 litellm 의존성 그룹을 설치하세요: + +```bash +pip install "openai-agents[litellm]" +``` + +그런 다음 `litellm/` 접두어와 함께 [지원되는 모델](https://docs.litellm.ai/docs/providers) 중 아무거나 사용하세요: + +```python +claude_agent = Agent(model="litellm/anthropic/claude-3-5-sonnet-20240620", ...) +gemini_agent = Agent(model="litellm/gemini/gemini-2.5-flash-preview-04-17", ...) +``` + +### 비 OpenAI 모델을 사용하는 다른 방법 + +다른 LLM 제공자를 다음 3가지 방법으로 통합할 수 있습니다 (code examples 는 [여기](https://github.com/openai/openai-agents-python/tree/main/examples/model_providers/)에 있습니다): + +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)를 참고하세요. 대부분의 사용 가능한 모델을 쉽게 사용하는 방법은 [LiteLLM 통합](./litellm.md)을 활용하는 것입니다. + +`platform.openai.com`의 API 키가 없는 경우, `set_tracing_disabled()`로 트레이싱을 비활성화하거나, [다른 트레이싱 프로세서](../tracing.md)를 설정하는 것을 권장합니다. + +!!! note + + 이 code examples 에서는 대부분의 LLM 제공자가 아직 Responses API 를 지원하지 않기 때문에 Chat Completions API/모델을 사용합니다. 사용 중인 LLM 제공자가 Responses 를 지원한다면 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="o3-mini", # (1)! +) + +english_agent = Agent( + name="English agent", + instructions="You only speak English", + model=OpenAIChatCompletionsModel( # (2)! + model="gpt-4o", + 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-3.5-turbo", +) + +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] 구현을 제공 + +에이전트에 사용되는 모델을 더 세부적으로 구성하려면 [`ModelSettings`][agents.models.interface.ModelSettings]를 전달하세요. temperature 같은 선택적 모델 구성 매개변수를 제공합니다. + +```python +from agents import Agent, ModelSettings + +english_agent = Agent( + name="English agent", + instructions="You only speak English", + model="gpt-4o", + model_settings=ModelSettings(temperature=0.1), +) +``` + +또한 OpenAI 의 Responses API 를 사용할 때는 `user`, `service_tier` 등 [몇 가지 다른 선택적 매개변수](https://platform.openai.com/docs/api-reference/responses/create)가 있습니다. 최상위 수준에서 제공되지 않는 경우 `extra_args`를 사용하여 함께 전달할 수 있습니다. + +```python +from agents import Agent, ModelSettings + +english_agent = Agent( + name="English agent", + instructions="You only speak English", + model="gpt-4o", + model_settings=ModelSettings( + temperature=0.1, + extra_args={"service_tier": "flex", "user": "user_12345"}, + ), +) +``` + +## 다른 LLM 제공자 사용 시 일반적인 문제 + +### 트레이싱 클라이언트 오류 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]을 사용하세요. code examples 는 [여기](https://github.com/openai/openai-agents-python/tree/main/examples/model_providers/)에 있습니다. + +### 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`를 보내지 말 것 +- 텍스트 전용 모델을 호출하기 전에 멀티모달 입력을 필터링할 것 +- structured JSON 출력을 지원하지 않는 제공자는 때때로 잘못된 JSON을 생성할 수 있음을 인지할 것 \ No newline at end of file diff --git a/docs/ko/models/litellm.md b/docs/ko/models/litellm.md new file mode 100644 index 000000000..4ae91fa80 --- /dev/null +++ b/docs/ko/models/litellm.md @@ -0,0 +1,77 @@ +--- +search: + exclude: true +--- +# LiteLLM을 통한 임의 모델 사용 + +!!! note + + LiteLLM 통합은 베타 단계입니다. 특히 소규모 모델 제공업체와 함께 사용할 때 문제가 발생할 수 있습니다. 문제를 발견하시면 [Github issues](https://github.com/openai/openai-agents-python/issues)를 통해 보고해 주세요. 신속하게 수정하겠습니다. + +[LiteLLM](https://docs.litellm.ai/docs/)은 단일 인터페이스로 100개 이상의 모델을 사용할 수 있게 해주는 라이브러리입니다. Agents SDK에 LiteLLM 통합을 추가하여, 원하는 어떤 AI 모델이든 사용할 수 있습니다. + +## 설정 + +`litellm`이 사용 가능한지 확인해야 합니다. 선택적 `litellm` 종속성 그룹을 설치하면 됩니다: + +```bash +pip install "openai-agents[litellm]" +``` + +설치가 완료되면, 어떤 에이전트에서든 [`LitellmModel`][agents.extensions.models.litellm_model.LitellmModel]을 사용할 수 있습니다. + +## 예시 + +다음은 완전한 동작 예시입니다. 실행하면 모델 이름과 API 키 입력을 요청합니다. 예를 들어 다음과 같이 입력할 수 있습니다: + +- 모델에 `openai/gpt-4.1`, 그리고 OpenAI API 키 +- 모델에 `anthropic/claude-3-5-sonnet-20240620`, 그리고 Anthropic API 키 +- 기타 + +LiteLLM에서 지원하는 모델 전체 목록은 [litellm providers 문서](https://docs.litellm.ai/docs/providers)를 참고하세요. + +```python +from __future__ import annotations + +import asyncio + +from agents import Agent, Runner, function_tool, set_tracing_disabled +from agents.extensions.models.litellm_model import LitellmModel + +@function_tool +def get_weather(city: str): + print(f"[debug] getting weather for {city}") + return f"The weather in {city} is sunny." + + +async def main(model: str, api_key: str): + agent = Agent( + name="Assistant", + instructions="You only respond in haikus.", + model=LitellmModel(model=model, api_key=api_key), + tools=[get_weather], + ) + + result = await Runner.run(agent, "What's the weather in Tokyo?") + print(result.final_output) + + +if __name__ == "__main__": + # First try to get model/api key from args + import argparse + + parser = argparse.ArgumentParser() + parser.add_argument("--model", type=str, required=False) + parser.add_argument("--api-key", type=str, required=False) + args = parser.parse_args() + + model = args.model + if not model: + model = input("Enter a model name for Litellm: ") + + api_key = args.api_key + if not api_key: + api_key = input("Enter an API key for Litellm: ") + + asyncio.run(main(model, api_key)) +``` \ No newline at end of file diff --git a/docs/ko/multi_agent.md b/docs/ko/multi_agent.md new file mode 100644 index 000000000..2b0ea2af8 --- /dev/null +++ b/docs/ko/multi_agent.md @@ -0,0 +1,41 @@ +--- +search: + exclude: true +--- +# 멀티 에이전트 오케스트레이션 + +오케스트레이션은 앱에서 에이전트가 흐르는 방식을 의미합니다. 어떤 에이전트를 어떤 순서로 실행하며, 다음에 무엇을 할지 어떻게 결정할까요? 에이전트를 오케스트레이션하는 방법은 크게 두 가지입니다: + +1. LLM 에게 결정을 맡기기: LLM 의 지능을 활용해 계획하고 추론하며 그에 따라 다음 단계를 결정 +2. 코드로 오케스트레이션하기: 코드로 에이전트의 흐름을 결정 + +이 패턴들은 섞어서 사용할 수 있습니다. 각각의 트레이드오프는 아래에 설명되어 있습니다. + +## LLM 기반 오케스트레이션 + +에이전트는 instructions, tools, 핸드오프로 무장한 LLM 입니다. 이는 열린 과제가 주어졌을 때 LLM 이 도구를 사용해 행동하고 데이터를 얻으며, 핸드오프를 통해 하위 에이전트에 작업을 위임하면서 과제를 수행할 계획을 자율적으로 세울 수 있음을 의미합니다. 예를 들어, 리서치 에이전트는 다음과 같은 도구를 갖출 수 있습니다: + +- 웹 검색을 통한 온라인 정보 탐색 +- 파일 검색 및 조회를 통한 사내 데이터와 연결 검색 +- 컴퓨터 사용을 통한 컴퓨터 상의 액션 수행 +- 코드 실행을 통한 데이터 분석 +- 기획, 보고서 작성 등에 특화된 에이전트로의 핸드오프 + +이 패턴은 과제가 개방형이고 LLM 의 지능에 의존하고 싶을 때 적합합니다. 여기서 중요한 전술은 다음과 같습니다: + +1. 좋은 프롬프트에 투자하세요. 사용 가능한 도구, 사용 방법, 그리고 운영해야 할 매개변수를 명확히 하세요. +2. 앱을 모니터링하고 반복 개선하세요. 문제가 발생하는 지점을 확인하고 프롬프트를 개선하세요. +3. 에이전트가 자기 성찰과 개선을 할 수 있게 하세요. 예를 들어 루프에서 실행하여 스스로 비판하게 하거나, 에러 메시지를 제공해 개선하게 하세요. +4. 모든 것을 잘하는 범용 에이전트 대신 하나의 작업에 뛰어난 전문 에이전트를 구성하세요. +5. [평가(evals)](https://platform.openai.com/docs/guides/evals)에 투자하세요. 이를 통해 에이전트를 학습시키고 과제 수행 능력을 향상시킬 수 있습니다. + +## 코드 기반 오케스트레이션 + +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) 에 다양한 코드 예제가 있습니다. \ No newline at end of file diff --git a/docs/ko/quickstart.md b/docs/ko/quickstart.md new file mode 100644 index 000000000..35195bac3 --- /dev/null +++ b/docs/ko/quickstart.md @@ -0,0 +1,203 @@ +--- +search: + exclude: true +--- +# 빠른 시작 + +## 프로젝트 및 가상 환경 생성 + +한 번만 수행하면 됩니다. + +```bash +mkdir my_project +cd my_project +python -m venv .venv +``` + +### 가상 환경 활성화 + +새 터미널 세션을 시작할 때마다 실행하세요. + +```bash +source .venv/bin/activate +``` + +### Agents SDK 설치 + +```bash +pip install openai-agents # or `uv add openai-agents`, etc +``` + +### OpenAI API 키 설정 + +키가 없다면 OpenAI API 키를 생성하려면 [이 안내](https://platform.openai.com/docs/quickstart#create-and-export-an-api-key)를 따라주세요. + +```bash +export OPENAI_API_KEY=sk-... +``` + +## 첫 에이전트 생성 + +에이전트는 instructions, 이름, 그리고 선택적 구성(예: `model_config`)으로 정의됩니다 + +```python +from agents import Agent + +agent = Agent( + name="Math Tutor", + instructions="You provide help with math problems. Explain your reasoning at each step and include examples", +) +``` + +## 에이전트 몇 개 더 추가 + +추가 에이전트도 같은 방식으로 정의할 수 있습니다. `handoff_descriptions` 는 핸드오프 라우팅을 결정하는 데 필요한 추가 컨텍스트를 제공합니다 + +```python +from agents import Agent + +history_tutor_agent = Agent( + name="History Tutor", + handoff_description="Specialist agent for historical questions", + instructions="You provide assistance with historical queries. Explain important events and context clearly.", +) + +math_tutor_agent = Agent( + name="Math Tutor", + handoff_description="Specialist agent for math questions", + instructions="You provide help with math problems. Explain your reasoning at each step and include examples", +) +``` + +## 핸드오프 정의 + +각 에이전트에서, 작업을 진행하는 방법을 결정하기 위해 에이전트가 선택할 수 있는 아웃바운드 핸드오프 옵션의 목록을 정의할 수 있습니다. + +```python +triage_agent = Agent( + name="Triage Agent", + instructions="You determine which agent to use based on the user's homework question", + handoffs=[history_tutor_agent, math_tutor_agent] +) +``` + +## 에이전트 오케스트레이션 실행 + +워크플로가 실행되고 분류(triage) 에이전트가 두 전문 에이전트 사이를 올바르게 라우팅하는지 확인해 봅시다 + +```python +from agents import Runner + +async def main(): + result = await Runner.run(triage_agent, "What is the capital of France?") + print(result.final_output) +``` + +## 가드레일 추가 + +입력 또는 출력에 대해 실행할 사용자 지정 가드레일을 정의할 수 있습니다 + +```python +from agents import GuardrailFunctionOutput, Agent, Runner +from pydantic import BaseModel + + +class HomeworkOutput(BaseModel): + is_homework: bool + reasoning: str + +guardrail_agent = Agent( + name="Guardrail check", + instructions="Check if the user is asking about homework.", + output_type=HomeworkOutput, +) + +async def homework_guardrail(ctx, agent, input_data): + result = await Runner.run(guardrail_agent, input_data, context=ctx.context) + final_output = result.final_output_as(HomeworkOutput) + return GuardrailFunctionOutput( + output_info=final_output, + tripwire_triggered=not final_output.is_homework, + ) +``` + +## 전체 통합 + +핸드오프와 입력 가드레일을 사용해 모두 통합한 전체 워크플로를 실행해 봅시다 + +```python +from agents import Agent, InputGuardrail, GuardrailFunctionOutput, Runner +from agents.exceptions import InputGuardrailTripwireTriggered +from pydantic import BaseModel +import asyncio + +class HomeworkOutput(BaseModel): + is_homework: bool + reasoning: str + +guardrail_agent = Agent( + name="Guardrail check", + instructions="Check if the user is asking about homework.", + output_type=HomeworkOutput, +) + +math_tutor_agent = Agent( + name="Math Tutor", + handoff_description="Specialist agent for math questions", + instructions="You provide help with math problems. Explain your reasoning at each step and include examples", +) + +history_tutor_agent = Agent( + name="History Tutor", + handoff_description="Specialist agent for historical questions", + instructions="You provide assistance with historical queries. Explain important events and context clearly.", +) + + +async def homework_guardrail(ctx, agent, input_data): + result = await Runner.run(guardrail_agent, input_data, context=ctx.context) + final_output = result.final_output_as(HomeworkOutput) + return GuardrailFunctionOutput( + output_info=final_output, + tripwire_triggered=not final_output.is_homework, + ) + +triage_agent = Agent( + name="Triage Agent", + instructions="You determine which agent to use based on the user's homework question", + handoffs=[history_tutor_agent, math_tutor_agent], + input_guardrails=[ + InputGuardrail(guardrail_function=homework_guardrail), + ], +) + +async def main(): + # Example 1: History question + try: + result = await Runner.run(triage_agent, "who was the first president of the united states?") + print(result.final_output) + except InputGuardrailTripwireTriggered as e: + print("Guardrail blocked this input:", e) + + # Example 2: General/philosophical question + try: + result = await Runner.run(triage_agent, "What is the meaning of life?") + print(result.final_output) + except InputGuardrailTripwireTriggered as e: + print("Guardrail blocked this input:", e) + +if __name__ == "__main__": + asyncio.run(main()) +``` + +## 트레이스 보기 + +에이전트 실행 중에 무슨 일이 일어났는지 검토하려면 [OpenAI 대시보드의 Trace viewer](https://platform.openai.com/traces)로 이동해 실행 트레이스를 확인하세요. + +## 다음 단계 + +더 복잡한 에이전트형 플로우를 만드는 방법: + +- [에이전트](agents.md) 구성 방법 알아보기 +- [에이전트 실행](running_agents.md) 알아보기 +- [도구](tools.md), [가드레일](guardrails.md), [모델](models/index.md) 알아보기 \ No newline at end of file diff --git a/docs/ko/realtime/guide.md b/docs/ko/realtime/guide.md new file mode 100644 index 000000000..27985d446 --- /dev/null +++ b/docs/ko/realtime/guide.md @@ -0,0 +1,176 @@ +--- +search: + exclude: true +--- +# 가이드 + +이 가이드는 OpenAI Agents SDK의 실시간 기능을 사용하여 음성 기반 AI 에이전트를 구축하는 방법을 자세히 설명합니다. + +!!! warning "베타 기능" +Realtime agents는 베타 단계입니다. 구현을 개선하는 과정에서 호환성 문제가 발생할 수 있습니다. + +## 개요 + +Realtime agents는 실시간으로 오디오와 텍스트 입력을 처리하고 실시간 오디오로 응답하는 대화형 흐름을 제공합니다. OpenAI의 Realtime API와 지속적인 연결을 유지하여 낮은 지연의 자연스러운 음성 대화와 원활한 인터럽션(중단 처리)을 지원합니다. + +## 아키텍처 + +### 핵심 구성 요소 + +실시간 시스템은 다음과 같은 주요 구성 요소로 이루어져 있습니다: + +- **RealtimeAgent**: instructions, tools 및 핸드오프로 구성된 에이전트 +- **RealtimeRunner**: 구성을 관리합니다. `runner.run()`을 호출하여 세션을 얻을 수 있습니다. +- **RealtimeSession**: 단일 상호작용 세션입니다. 일반적으로 사용자 대화가 시작될 때 하나를 만들고, 대화가 끝날 때까지 유지합니다. +- **RealtimeModel**: 기본 모델 인터페이스(일반적으로 OpenAI의 WebSocket 구현) + +### 세션 흐름 + +일반적인 실시간 세션의 흐름은 다음과 같습니다: + +1. instructions, tools 및 핸드오프로 **RealtimeAgent를 생성**합니다 +2. 에이전트와 구성 옵션으로 **RealtimeRunner를 설정**합니다 +3. `await runner.run()`을 사용해 **세션을 시작**하고 RealtimeSession을 반환받습니다 +4. `send_audio()` 또는 `send_message()`를 사용해 **오디오 또는 텍스트 메시지 전송**합니다 +5. 세션을 반복(iterate)하면서 **이벤트를 수신**합니다 - 오디오 출력, 전사 결과, 도구 호출, 핸드오프, 오류 등이 포함됩니다 +6. 사용자가 에이전트가 말하는 동안 말을 겹쳐 할 때 **인터럽션(중단 처리)** 을 처리합니다. 현재 오디오 생성이 자동으로 중단됩니다 + +세션은 대화 기록을 유지하고 실시간 모델과의 지속적인 연결을 관리합니다. + +## 에이전트 구성 + +RealtimeAgent는 일반 Agent 클래스와 유사하지만 몇 가지 중요한 차이점이 있습니다. 전체 API 세부 정보는 [`RealtimeAgent`][agents.realtime.agent.RealtimeAgent] API 레퍼런스를 참조하세요. + +일반 에이전트와의 주요 차이점: + +- 모델 선택은 에이전트 수준이 아닌 세션 수준에서 구성합니다 +- structured output 지원이 없습니다 (`outputType` 미지원) +- 음성은 에이전트별로 설정할 수 있으나 첫 번째 에이전트가 말한 이후에는 변경할 수 없습니다 +- tools, 핸드오프, instructions 등의 다른 기능은 동일하게 동작합니다 + +## 세션 구성 + +### 모델 설정 + +세션 구성에서는 기본 실시간 모델 동작을 제어할 수 있습니다. 모델 이름(예: `gpt-4o-realtime-preview`), 음성 선택(alloy, echo, fable, onyx, nova, shimmer), 지원 모달리티(텍스트 및/또는 오디오)를 설정할 수 있습니다. 오디오 포맷은 입력과 출력 모두에 대해 설정할 수 있으며 기본값은 PCM16입니다. + +### 오디오 구성 + +오디오 설정은 세션이 음성 입력과 출력을 처리하는 방식을 제어합니다. Whisper와 같은 모델을 사용한 입력 오디오 전사, 언어 기본값 설정, 도메인 특화 용어의 정확도를 높이기 위한 전사 프롬프트 제공이 가능합니다. 턴 감지 설정은 에이전트가 언제 응답을 시작하고 종료해야 하는지를 제어하며, 음성 활동 감지 임계값, 무음 지속 시간, 감지된 음성 주변 패딩 등의 옵션을 제공합니다. + +## 도구와 함수 + +### 도구 추가 + +일반 에이전트와 마찬가지로, 실시간 에이전트는 대화 중 실행되는 함수 도구를 지원합니다: + +```python +from agents import function_tool + +@function_tool +def get_weather(city: str) -> str: + """Get current weather for a city.""" + # Your weather API logic here + return f"The weather in {city} is sunny, 72°F" + +@function_tool +def book_appointment(date: str, time: str, service: str) -> str: + """Book an appointment.""" + # Your booking logic here + return f"Appointment booked for {service} on {date} at {time}" + +agent = RealtimeAgent( + name="Assistant", + instructions="You can help with weather and appointments.", + tools=[get_weather, book_appointment], +) +``` + +## 핸드오프 + +### 핸드오프 생성 + +핸드오프를 통해 전문화된 에이전트 간에 대화를 전환할 수 있습니다. + +```python +from agents.realtime import realtime_handoff + +# Specialized agents +billing_agent = RealtimeAgent( + name="Billing Support", + instructions="You specialize in billing and payment issues.", +) + +technical_agent = RealtimeAgent( + name="Technical Support", + instructions="You handle technical troubleshooting.", +) + +# Main agent with handoffs +main_agent = RealtimeAgent( + name="Customer Service", + instructions="You are the main customer service agent. Hand off to specialists when needed.", + handoffs=[ + realtime_handoff(billing_agent, tool_description="Transfer to billing support"), + realtime_handoff(technical_agent, tool_description="Transfer to technical support"), + ] +) +``` + +## 이벤트 처리 + +세션은 세션 객체를 반복(iterate)하여 수신할 수 있는 이벤트를 스트리밍합니다. 이벤트에는 오디오 출력 청크, 전사 결과, 도구 실행 시작 및 종료, 에이전트 핸드오프, 오류 등이 포함됩니다. 처리해야 할 주요 이벤트는 다음과 같습니다: + +- **audio**: 에이전트 응답의 원시 오디오 데이터 +- **audio_end**: 에이전트가 말하기를 완료함 +- **audio_interrupted**: 사용자가 에이전트를 중단함 +- **tool_start/tool_end**: 도구 실행 라이프사이클 +- **handoff**: 에이전트 핸드오프 발생 +- **error**: 처리 중 오류 발생 + +전체 이벤트 상세는 [`RealtimeSessionEvent`][agents.realtime.events.RealtimeSessionEvent]를 참조하세요. + +## 가드레일 + +실시간 에이전트에서는 출력 가드레일만 지원합니다. 실시간 생성 중 성능 문제를 피하기 위해 가드레일은 디바운스되어 주기적으로 실행되며(모든 단어마다 실행되지 않음), 기본 디바운스 길이는 100자이며 구성 가능합니다. + +가드레일은 `RealtimeAgent`에 직접 연결하거나 세션의 `run_config`를 통해 제공할 수 있습니다. 두 소스의 가드레일은 함께 실행됩니다. + +```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)], +) +``` + +가드레일이 트리거되면 `guardrail_tripped` 이벤트를 생성하고 에이전트의 현재 응답을 인터럽트할 수 있습니다. 디바운스 동작은 안전성과 실시간 성능 요구 사이의 균형을 맞추는 데 도움이 됩니다. 텍스트 에이전트와 달리, 실시간 에이전트는 가드레일이 작동해도 Exception을 발생시키지 않습니다. + +## 오디오 처리 + +[`session.send_audio(audio_bytes)`][agents.realtime.session.RealtimeSession.send_audio]를 사용해 오디오를, [`session.send_message()`][agents.realtime.session.RealtimeSession.send_message]를 사용해 텍스트를 세션으로 전송합니다. + +오디오 출력의 경우 `audio` 이벤트를 수신하여 선호하는 오디오 라이브러리로 재생하세요. 사용자가 에이전트를 중단할 때 즉시 재생을 중지하고 대기 중인 오디오를 모두 비우기 위해 `audio_interrupted` 이벤트를 반드시 수신하세요. + +## 모델 직접 액세스 + +기저 모델에 접근하여 커스텀 리스너를 추가하거나 고급 작업을 수행할 수 있습니다: + +```python +# Add a custom listener to the model +session.model.add_listener(my_custom_listener) +``` + +이는 연결에 대한 하위 수준 제어가 필요한 고급 사용 사례를 위해 [`RealtimeModel`][agents.realtime.model.RealtimeModel] 인터페이스에 직접 액세스할 수 있게 해줍니다. + +## 코드 예제 + +완전한 동작 코드 예제는 [examples/realtime 디렉터리](https://github.com/openai/openai-agents-python/tree/main/examples/realtime)를 참고하세요. UI 구성 요소가 있는 데모와 없는 데모를 모두 포함합니다. \ No newline at end of file diff --git a/docs/ko/realtime/quickstart.md b/docs/ko/realtime/quickstart.md new file mode 100644 index 000000000..b4692563a --- /dev/null +++ b/docs/ko/realtime/quickstart.md @@ -0,0 +1,179 @@ +--- +search: + exclude: true +--- +# 빠른 시작 + +실시간 에이전트는 OpenAI의 Realtime API를 사용해 AI 에이전트와 음성 대화를 가능하게 합니다. 이 가이드는 첫 실시간 음성 에이전트를 만드는 과정을 안내합니다. + +!!! warning "베타 기능" +실시간 에이전트는 베타 버전입니다. 구현을 개선하는 동안 호환성 깨짐이 발생할 수 있습니다. + +## 사전 준비 + +- Python 3.9 이상 +- 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 your responses conversational and friendly.", +) +``` + +### 3. 러너 설정 + +```python +runner = RealtimeRunner( + starting_agent=agent, + config={ + "model_settings": { + "model_name": "gpt-4o-realtime-preview", + "voice": "alloy", + "modalities": ["text", "audio"], + } + } +) +``` + +### 4. 세션 시작 + +```python +async def main(): + # Start the realtime session + session = await runner.run() + + async with session: + # Send a text message to start the conversation + await session.send_message("Hello! How are you today?") + + # The agent will stream back audio in real-time (not shown in this example) + # Listen for events from the session + async for event in session: + if event.type == "response.audio_transcript.done": + print(f"Assistant: {event.transcript}") + elif event.type == "conversation.item.input_audio_transcription.completed": + print(f"User: {event.transcript}") + +# Run the session +asyncio.run(main()) +``` + +## 전체 예시 + +다음은 완전한 작동 예시입니다: + +```python +import asyncio +from agents.realtime import RealtimeAgent, RealtimeRunner + +async def main(): + # Create the agent + agent = RealtimeAgent( + name="Assistant", + instructions="You are a helpful voice assistant. Keep responses brief and conversational.", + ) + + # Set up the runner with configuration + runner = RealtimeRunner( + starting_agent=agent, + config={ + "model_settings": { + "model_name": "gpt-4o-realtime-preview", + "voice": "alloy", + "modalities": ["text", "audio"], + "input_audio_transcription": { + "model": "whisper-1" + }, + "turn_detection": { + "type": "server_vad", + "threshold": 0.5, + "prefix_padding_ms": 300, + "silence_duration_ms": 200 + } + } + } + ) + + # Start the session + session = await runner.run() + + async with session: + print("Session started! The agent will stream audio responses in real-time.") + + # Process events + async for event in session: + if event.type == "response.audio_transcript.done": + print(f"Assistant: {event.transcript}") + elif event.type == "conversation.item.input_audio_transcription.completed": + print(f"User: {event.transcript}") + elif event.type == "error": + print(f"Error: {event.error}") + break + +if __name__ == "__main__": + asyncio.run(main()) +``` + +## 구성 옵션 + +### 모델 설정 + +- `model_name`: 사용 가능한 실시간 모델 중에서 선택 (예: `gpt-4o-realtime-preview`) +- `voice`: 음성 선택 (`alloy`, `echo`, `fable`, `onyx`, `nova`, `shimmer`) +- `modalities`: 텍스트 및/또는 오디오 활성화 (`["text", "audio"]`) + +### 오디오 설정 + +- `input_audio_format`: 입력 오디오 형식 (`pcm16`, `g711_ulaw`, `g711_alaw`) +- `output_audio_format`: 출력 오디오 형식 +- `input_audio_transcription`: 음성 인식(전사) 구성 + +### 턴 감지 + +- `type`: 감지 방식 (`server_vad`, `semantic_vad`) +- `threshold`: 음성 활동 임계값 (0.0-1.0) +- `silence_duration_ms`: 턴 종료를 감지할 무음 지속 시간 +- `prefix_padding_ms`: 발화 전 오디오 패딩 + +## 다음 단계 + +- [실시간 에이전트 자세히 알아보기](guide.md) +- [examples/realtime](https://github.com/openai/openai-agents-python/tree/main/examples/realtime) 폴더에서 동작하는 code examples 확인 +- 에이전트에 도구 추가 +- 에이전트 간 핸드오프 구현 +- 안전을 위한 가드레일 설정 + +## 인증 + +환경 변수에 OpenAI API 키가 설정되어 있는지 확인하세요: + +```bash +export OPENAI_API_KEY="your-api-key-here" +``` + +또는 세션을 생성할 때 직접 전달하세요: + +```python +session = await runner.run(model_config={"api_key": "your-api-key"}) +``` \ No newline at end of file diff --git a/docs/ko/release.md b/docs/ko/release.md new file mode 100644 index 000000000..255153208 --- /dev/null +++ b/docs/ko/release.md @@ -0,0 +1,32 @@ +--- +search: + exclude: true +--- +# 릴리스 프로세스/변경 로그 + +이 프로젝트는 `0.Y.Z` 형식의 약간 수정된 시맨틱 버저닝을 따릅니다. 앞의 `0`은 SDK가 아직 빠르게 발전 중임을 나타냅니다. 각 구성 요소는 다음과 같이 증가됩니다: + +## 마이너(`Y`) 버전 + +베타로 표시되지 않은 모든 공개 인터페이스에 **하위 호환성이 깨지는 변경 사항(브레이킹 체인지)** 이 있을 때 마이너 버전 `Y`를 증가시킵니다. 예를 들어, `0.0.x`에서 `0.1.x`로의 변경에는 브레이킹 체인지가 포함될 수 있습니다. + +브레이킹 체인지를 피하고 싶다면, 프로젝트에서 `0.0.x` 버전에 고정하기를 권장합니다. + +## 패치(`Z`) 버전 + +하위 호환성이 깨지지 않는 변경 사항에 대해 `Z`를 증가시킵니다: + +- 버그 수정 +- 새로운 기능 +- 비공개 인터페이스 변경 +- 베타 기능 업데이트 + +## 브레이킹 체인지 변경 로그 + +### 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`를 상속하는 모든 클래스에 이 매개변수를 추가해야 합니다. \ No newline at end of file diff --git a/docs/ko/repl.md b/docs/ko/repl.md new file mode 100644 index 000000000..82375addb --- /dev/null +++ b/docs/ko/repl.md @@ -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` 키보드 단축키를 사용하세요. \ No newline at end of file diff --git a/docs/ko/results.md b/docs/ko/results.md new file mode 100644 index 000000000..b5205b6a5 --- /dev/null +++ b/docs/ko/results.md @@ -0,0 +1,56 @@ +--- +search: + exclude: true +--- +# 결과 + +`Runner.run` 메서드를 호출하면 다음 중 하나를 받습니다: + +- `run` 또는 `run_sync` 를 호출한 경우 [`RunResult`][agents.result.RunResult] +- `run_streamed` 를 호출한 경우 [`RunResultStreaming`][agents.result.RunResultStreaming] + +이 둘은 모두 [`RunResultBase`][agents.result.RunResultBase] 를 상속하며, 대부분의 유용한 정보가 여기에 있습니다. + +## 최종 출력 + +[`final_output`][agents.result.RunResultBase.final_output] 속성에는 마지막으로 실행된 에이전트의 최종 출력이 들어 있습니다. 이는 다음 중 하나입니다: + +- 마지막 에이전트에 `output_type` 이 정의되지 않았다면 `str` +- 에이전트에 output type 이 정의되어 있다면 `last_agent.output_type` 타입의 객체 + +!!! note + + `final_output` 의 타입은 `Any` 입니다. 핸드오프 때문에 정적으로 타입을 고정할 수 없습니다. 핸드오프가 발생하면 어떤 에이전트든 마지막 에이전트가 될 수 있으므로, 가능한 출력 타입 집합을 정적으로 알 수 없습니다. + +## 다음 턴 입력 + +[`result.to_input_list()`][agents.result.RunResultBase.to_input_list] 를 사용하면 결과를 입력 리스트로 변환하여, 원래 제공한 입력에 에이전트 실행 중 생성된 항목들을 이어 붙일 수 있습니다. 이를 통해 한 번의 에이전트 실행에서 나온 출력을 다른 실행에 전달하거나, 루프에서 실행하면서 매번 새로운 사용자 입력을 추가하기가 편리해집니다. + +## 마지막 에이전트 + +[`last_agent`][agents.result.RunResultBase.last_agent] 속성에는 마지막으로 실행된 에이전트가 들어 있습니다. 애플리케이션에 따라, 이는 다음에 사용자가 무언가를 입력할 때 유용한 경우가 많습니다. 예를 들어, 프론트라인 트리아지 에이전트가 언어별 에이전트로 핸드오프하는 경우, 마지막 에이전트를 저장해 두었다가 다음에 사용자가 에이전트에 메시지를 보낼 때 재사용할 수 있습니다. + +## 새 항목 + +[`new_items`][agents.result.RunResultBase.new_items] 속성에는 실행 중에 생성된 새 항목이 들어 있습니다. 항목은 [`RunItem`][agents.items.RunItem] 입니다. 실행 항목은 LLM 이 생성한 원문 항목을 감싼 래퍼입니다. + +- [`MessageOutputItem`][agents.items.MessageOutputItem] 은 LLM 의 메시지를 나타냅니다. 원문 항목은 생성된 메시지입니다. +- [`HandoffCallItem`][agents.items.HandoffCallItem] 은 LLM 이 핸드오프 도구를 호출했음을 나타냅니다. 원문 항목은 LLM 의 도구 호출 항목입니다. +- [`HandoffOutputItem`][agents.items.HandoffOutputItem] 은 핸드오프가 발생했음을 나타냅니다. 원문 항목은 핸드오프 도구 호출에 대한 도구 응답입니다. 항목에서 소스/타깃 에이전트에도 접근할 수 있습니다. +- [`ToolCallItem`][agents.items.ToolCallItem] 은 LLM 이 도구를 호출했음을 나타냅니다. +- [`ToolCallOutputItem`][agents.items.ToolCallOutputItem] 은 도구가 호출되었음을 나타냅니다. 원문 항목은 도구 응답입니다. 항목에서 도구 출력에도 접근할 수 있습니다. +- [`ReasoningItem`][agents.items.ReasoningItem] 은 LLM 의 추론 항목을 나타냅니다. 원문 항목은 생성된 추론입니다. + +## 기타 정보 + +### 가드레일 결과 + +[`input_guardrail_results`][agents.result.RunResultBase.input_guardrail_results] 및 [`output_guardrail_results`][agents.result.RunResultBase.output_guardrail_results] 속성에는 (있는 경우) 가드레일의 결과가 들어 있습니다. 가드레일 결과에는 기록하거나 저장하고 싶은 유용한 정보가 들어 있을 수 있으므로 이를 제공해 드립니다. + +### 원문 응답 + +[`raw_responses`][agents.result.RunResultBase.raw_responses] 속성에는 LLM 이 생성한 [`ModelResponse`][agents.items.ModelResponse] 가 들어 있습니다. + +### 원본 입력 + +[`input`][agents.result.RunResultBase.input] 속성에는 `run` 메서드에 제공한 원본 입력이 들어 있습니다. 대부분의 경우 필요하지 않겠지만, 필요한 경우를 대비해 제공됩니다. \ No newline at end of file diff --git a/docs/ko/running_agents.md b/docs/ko/running_agents.md new file mode 100644 index 000000000..437fbe06c --- /dev/null +++ b/docs/ko/running_agents.md @@ -0,0 +1,141 @@ +--- +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` 의 run 메서드를 사용할 때 시작 에이전트와 입력을 전달합니다. 입력은 문자열(사용자 메시지로 간주됨) 또는 OpenAI Responses API 의 항목 목록일 수 있습니다. + +그 후 러너는 다음과 같은 루프를 실행합니다: + +1. 현재 에이전트와 현재 입력으로 LLM 을 호출합니다 +2. LLM 이 출력을 생성합니다 + 1. LLM 이 `final_output` 을 반환하면 루프가 종료되고 결과를 반환합니다 + 2. LLM 이 핸드오프를 수행하면 현재 에이전트와 입력을 업데이트하고 루프를 다시 실행합니다 + 3. LLM 이 도구 호출을 생성하면 해당 도구 호출을 실행하고 결과를 추가한 뒤 루프를 다시 실행합니다 +3. 전달된 `max_turns` 를 초과하면 [`MaxTurnsExceeded`][agents.exceptions.MaxTurnsExceeded] 예외를 발생시킵니다 + +!!! note + + LLM 출력이 "최종 출력"으로 간주되는 규칙은 원하는 유형의 텍스트 출력을 생성하고, 도구 호출이 없는 경우입니다. + +## 스트리밍 + +스트리밍을 사용하면 LLM 이 실행되는 동안 스트리밍 이벤트를 추가로 수신할 수 있습니다. 스트림이 완료되면 [`RunResultStreaming`][agents.result.RunResultStreaming] 에는 실행에 대한 전체 정보와 생성된 모든 새 출력이 포함됩니다. 스트리밍 이벤트는 `.stream_events()` 를 호출하여 받을 수 있습니다. 자세한 내용은 [스트리밍 가이드](streaming.md)를 참조하세요. + +## 실행 설정 + +`run_config` 매개변수는 에이전트 실행에 대한 일부 전역 설정을 구성할 수 있게 합니다: + +- [`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` 를 설정할 수 있습니다 +- [`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] 문서를 참조하세요 +- [`tracing_disabled`][agents.run.RunConfig.tracing_disabled]: 전체 실행에 대해 [트레이싱](tracing.md) 을 비활성화할 수 있습니다 +- [`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]: 모든 트레이스에 포함할 메타데이터입니다 + +## 대화/채팅 스레드 + +어떤 run 메서드를 호출하든 하나 이상의 에이전트가 실행될 수 있습니다(따라서 하나 이상의 LLM 호출이 발생). 하지만 이는 채팅 대화에서 단일 논리적 턴을 의미합니다. 예를 들어: + +1. 사용자 턴: 사용자가 텍스트를 입력 +2. Runner 실행: 첫 번째 에이전트가 LLM 을 호출하고 도구를 실행하며 두 번째 에이전트로 핸드오프를 수행하고, 두 번째 에이전트가 더 많은 도구를 실행한 후 출력을 생성 + +에이전트 실행이 끝나면 사용자에게 무엇을 보여줄지 선택할 수 있습니다. 예를 들어 에이전트가 생성한 모든 새 항목을 보여주거나 최종 출력만 보여줄 수 있습니다. 어느 쪽이든 사용자가 후속 질문을 할 수 있으며, 그 경우 run 메서드를 다시 호출하면 됩니다. + +### 수동 대화 관리 + +다음 턴에 대한 입력을 얻기 위해 [`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](sessions.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") + + 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.md) 를 참조하세요. + +## 장기 실행 에이전트 및 휴먼인더루프 (HITL) + +Agents SDK 의 [Temporal](https://temporal.io/) 통합을 사용하면 휴먼인더루프 작업을 포함한 내구성 있는 장기 실행 워크플로를 수행할 수 있습니다. 장기 실행 작업을 완료하기 위해 Temporal 과 Agents SDK 가 함께 작동하는 데모는 [이 동영상](https://www.youtube.com/watch?v=fFBZqzT4DD8)에서 확인하고, [여기 문서](https://github.com/temporalio/sdk-python/tree/main/temporalio/contrib/openai_agents)도 참고하세요. + +## 예외 + +SDK 는 특정 경우에 예외를 발생시킵니다. 전체 목록은 [`agents.exceptions`][] 에 있습니다. 개요는 다음과 같습니다: + +- [`AgentsException`][agents.exceptions.AgentsException]: SDK 내에서 발생하는 모든 예외의 기본 클래스입니다. 다른 모든 구체적인 예외가 파생되는 일반적인 타입으로 사용됩니다 +- [`MaxTurnsExceeded`][agents.exceptions.MaxTurnsExceeded]: 에이전트 실행이 `max_turns` 제한을 초과할 때, 즉 `Runner.run`, `Runner.run_sync`, `Runner.run_streamed` 메서드에서 전달된 제한을 넘었을 때 발생합니다. 이는 지정된 상호작용 턴 수 내에 에이전트가 작업을 완료하지 못했음을 나타냅니다 +- [`ModelBehaviorError`][agents.exceptions.ModelBehaviorError]: 기본 모델(LLM) 이 예기치 않거나 잘못된 출력을 생성할 때 발생합니다. 다음을 포함할 수 있습니다: + - 잘못된 JSON: 특히 특정 `output_type` 이 정의된 경우, 도구 호출이나 직접 출력에서 모델이 잘못된 JSON 구조를 제공하는 경우 + - 예기치 않은 도구 관련 실패: 모델이 예상 방식으로 도구를 사용하지 못하는 경우 +- [`UserError`][agents.exceptions.UserError]: SDK 를 사용하는 과정에서(즉, SDK 를 사용하는 코드를 작성하는 사람인 당신이) 오류를 만들었을 때 발생합니다. 이는 보통 잘못된 코드 구현, 잘못된 구성, SDK 의 API 오용으로 인해 발생합니다 +- [`InputGuardrailTripwireTriggered`][agents.exceptions.InputGuardrailTripwireTriggered], [`OutputGuardrailTripwireTriggered`][agents.exceptions.OutputGuardrailTripwireTriggered]: 각각 입력 가드레일 또는 출력 가드레일의 조건이 충족되었을 때 발생합니다. 입력 가드레일은 처리 전에 들어오는 메시지를 검사하고, 출력 가드레일은 전달 전에 에이전트의 최종 응답을 검사합니다 \ No newline at end of file diff --git a/docs/ko/sessions.md b/docs/ko/sessions.md new file mode 100644 index 000000000..5d81da3a8 --- /dev/null +++ b/docs/ko/sessions.md @@ -0,0 +1,324 @@ +--- +search: + exclude: true +--- +# 세션 + +Agents SDK 는 여러 에이전트 실행(run) 간의 대화 이력을 자동으로 유지하는 내장 세션 메모리를 제공합니다. 이를 통해 턴 사이에 `.to_input_list()` 를 수동으로 처리할 필요가 없습니다. + +Sessions 는 특정 세션의 대화 이력을 저장하여, 명시적인 수동 메모리 관리 없이도 에이전트가 문맥을 유지할 수 있게 합니다. 이는 에이전트가 이전 상호작용을 기억해야 하는 채팅 애플리케이션이나 멀티 턴 대화에 특히 유용합니다. + +## 빠른 시작 + +```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()` 를 수동으로 호출하고 실행 사이의 대화 상태를 관리할 필요가 없어집니다. + +## 메모리 작업 + +### 기본 작업 + +Sessions 는 대화 이력을 관리하기 위한 여러 작업을 지원합니다: + +```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") +``` + +### 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 +) +``` + +## 사용자 지정 메모리 구현 + +[`Session`][agents.memory.session.Session] 프로토콜을 따르는 클래스를 만들어 직접 세션 메모리를 구현할 수 있습니다: + +```python +from agents.memory import Session +from typing import List + +class MyCustomSession: + """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[dict]: + """Retrieve conversation history for this session.""" + # Your implementation here + pass + + async def add_items(self, items: List[dict]) -> None: + """Store new items for this session.""" + # Your implementation here + pass + + async def pop_item(self) -> dict | 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")`) 사용 +- 프로덕션 시스템에서는 사용자 지정 세션 백엔드(Redis, PostgreSQL 등) 구현 고려 + +### 세션 관리 + +```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 구현 \ No newline at end of file diff --git a/docs/ko/streaming.md b/docs/ko/streaming.md new file mode 100644 index 000000000..83da9ca31 --- /dev/null +++ b/docs/ko/streaming.md @@ -0,0 +1,91 @@ +--- +search: + exclude: true +--- +# 스트리밍 + +스트리밍을 사용하면 에이전트 실행 진행 상황의 업데이트를 구독할 수 있습니다. 이는 최종 사용자에게 진행 상황과 부분 응답을 보여주는 데 유용합니다. + +스트리밍하려면 [`Runner.run_streamed()`][agents.run.Runner.run_streamed]를 호출하면 [`RunResultStreaming`][agents.result.RunResultStreaming]을 받습니다. `result.stream_events()`를 호출하면 아래에 설명된 [`StreamEvent`][agents.stream_events.StreamEvent] 객체의 비동기 스트림을 얻습니다. + +## 원문 응답 이벤트 + +[`RawResponsesStreamEvent`][agents.stream_events.RawResponsesStreamEvent]는 LLM에서 직접 전달되는 원문 이벤트입니다. OpenAI Responses API 형식이며, 각 이벤트에는 타입(`response.created`, `response.output_text.delta` 등)과 데이터가 있습니다. 이 이벤트는 생성 즉시 사용자에게 응답 메시지를 스트리밍하려는 경우에 유용합니다. + +예를 들어, 다음은 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()) +``` + +## 실행 항목 이벤트와 에이전트 이벤트 + +[`RunItemStreamEvent`][agents.stream_events.RunItemStreamEvent]는 상위 수준 이벤트입니다. 항목이 완전히 생성되었을 때 알려줍니다. 이를 통해 각 토큰 대신 "메시지 생성됨", "도구 실행됨" 등의 수준에서 진행 상황 업데이트를 전달할 수 있습니다. 이와 유사하게, [`AgentUpdatedStreamEvent`][agents.stream_events.AgentUpdatedStreamEvent]는 현재 에이전트가 변경될 때(예: 핸드오프 결과) 업데이트를 제공합니다. + +예를 들어, 다음은 원문 이벤트를 무시하고 사용자에게 업데이트를 스트리밍합니다. + +```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()) +``` \ No newline at end of file diff --git a/docs/ko/tools.md b/docs/ko/tools.md new file mode 100644 index 000000000..b1f522af0 --- /dev/null +++ b/docs/ko/tools.md @@ -0,0 +1,415 @@ +--- +search: + exclude: true +--- +# 도구 + +도구는 에이전트가 동작을 수행하도록 합니다. 예를 들어 데이터 가져오기, 코드 실행, 외부 API 호출, 심지어 컴퓨터 사용까지 가능합니다. Agent SDK에는 세 가지 종류의 도구가 있습니다: + +- 호스티드 툴: 이 도구들은 AI 모델과 함께 LLM 서버에서 실행됩니다. OpenAI는 retrieval, 웹 검색 및 컴퓨터 사용을 호스티드 툴로 제공합니다. +- 함수 호출: 모든 Python 함수를 도구로 사용할 수 있습니다. +- 도구로서의 에이전트: 에이전트를 도구로 사용하여, 에이전트가 핸드오프 없이 다른 에이전트를 호출할 수 있습니다. + +## 호스티드 툴 + +OpenAI는 [`OpenAIResponsesModel`][agents.models.openai_responses.OpenAIResponsesModel] 사용 시 몇 가지 기본 제공 도구를 제공합니다: + +- [`WebSearchTool`][agents.tool.WebSearchTool]은 에이전트가 웹을 검색할 수 있게 해줍니다. +- [`FileSearchTool`][agents.tool.FileSearchTool]은 OpenAI 벡터 스토어에서 정보를 조회할 수 있게 해줍니다. +- [`ComputerTool`][agents.tool.ComputerTool]은 컴퓨터 사용 작업을 자동화할 수 있게 해줍니다. +- [`CodeInterpreterTool`][agents.tool.CodeInterpreterTool]은 LLM이 샌드박스 환경에서 코드를 실행할 수 있게 해줍니다. +- [`HostedMCPTool`][agents.tool.HostedMCPTool]은 원격 MCP 서버의 도구를 모델에 노출합니다. +- [`ImageGenerationTool`][agents.tool.ImageGenerationTool]은 프롬프트로부터 이미지를 생성합니다. +- [`LocalShellTool`][agents.tool.LocalShellTool]은 로컬 머신에서 셸 명령을 실행합니다. + +```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) +``` + +## 함수 도구 + +어떤 Python 함수든 도구로 사용할 수 있습니다. Agents SDK가 도구 설정을 자동으로 처리합니다: + +- 도구 이름은 Python 함수 이름이 됩니다(직접 이름을 제공할 수도 있음) +- 도구 설명은 함수의 docstring에서 가져옵니다(직접 설명을 제공할 수도 있음) +- 함수 입력에 대한 스키마는 함수의 인자에서 자동으로 생성됩니다 +- 각 입력의 설명은 비활성화하지 않는 한 함수의 docstring에서 가져옵니다 + +Python의 `inspect` 모듈을 사용해 함수 시그니처를 추출하고, [`griffe`](https://mkdocstrings.github.io/griffe/)로 docstring을 파싱하며 `pydantic`으로 스키마를 생성합니다. + +```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 "" + + +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" + } + ``` + +### 사용자 정의 함수 도구 + +때로는 Python 함수를 도구로 사용하고 싶지 않을 수 있습니다. 원한다면 직접 [`FunctionTool`][agents.tool.FunctionTool]을 생성할 수 있습니다. 다음을 제공해야 합니다: + +- `name` +- `description` +- `params_json_schema` — 인자에 대한 JSON 스키마 +- `on_invoke_tool` — [`ToolContext`][agents.tool_context.ToolContext]와 JSON 문자열 형태의 인자를 받아, 도구 출력을 문자열로 반환해야 하는 비동기 함수 + +```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. docstring 파싱에는 `griffe`를 사용합니다. 지원되는 docstring 형식은 `google`, `sphinx`, `numpy`입니다. docstring 형식은 자동 감지를 시도하지만 최선의 노력일 뿐이며, `function_tool` 호출 시 명시적으로 설정할 수 있습니다. `use_docstring_info`를 `False`로 설정하여 docstring 파싱을 비활성화할 수도 있습니다. + +스키마 추출을 위한 코드는 [`agents.function_schema`][]에 있습니다. + +## 도구로서의 에이전트 + +일부 워크플로에서는 핸드오프 대신 중앙 에이전트가 특화된 에이전트 네트워크를 오케스트레이션하도록 하고 싶을 수 있습니다. 에이전트를 도구로 모델링하여 이를 구현할 수 있습니다. + +```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`를 설정할 수 없습니다. 고급 사용 사례의 경우 도구 구현에서 `Runner.run`을 직접 사용하세요: + +```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) +``` + +### 사용자 정의 출력 추출 + +특정 경우 중앙 에이전트에 반환하기 전에 툴 에이전트의 출력을 수정하고 싶을 수 있습니다. 다음과 같은 상황에서 유용합니다: + +- 하위 에이전트의 대화 기록에서 특정 정보(예: 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, +) +``` + +### 조건부 도구 활성화 + +런타임에 `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에서 완전히 숨겨지므로 다음과 같은 경우에 유용합니다: +- 사용자 권한에 따른 기능 게이팅 +- 환경별 도구 가용성(dev vs prod) +- 서로 다른 도구 구성을 A/B 테스트 +- 런타임 상태에 따른 동적 도구 필터링 + +## 함수 도구에서의 오류 처리 + +`@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` 함수 내부에서 오류를 처리해야 합니다. \ No newline at end of file diff --git a/docs/ko/tracing.md b/docs/ko/tracing.md new file mode 100644 index 000000000..af059b969 --- /dev/null +++ b/docs/ko/tracing.md @@ -0,0 +1,151 @@ +--- +search: + exclude: true +--- +# 트레이싱 + +Agents SDK에는 내장 트레이싱이 포함되어 있어 에이전트 실행 중 발생하는 이벤트의 포괄적인 기록을 수집합니다: LLM 생성, 도구 호출, 핸드오프, 가드레일, 그리고 사용자 지정 이벤트까지. [Traces 대시보드](https://platform.openai.com/traces)를 사용하여 개발 및 프로덕션 환경에서 워크플로를 디버그, 시각화, 모니터링할 수 있습니다. + +!!!note + + 트레이싱은 기본적으로 활성화되어 있습니다. 트레이싱을 비활성화하는 방법은 두 가지입니다: + + 1. 환경 변수 `OPENAI_AGENTS_DISABLE_TRACING=1` 을 설정하여 전역적으로 비활성화할 수 있습니다 + 2. 단일 실행에 대해 [`agents.run.RunConfig.tracing_disabled`][] 를 `True` 로 설정하여 비활성화할 수 있습니다 + +***OpenAI의 API를 사용하면서 Zero Data Retention (ZDR) 정책을 적용하는 조직의 경우, 트레이싱을 사용할 수 없습니다.*** + +## 트레이스와 스팬 + +- **트레이스(Traces)** 는 "워크플로"의 단일 엔드투엔드 작업을 나타냅니다. 스팬으로 구성됩니다. 트레이스에는 다음 속성이 있습니다: + - `workflow_name`: 논리적 워크플로 또는 앱 이름입니다. 예: "Code generation" 또는 "Customer service" + - `trace_id`: 트레이스의 고유 ID입니다. 전달하지 않으면 자동으로 생성됩니다. 형식은 `trace_<32_alphanumeric>` 이어야 합니다 + - `group_id`: 선택적 그룹 ID로, 동일한 대화에서 여러 트레이스를 연결하는 데 사용합니다. 예: 채팅 스레드 ID를 사용할 수 있습니다 + - `disabled`: True 인 경우 트레이스가 기록되지 않습니다 + - `metadata`: 트레이스에 대한 선택적 메타데이터 +- **스팬(Spans)** 은 시작 및 종료 시간이 있는 작업을 나타냅니다. 스팬에는 다음이 있습니다: + - `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)를 설정하여 다른 목적지로 트레이스를 전송할 수 있습니다(대체 또는 보조 목적지). + +## 상위 수준 트레이스 + +때로는 여러 번의 `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. `with trace()` 로 `Runner.run` 에 대한 두 호출을 감쌌기 때문에, 개별 실행이 두 개의 트레이스를 생성하는 대신 전체 트레이스의 일부가 됩니다. + +## 트레이스 생성 + +[`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] 를 구성하여 이 오디오 데이터 캡처를 비활성화할 수 있습니다. + +## 사용자 지정 트레이싱 프로세서 + +트레이싱의 상위 수준 아키텍처는 다음과 같습니다: + +- 초기화 시, 트레이스를 생성하는 역할을 하는 전역 [`TraceProvider`][agents.tracing.setup.TraceProvider] 를 생성합니다 +- `TraceProvider` 를 [`BatchTraceProcessor`][agents.tracing.processors.BatchTraceProcessor] 로 구성하여 스팬과 트레이스를 배치로 [`BackendSpanExporter`][agents.tracing.processors.BackendSpanExporter] 에 전송하고, 이는 배치로 OpenAI 백엔드에 스팬과 트레이스를 내보냅니다 + +기본 설정을 사용자 지정하여, 대체 또는 추가 백엔드로 트레이스를 전송하거나 내보내기 동작을 수정하려면 두 가지 옵션이 있습니다: + +1. [`add_trace_processor()`][agents.tracing.add_trace_processor] 는 트레이스와 스팬이 준비될 때 이를 수신하는 **추가** 트레이스 프로세서를 추가할 수 있게 합니다. 이를 통해 OpenAI 백엔드로 전송하는 것에 더해 자체 처리를 수행할 수 있습니다 +2. [`set_trace_processors()`][agents.tracing.set_trace_processors] 는 기본 프로세서를 여러분의 트레이스 프로세서로 **교체** 할 수 있게 합니다. 이 경우 OpenAI 백엔드로 트레이스가 전송되지 않으며, 이를 수행하는 `TracingProcessor` 를 포함해야 합니다 + + +## 비 OpenAI 모델과의 트레이싱 + +OpenAI의 API 키를 비 OpenAI 모델과 함께 사용하여 트레이싱을 비활성화할 필요 없이 OpenAI Traces 대시보드에서 무료 트레이싱을 활성화할 수 있습니다. + +```python +import os +from agents import set_tracing_export_api_key, Agent, Runner +from agents.extensions.models.litellm_model import LitellmModel + +tracing_api_key = os.environ["OPENAI_API_KEY"] +set_tracing_export_api_key(tracing_api_key) + +model = LitellmModel( + model="your-model-name", + api_key="your-api-key", +) + +agent = Agent( + name="Assistant", + model=model, +) +``` + +## 참고 +- OpenAI Traces 대시보드에서 무료 트레이스를 확인하세요 + +## 외부 트레이싱 프로세서 목록 + +- [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) +- [Keywords AI](https://docs.keywordsai.co/integration/development-frameworks/openai-agent) +- [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) \ No newline at end of file diff --git a/docs/ko/usage.md b/docs/ko/usage.md new file mode 100644 index 000000000..a728191b2 --- /dev/null +++ b/docs/ko/usage.md @@ -0,0 +1,58 @@ +--- +search: + exclude: true +--- +# 사용량 + +Agents SDK 는 각 실행(run)의 토큰 사용량을 자동으로 추적합니다. 실행 컨텍스트에서 이를 확인하여 비용 모니터링, 제한 적용, 분석 기록에 사용할 수 있습니다. + +## 추적 항목 + +- **requests**: 수행된 LLM API 호출 수 +- **input_tokens**: 전송된 입력 토큰 총합 +- **output_tokens**: 수신된 출력 토큰 총합 +- **total_tokens**: 입력 + 출력 +- **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) +``` + +사용량은 실행 중 발생한 모든 모델 호출(도구 호출과 핸드오프 포함)을 합산하여 집계됩니다. + +## 세션에서 사용량 접근 + +`Session`(예: `SQLiteSession`)을 사용하는 경우, 동일한 실행 내 여러 턴에 걸쳐 사용량이 계속 누적됩니다. 각 `Runner.run(...)` 호출은 해당 시점의 실행 누적 사용량을 반환합니다. + +```python +session = SQLiteSession("my_conversation") + +first = await Runner.run(agent, "Hi!", session=session) +print(first.context_wrapper.usage.total_tokens) + +second = await Runner.run(agent, "Can you elaborate?", session=session) +print(second.context_wrapper.usage.total_tokens) # includes both turns +``` + +## 훅에서 사용량 활용 + +`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") +``` \ No newline at end of file diff --git a/docs/ko/visualization.md b/docs/ko/visualization.md new file mode 100644 index 000000000..c144b9528 --- /dev/null +++ b/docs/ko/visualization.md @@ -0,0 +1,105 @@ +--- +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) +``` + +![Agent Graph](../assets/images/graph.png) + +이는 **triage 에이전트** 와 하위 에이전트 및 도구와의 연결 구조를 시각적으로 나타내는 그래프를 생성합니다. + + +## 시각화 이해 + +생성된 그래프에는 다음이 포함됩니다: + +- 진입점을 나타내는 **시작 노드** (`__start__`) +- 노란색 채우기의 **직사각형** 으로 표시된 에이전트 +- 녹색 채우기의 **타원** 으로 표시된 도구 +- 회색 채우기의 **직사각형** 으로 표시된 MCP 서버 +- 상호작용을 나타내는 방향 간선: + - 에이전트 간 핸드오프는 **실선 화살표** + - 도구 호출은 **점선 화살표** + - MCP 서버 호출은 **파선 화살표** +- 실행이 종료되는 위치를 나타내는 **종료 노드** (`__end__`) + +## 그래프 커스터마이즈 + +### 그래프 표시 +기본적으로 `draw_graph` 는 그래프를 인라인으로 표시합니다. 별도의 창에서 표시하려면 다음을 작성하세요: + +```python +draw_graph(triage_agent).view() +``` + +### 그래프 저장 +기본적으로 `draw_graph` 는 그래프를 인라인으로 표시합니다. 파일로 저장하려면 파일명을 지정하세요: + +```python +draw_graph(triage_agent, filename="agent_graph") +``` + +이 경우 작업 디렉터리에 `agent_graph.png` 가 생성됩니다. \ No newline at end of file diff --git a/docs/ko/voice/pipeline.md b/docs/ko/voice/pipeline.md new file mode 100644 index 000000000..1c97a4bfb --- /dev/null +++ b/docs/ko/voice/pipeline.md @@ -0,0 +1,79 @@ +--- +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]: 전체 오디오 전사가 있고 해당 전사에 대한 결과만 생성하면 되는 경우 사용합니다. 화자가 말을 마쳤는지 감지할 필요가 없는 상황, 예를 들어 사전 녹음된 오디오이거나 사용자가 말하기를 마치는 시점이 명확한 푸시투토크 앱에 유용합니다 +2. [`StreamedAudioInput`][agents.voice.input.StreamedAudioInput]: 사용자가 말을 마치는 시점을 감지해야 할 수 있는 경우 사용합니다. 감지되는 대로 오디오 청크를 푸시할 수 있으며, 보이스 파이프라인은 "activity detection"이라 불리는 프로세스를 통해 적절한 시점에 에이전트 워크플로를 자동으로 실행합니다 + +## 결과 + +보이스 파이프라인 실행 결과는 [`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 + elif event.type == "voice_stream_event_lifecycle": + # lifecycle + elif event.type == "voice_stream_event_error" + # error + ... +``` + +## 모범 사례 + +### 인터럽션(중단 처리) + +Agents SDK 는 현재 [`StreamedAudioInput`][agents.voice.input.StreamedAudioInput] 에 대해 내장된 인터럽션(중단 처리) 기능을 지원하지 않습니다. 대신 감지된 각 턴마다 별도의 워크플로 실행이 트리거됩니다. 애플리케이션 내부에서 인터럽션(중단 처리)을 다루려면 [`VoiceStreamEventLifecycle`][agents.voice.events.VoiceStreamEventLifecycle] 이벤트를 구독하세요. `turn_started` 는 새 턴이 전사되어 처리가 시작됨을 나타내고, `turn_ended` 는 해당 턴에 대한 모든 오디오가 전송된 후 트리거됩니다. 모델이 턴을 시작할 때 화자의 마이크를 음소거하고, 해당 턴의 관련 오디오를 모두 전송한 후 음소거를 해제하는 방식으로 이 이벤트들을 활용할 수 있습니다. \ No newline at end of file diff --git a/docs/ko/voice/quickstart.md b/docs/ko/voice/quickstart.md new file mode 100644 index 000000000..3b67f7a02 --- /dev/null +++ b/docs/ko/voice/quickstart.md @@ -0,0 +1,198 @@ +--- +search: + exclude: true +--- +# 빠른 시작 + +## 사전 준비 + +Agents SDK의 기본 [빠른 시작 안내](../quickstart.md)를 따라 가상 환경을 설정했는지 확인하세요. 그런 다음, SDK에서 선택적 음성 관련 종속성을 설치하세요: + +```bash +pip install 'openai-agents[voice]' +``` + +## 개념 + +핵심 개념은 [`VoicePipeline`][agents.voice.pipeline.VoicePipeline]이며, 3단계 프로세스입니다: + +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-4o-mini", +) + +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, handoff to the spanish agent.", + ), + model="gpt-4o-mini", + 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-4o-mini", +) + +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, handoff to the spanish agent.", + ), + model="gpt-4o-mini", + 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)의 예제를 확인해 직접 에이전트와 대화할 수 있는 데모를 살펴보세요. \ No newline at end of file diff --git a/docs/ko/voice/tracing.md b/docs/ko/voice/tracing.md new file mode 100644 index 000000000..37ab3c83b --- /dev/null +++ b/docs/ko/voice/tracing.md @@ -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.tracing_disabled]: 트레이스에 포함할 추가 메타데이터입니다 \ No newline at end of file diff --git a/docs/scripts/translate_docs.py b/docs/scripts/translate_docs.py index bb8a2be5b..d86cc1ac1 100644 --- a/docs/scripts/translate_docs.py +++ b/docs/scripts/translate_docs.py @@ -4,6 +4,12 @@ import argparse from openai import OpenAI from concurrent.futures import ThreadPoolExecutor +from typing import Any, Dict, List, Optional + +try: + import yaml # type: ignore +except Exception: + yaml = None # PyYAML may not be available in some environments # import logging # logging.basicConfig(level=logging.INFO) @@ -26,9 +32,17 @@ source_dir = "docs" languages = { "ja": "Japanese", + "ko": "Korean", # Add more languages here, e.g., "fr": "French" } +# Comma-separated list to restrict which languages to translate (e.g., "ko" or "ja,ko") +ONLY_LANGS = [ + s.strip() + for s in (os.environ.get("ONLY_LANG") or os.environ.get("LANGS") or "").split(",") + if s.strip() +] + # Initialize OpenAI client api_key = os.getenv("PROD_OPENAI_API_KEY") or os.getenv("OPENAI_API_KEY") openai_client = OpenAI(api_key=api_key) @@ -79,6 +93,39 @@ "Python first": "Python ファースト", # Add more Japanese mappings here }, + "ko": { + "agents": "에이전트", + "computer use": "컴퓨터 사용", + "OAI hosted tools": "OpenAI 호스트하는 도구", + "well formed data": "적절한 형식의 데이터", + "guardrail": "가드레일", + "orchestrating multiple agents": "멀티 에이전트 오케스트레이션", + "handoffs": "핸드오프", + "function tools": "함수 도구", + "function calling": "함수 호출", + "tracing": "트레이싱", + "code examples": "코드 예제", + "vector store": "벡터 스토어", + "deep research": "딥 리서치", + "category": "카테고리", + "user": "사용자", + "parameter": "매개변수", + "processor": "프로세서", + "server": "서버", + "web search": "웹 검색", + "file search": "파일 검색", + "streaming": "스트리밍", + "system prompt": "시스템 프롬프트", + "interruption": "인터럽션(중단 처리)", + "TypeScript-first": "TypeScript 우선", + "Human in the loop": "휴먼인더루프 (HITL)", + "Hosted tool": "호스티드 툴", + "Hosted MCP server tools": "호스티드 MCP 서버 도구", + "raw": "원문", + "Realtime Agents": "실시간 에이전트", + "Build your first agent in minutes.": "단 몇 분 만에 첫 에이전트를 만들 수 있습니다", + "Let's build": "시작하기", + }, # Add more languages here } eng_to_non_eng_instructions = { @@ -95,11 +142,81 @@ "* You must consistently use polite wording such as です/ます rather than である/なのだ.", # Add more Japanese mappings here ], + "ko": [ + "* 공손하고 중립적인 문체(합니다/입니다체)를 일관되게 사용하세요.", + "* 개발자 문서이므로 자연스러운 의역을 허용하되 정확성을 유지하세요.", + "* 'instructions', 'tools' 같은 API 매개변수와 temperature, top_p, max_tokens, presence_penalty, frequency_penalty 등은 영문 그대로 유지하세요.", + "* 문장이 아닌 불릿 항목 끝에는 마침표를 찍지 마세요.", + ], # Add more languages here } -def built_instructions(target_language: str, lang_code: str) -> str: +def _extract_sidebar_translations(lang_code: str) -> Dict[str, Dict[str, Optional[str]]]: + """Extract mapping of doc file paths to labels/translations from mkdocs.yml. + + Returns a map: { path: { "label": str, "translation": str|None } } + """ + sidebar_map: Dict[str, Dict[str, Optional[str]]] = {} + repo_root = os.path.dirname(os.path.dirname(os.path.dirname(__file__))) + mkdocs_path = os.path.join(repo_root, "mkdocs.yml") + if yaml is None: + return sidebar_map + try: + with open(mkdocs_path, "r", encoding="utf-8") as f: + data = yaml.safe_load(f) + except Exception: + return sidebar_map + + try: + languages_block = [] + for plugin in data.get("plugins", []): + if isinstance(plugin, dict) and "i18n" in plugin: + languages_block = plugin["i18n"].get("languages", []) + break + if not languages_block: + return sidebar_map + + nav_by_locale: Dict[str, Any] = {} + for lang in languages_block: + locale = lang.get("locale") + nav_by_locale[locale] = lang.get("nav") + + en_nav = nav_by_locale.get("en") + tgt_nav = nav_by_locale.get(lang_code) + + def collect(nav: Any) -> Dict[str, str]: + result: Dict[str, str] = {} + if not isinstance(nav, list): + return result + for item in nav: + if isinstance(item, dict): + for label, value in item.items(): + if isinstance(value, str): + result[value] = str(label) + else: + result.update(collect(value)) + elif isinstance(item, str): + continue + return result + + en_map = collect(en_nav) if en_nav else {} + tgt_map = collect(tgt_nav) if tgt_nav else {} + for path_key, en_label in en_map.items(): + sidebar_map[path_key] = { + "label": en_label, + "translation": tgt_map.get(path_key), + } + except Exception: + return {} + return sidebar_map + + +def built_instructions( + target_language: str, + lang_code: str, + sidebar_map: Optional[Dict[str, Dict[str, Optional[str]]]] = None, +) -> str: do_not_translate_terms = "\n".join(do_not_translate) specific_terms = "\n".join( [f"* {k} -> {v}" for k, v in eng_to_non_eng_mapping.get(lang_code, {}).items()] @@ -108,6 +225,23 @@ def built_instructions(target_language: str, lang_code: str) -> str: eng_to_non_eng_instructions.get("common", []) + eng_to_non_eng_instructions.get(lang_code, []) ) + sidebar_labels_block = "" + if sidebar_map: + label_lines: List[str] = [] + for link, entry in sidebar_map.items(): + if entry.get("translation"): + label_lines.append( + f"- {link}: {entry['translation']} (sidebar translation)" + ) + elif entry.get("label"): + label_lines.append(f"- {link}: {entry['label']} (sidebar label)") + if label_lines: + sidebar_labels_block = ( + "\n\n#########################\n## PAGE TITLES ##\n#########################\n" + "When you see links to another page, consistently use the following labels:\n" + + "\n".join(label_lines) + + "\n\nAlways use these canonical translations for page titles and references." + ) return f"""You are an expert technical translator. Your task: translate the markdown passed as a user input from English into {target_language}. @@ -135,6 +269,8 @@ def built_instructions(target_language: str, lang_code: str) -> str: - Inline code surrounded by single back‑ticks ( `like_this` ). - Fenced code blocks delimited by ``` or ~~~, including all comments inside them. - Link URLs inside `[label](URL)` – translate the label, never the URL. +- When translating Markdown tables, preserve the exact table structure, including all delimiters (|), header separators (---), and row/column counts. Only translate the cell contents. Do not add, remove, or reorder columns or rows. +{sidebar_labels_block} ######################### ## LANGUAGE‑SPECIFIC ## @@ -142,6 +278,9 @@ def built_instructions(target_language: str, lang_code: str) -> str: *(applies only when {target_language} = Japanese)* - Insert a half‑width space before and after all alphanumeric terms. - Add a half‑width space just outside markdown emphasis markers: ` **太字** ` (good) vs `** 太字 **` (bad). +*(applies only when {target_language} = Korean)* +- Do not alter spaces around code/identifiers; keep them as in the original. +- Do not add stray spaces around markdown emphasis: `**굵게**` (good) vs `** 굵게 **` (bad). ######################### ## DO NOT TRANSLATE ## @@ -196,6 +335,15 @@ def translate_file(file_path: str, target_path: str, lang_code: str) -> None: code_blocks: list[str] = [] code_block_chunks: list[str] = [] for line in lines: + # Treat single-line import statements as code blocks to avoid accidental translation + if ( + ENABLE_CODE_SNIPPET_EXCLUSION is True + and (in_code_block is False) + and line.startswith("import ") + ): + code_blocks.append(line) + current_chunk.append(f"CODE_BLOCK_{(len(code_blocks) - 1):02}") + continue if ( ENABLE_SMALL_CHUNK_TRANSLATION is True and len(current_chunk) >= 120 # required for gpt-4.5 @@ -222,7 +370,11 @@ def translate_file(file_path: str, target_path: str, lang_code: str) -> None: # Translate each chunk separately and combine results translated_content: list[str] = [] for chunk in chunks: - instructions = built_instructions(languages[lang_code], lang_code) + instructions = built_instructions( + languages[lang_code], + lang_code, + sidebar_map=_extract_sidebar_translations(lang_code), + ) if OPENAI_MODEL.startswith("gpt-5"): response = openai_client.responses.create( model=OPENAI_MODEL, @@ -261,10 +413,18 @@ def translate_file(file_path: str, target_path: str, lang_code: str) -> None: def translate_single_source_file(file_path: str) -> None: relative_path = os.path.relpath(file_path, source_dir) - if "ref/" in relative_path or not file_path.endswith(".md"): + if "ref/" in relative_path or not ( + file_path.endswith(".md") or file_path.endswith(".mdx") + ): return - for lang_code in languages: + # Determine target languages + target_langs = ( + [code for code in ONLY_LANGS if code in languages] + if ONLY_LANGS + else list(languages.keys()) + ) + for lang_code in target_langs: target_dir = os.path.join(source_dir, lang_code) target_path = os.path.join(target_dir, relative_path) diff --git a/mkdocs.yml b/mkdocs.yml index 324a33614..53c39579e 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -180,17 +180,56 @@ plugins: - realtime/quickstart.md - realtime/guide.md + - locale: ko + name: 한국어 + build: true + nav: + - 소개: index.md + - 빠른 시작: quickstart.md + - 코드 예제: examples.md + - 문서: + - agents.md + - running_agents.md + - sessions.md + - results.md + - streaming.md + - repl.md + - tools.md + - mcp.md + - handoffs.md + - tracing.md + - context.md + - guardrails.md + - multi_agent.md + - usage.md + - 모델: + - models/index.md + - models/litellm.md + - config.md + - visualization.md + - release.md + - 음성 에이전트: + - voice/quickstart.md + - voice/pipeline.md + - voice/tracing.md + - 실시간 에이전트: + - realtime/quickstart.md + - realtime/guide.md + extra: # Remove material generation message in footer generator: false language: en alternate: - name: English - link: /openai-agents-python/ + link: ./ lang: en - name: 日本語 - link: /openai-agents-python/ja/ + link: ./ja/ lang: ja + - name: 한국어 + link: ./ko/ + lang: ko markdown_extensions: - pymdownx.superfences: