Python에서 OpenAI API를 비동기로 호출하는 방법 완벽 가이드

Python asyncio와 OpenAI 비동기 클라이언트를 활용해 API를 효율적으로 호출하는 방법을 단계별로 알아보세요. 실전 코드 예제 포함!

TRY NANO BANANA FOR FREE

Python에서 OpenAI API를 비동기로 호출하는 방법 완벽 가이드

TRY NANO BANANA FOR FREE
Contents

TL;DR: Python에서 OpenAI API를 비동기로 호출하려면 `AsyncOpenAI` 클라이언트와 `asyncio` 라이브러리를 함께 사용하면 됩니다.

왜 OpenAI API를 비동기로 호출해야 할까요?

AI 애플리케이션을 개발하다 보면 동시에 여러 요청을 처리해야 하는 상황이 자주 발생합니다. 예를 들어, 여러 사용자가 동시에 챗봇을 사용하거나, 대량의 텍스트를 한꺼번에 분석해야 할 때가 있습니다. 이런 상황에서 동기 방식으로 API를 호출하면 각 요청이 완료될 때까지 프로그램이 멈추게 되어 성능이 크게 저하됩니다.

비동기 프로그래밍을 활용하면 하나의 요청이 응답을 기다리는 동안 다른 작업을 동시에 처리할 수 있습니다. 이를 통해 애플리케이션의 처리량을 극적으로 향상시킬 수 있으며, 사용자 경험도 훨씬 매끄러워집니다. OpenAI의 공식 Python 라이브러리는 버전 1.0부터 비동기 클라이언트를 공식 지원하므로, 이를 적극 활용하는 것이 현명합니다.

사전 준비: 필요한 패키지 설치

비동기 OpenAI API 호출을 시작하기 전에 필요한 패키지를 설치해야 합니다. Python 3.7 이상 버전이 필요하며, `asyncio`는 Python 표준 라이브러리에 포함되어 있으므로 별도 설치가 필요 없습니다.

터미널에서 다음 명령어를 실행하여 OpenAI 라이브러리를 설치하세요:

pip install openai

설치가 완료되면 OpenAI API 키를 환경 변수로 설정합니다. 보안을 위해 코드에 직접 API 키를 입력하지 않는 것이 중요합니다:

export OPENAI_API_KEY="your-api-key-here"

Windows 사용자라면 PowerShell에서 `$env:OPENAI_API_KEY="your-api-key-here"`를 사용하거나, `.env` 파일과 `python-dotenv` 패키지를 활용하는 것을 권장합니다.

AsyncOpenAI 클라이언트 기본 사용법

OpenAI Python 라이브러리는 `AsyncOpenAI`라는 전용 비동기 클라이언트를 제공합니다. 이 클라이언트는 동기 버전인 `OpenAI`와 거의 동일한 인터페이스를 가지지만, 모든 메서드가 코루틴(coroutine)으로 동작합니다.

아래는 단일 비동기 요청을 보내는 기본 예제입니다:

import asyncio
from openai import AsyncOpenAI

# AsyncOpenAI 클라이언트 초기화
client = AsyncOpenAI()

async def get_completion(prompt: str) -> str:
    """단일 프롬프트에 대한 완성 텍스트를 비동기로 가져옵니다."""
    response = await client.chat.completions.create(
        model="gpt-4o",
        messages=[
            {"role": "system", "content": "당신은 도움이 되는 AI 어시스턴트입니다."},
            {"role": "user", "content": prompt}
        ],
        max_tokens=500
    )
    return response.choices[0].message.content

async def main():
    result = await get_completion("Python 비동기 프로그래밍의 장점을 설명해주세요.")
    print(result)

# 이벤트 루프 실행
if __name__ == "__main__":
    asyncio.run(main())

이 코드에서 핵심은 `async def`로 함수를 정의하고, `await` 키워드를 사용하여 비동기 작업이 완료될 때까지 기다리는 것입니다. `asyncio.run()`은 이벤트 루프를 생성하고 비동기 함수를 실행하는 진입점 역할을 합니다.

여러 요청을 동시에 처리하기: asyncio.gather() 활용

비동기 프로그래밍의 진정한 강점은 여러 작업을 동시에 처리할 때 발휘됩니다. `asyncio.gather()`를 사용하면 여러 API 요청을 병렬로 실행할 수 있습니다.

asyncio.gather()로 배치 처리하기

예를 들어, 10개의 다른 주제에 대한 요약을 동시에 생성해야 한다고 가정해 보겠습니다. 동기 방식이라면 순서대로 10번 요청해야 하지만, 비동기 방식에서는 거의 동시에 모든 요청을 보낼 수 있습니다:

import asyncio
from openai import AsyncOpenAI

client = AsyncOpenAI()

async def summarize_text(text: str, index: int) -> dict:
    """주어진 텍스트를 요약합니다."""
    response = await client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[
            {"role": "system", "content": "텍스트를 2-3문장으로 간결하게 요약하세요."},
            {"role": "user", "content": text}
        ],
        max_tokens=200
    )
    return {
        "index": index,
        "summary": response.choices[0].message.content
    }

async def process_multiple_texts(texts: list) -> list:
    """여러 텍스트를 동시에 요약합니다."""
    tasks = [
        summarize_text(text, i) 
        for i, text in enumerate(texts)
    ]
    results = await asyncio.gather(*tasks)
    return results

