메인 콘텐츠로 건너뛰기
W&B Sandboxes는 현재 비공개 프리뷰로, 초대를 받은 경우에만 사용할 수 있습니다. 등록을 요청하려면 support 또는 AISE에 문의하세요.
이 튜토리얼에서는 W&B Sandbox 환경에서 에이전트를 호출합니다. 이를 위해 적절한 환경 변수를 사용해 샌드박스를 시작하고, 필요한 의존성을 설치한 다음, tool call을 사용해 특정 위치의 날씨를 조회하고 말장난이 섞인 날씨 예보로 응답하는 단순한 OpenAI 에이전트를 생성하고 호출하는 Python 스크립트를 실행합니다.
이 튜토리얼에서는 에이전트의 언어 모델로 OpenAI를 사용하며, 이를 위해 OpenAI API 키가 필요합니다.

사전 요구 사항

W&B Python SDK 설치

W&B Python SDK를 설치하세요. pip를 사용하면 됩니다:
pip install wandb

W&B에 로그인하고 인증하세요

wandb login CLI 명령을 실행한 다음, 안내에 따라 W&B 계정에 로그인하세요:
wandb login

Secret Manager에 API 키 저장

OpenAI API 키를 OPENAI_API_KEYW&B Secret Manager에 저장하세요. 샌드박스에서 시크릿을 사용하는 방법에 대한 자세한 내용은 Secrets를 참조하세요. 이렇게 하면 코드나 샌드박스 설정에 API 키를 하드코딩하지 않고도 샌드박스에서 API 키에 안전하게 액세스할 수 있습니다.

에이전트 코드 복사하기

다음 코드를 이 튜토리얼과 동일한 디렉토리에 demo.py 파일로 복사하여 붙여넣은 다음, 위의 코드 스니펫을 실행하여 샌드박스 환경에서 OpenAI 에이전트를 호출하는 방법을 확인하세요.
demo.py
import json
import os
import urllib.request
import urllib.parse

from openai import OpenAI

api_key = os.getenv("OPENAI_API_KEY")
if not api_key:
    raise ValueError("OPENAI_API_KEY environment variable not set")

client = OpenAI(api_key=api_key)

SYSTEM_PROMPT = """You are an expert weather forecaster, who speaks in puns.

You have access to two tools:

- get_weather_for_location: use this to get the weather for a specific location
- get_user_location: use this to get the user's location

If a user asks you for the weather, make sure you know the location. If you can tell from the question that they mean wherever they are, use the get_user_location tool to find their location.
"""

TOOLS = [
    {
        "type": "function",
        "function": {
            "name": "get_weather_for_location",
            "description": "Get weather for a given location name.",
            "parameters": {
                "type": "object",
                "properties": {
                    "location": {
                        "type": "string",
                        "description": "The location to get weather for.",
                    }
                },
                "required": ["location"],
            },
        },
    },
    {
        "type": "function",
        "function": {
            "name": "get_user_location",
            "description": "Retrieve the user's current location from application context.",
            "parameters": {
                "type": "object",
                "properties": {},
                "additionalProperties": False,
            },
        },
    },
]

RESPONSE_FORMAT = {
    "type": "json_schema",
    "json_schema": {
        "name": "weather_response",
        "strict": True,
        "schema": {
            "type": "object",
            "properties": {
                "punny_response": {"type": "string"},
                "weather_conditions": {"type": ["string", "null"]},
            },
            "required": ["punny_response", "weather_conditions"],
            "additionalProperties": False,
        },
    },
}

_WMO_CODES = {
    0: "clear sky", 1: "mainly clear", 2: "partly cloudy", 3: "overcast",
    45: "foggy", 48: "depositing rime fog",
    51: "light drizzle", 53: "moderate drizzle", 55: "dense drizzle",
    61: "slight rain", 63: "moderate rain", 65: "heavy rain",
    71: "slight snowfall", 73: "moderate snowfall", 75: "heavy snowfall",
    80: "slight rain showers", 81: "moderate rain showers", 82: "violent rain showers",
    95: "thunderstorm", 96: "thunderstorm with slight hail", 99: "thunderstorm with heavy hail",
}


def get_weather_for_location(location: str) -> str:
    geo_url = "https://geocoding-api.open-meteo.com/v1/search?" + urllib.parse.urlencode(
        {"name": location, "count": 1}
    )
    with urllib.request.urlopen(geo_url) as resp:
        geo = json.loads(resp.read())

    if not geo.get("results"):
        return f"Could not find a location named '{location}'."

    loc = geo["results"][0]
    lat, lon = loc["latitude"], loc["longitude"]
    name = loc.get("name", location)

    weather_url = "https://api.open-meteo.com/v1/forecast?" + urllib.parse.urlencode({
        "latitude": lat,
        "longitude": lon,
        "current": "temperature_2m,weather_code,wind_speed_10m",
        "temperature_unit": "fahrenheit",
        "wind_speed_unit": "mph",
    })
    with urllib.request.urlopen(weather_url) as resp:
        weather = json.loads(resp.read())

    cur = weather["current"]
    condition = _WMO_CODES.get(cur["weather_code"], "unknown conditions")
    temp = cur["temperature_2m"]
    wind = cur["wind_speed_10m"]

    return f"{name}: {condition}, {temp}°F, wind {wind} mph"


def get_user_location(user_id: str) -> str:
    return "Miami" if user_id == "1" else "San Francisco"


TOOL_DISPATCH = {
    "get_weather_for_location": lambda args, _ctx: get_weather_for_location(args["location"]),
    "get_user_location": lambda _args, ctx: get_user_location(ctx["user_id"]),
}


def run_agent(messages: list[dict], context: dict) -> dict:
    while True:
        response = client.chat.completions.create(
            model="gpt-4o",
            temperature=0,
            messages=messages,
            tools=TOOLS,
            response_format=RESPONSE_FORMAT,
        )

        message = response.choices[0].message

        if message.tool_calls:
            messages.append({
                "role": "assistant",
                "content": message.content,
                "tool_calls": [
                    {
                        "id": tc.id,
                        "type": "function",
                        "function": {
                            "name": tc.function.name,
                            "arguments": tc.function.arguments,
                        },
                    }
                    for tc in message.tool_calls
                ],
            })

            for tool_call in message.tool_calls:
                fn_name = tool_call.function.name
                fn_args = json.loads(tool_call.function.arguments)

                tool_fn = TOOL_DISPATCH.get(fn_name)
                if tool_fn is None:
                    raise ValueError(f"Unknown tool: {fn_name}")

                result = tool_fn(fn_args, context)
                messages.append({
                    "role": "tool",
                    "tool_call_id": tool_call.id,
                    "content": result,
                })
            continue

        messages.append({
            "role": "assistant",
            "content": message.content,
        })
        return json.loads(message.content)


# --- 에이전트 실행 ---

conversation: list[dict] = [{"role": "system", "content": SYSTEM_PROMPT}]
context = {"user_id": "1"}

# 첫 번째 턴
conversation.append({"role": "user", "content": "what is the weather outside?"})
result = run_agent(conversation, context)
print(result)
# {'punny_response': "Florida is still having a 'sun-derful' day! ...", 'weather_conditions': "It's always sunny in Florida!"}

# 두 번째 턴 (대화 이력 유지)
conversation.append({"role": "user", "content": "thank you!"})
result = run_agent(conversation, context)
print(json.dumps(result, indent=2))