async def main():
    sample_texts = [
        "인공지능은 현대 사회에서 점점 더 중요한 역할을 하고 있습니다...",
        "Python은 데이터 과학과 머신러닝 분야에서 가장 인기 있는 언어입니다...",
        "클라우드 컴퓨팅은 기업의 IT 인프라를 혁신적으로 변화시켰습니다..."
    ]
    
    results = await process_multiple_texts(sample_texts)
    for result in results:
        print(f"텍스트 {result['index'] + 1} 요약: {result['summary']}\n")

if __name__ == "__main__":
    asyncio.run(main())

속도 제한(Rate Limit) 처리하기

여러 요청을 동시에 보낼 때는 OpenAI API의 속도 제한(Rate Limit)을 고려해야 합니다. 너무 많은 요청을 한꺼번에 보내면 `RateLimitError`가 발생할 수 있습니다. 이를 방지하기 위해 `asyncio.Semaphore`를 사용하여 동시 요청 수를 제한하는 것이 좋습니다:

`semaphore = asyncio.Semaphore(5)`와 같이 설정하면 동시에 최대 5개의 요청만 처리되도록 제한할 수 있습니다. 또한 `tenacity` 라이브러리를 활용한 재시도 로직을 구현하면 일시적인 오류를 우아하게 처리할 수 있습니다.

스트리밍과 비동기 처리 결합하기

OpenAI API는 스트리밍 응답도 지원합니다. 비동기 클라이언트와 스트리밍을 함께 사용하면 사용자에게 실시간으로 텍스트를 표시할 수 있어 더욱 자연스러운 챗봇 경험을 제공할 수 있습니다.

비동기 스트리밍을 구현하려면 `async for` 루프를 사용합니다. `stream=True` 옵션을 활성화하고, 반환되는 청크(chunk)를 비동기적으로 처리하면 됩니다. 각 청크에는 부분적인 텍스트가 포함되어 있으며, 이를 실시간으로 화면에 출력하거나 WebSocket을 통해 클라이언트에 전송할 수 있습니다.

FastAPI와 같은 비동기 웹 프레임워크와 결합하면 더욱 강력한 실시간 AI 애플리케이션을 구축할 수 있습니다. FastAPI의 `StreamingResponse`와 AsyncOpenAI의 스트리밍을 함께 사용하면 서버에서 클라이언트로 실시간 데이터를 효율적으로 전송할 수 있습니다.

실전 팁과 모범 사례

비동기 OpenAI API 호출을 프로덕션 환경에서 사용할 때 고려해야 할 중요한 사항들이 있습니다.

클라이언트 재사용하기

`AsyncOpenAI` 클라이언트는 애플리케이션 전체에서 하나의 인스턴스를 재사용하는 것이 좋습니다. 매 요청마다 새로운 클라이언트를 생성하면 불필요한 오버헤드가 발생합니다. 싱글턴 패턴이나 의존성 주입을 통해 클라이언트를 관리하세요.

에러 처리 구현하기

비동기 코드에서도 적절한 예외 처리는 필수입니다. `try/except` 블록을 사용하여 `openai.APIError`, `openai.RateLimitError`, `openai.APITimeoutError` 등의 예외를 처리하세요. 특히 타임아웃 설정을 통해 응답이 너무 오래 걸리는 경우를 대비하는 것이 중요합니다.

Anakin.ai로 더 쉽게 시작하기

비동기 API 호출 코드를 직접 작성하고 관리하는 것이 부담스럽다면, Anakin.ai를 활용해 보세요. Anakin.ai는 복잡한 인프라 설정 없이도 OpenAI를 포함한 다양한 AI 모델을 손쉽게 활용할 수 있는 플랫폼입니다. 개발자와 비기술 사용자 모두가 AI 애플리케이션을 빠르게 구축하고 배포할 수 있도록 도와줍니다.

자주 묻는 질문 (FAQ)

Q: 동기 OpenAI 클라이언트와 비동기 클라이언트는 어떻게 다른가요?

동기 클라이언트(`OpenAI`)는 각 API 호출이 완료될 때까지 프로그램 실행을 차단합니다. 반면 비동기 클라이언트(`AsyncOpenAI`)는 `await` 키워드를 사용하여 응답을 기다리는 동안 다른 코루틴이 실행될 수 있도록 합니다. 단순한 스크립트에는 동기 방식이 적합하지만, 웹 서버나 대량 처리 작업에는 비동기 방식이 훨씬 효율적입니다.

Q: Jupyter Notebook에서 비동기 OpenAI 코드를 실행할 수 있나요?

네, 가능합니다. Jupyter Notebook은 자체 이벤트 루프를 실행하므로 `asyncio.run()` 대신 `await`를 직접 사용할 수 있습니다. 예를 들어, 노트북 셀에서 `result = await get_completion("안녕하세요")`와 같이 직접 실행하면 됩니다. 만약 충돌이 발생한다면 `nest_asyncio` 패키지를 설치하여 해결할 수 있습니다.

Q: 비동기 처리 시 OpenAI API 비용이 더 많이 발생하나요?

비동기 처리 자체가 API 비용에 영향을 주지는 않습니다. OpenAI API 비용은 사용하는 토큰 수에 따라 결정되며, 요청 방식(동기/비동기)과는 무관합니다. 다만, 비동기 처리로 인해 더 많은 요청을 빠르게 보낼 수 있으므로 전체 토큰 사용량이 늘어날 수 있습니다. 속도 제한과 예산 관리를 위해 `asyncio.Semaphore`로 동시 요청 수를 조절하는 것을 권장합니다.