비동기 DSPy 프로그래밍
비동기 DSPy 프로그래밍 (Async DSPy Programming)
DSPy는 비동기 프로그래밍을 기본 지원해서 더 효율적이고 확장 가능한 애플리케이션을 구축할 수 있게 해줘요. 이 가이드에서는 내장 모듈부터 커스텀 구현까지 DSPy의 async 기능을 활용하는 방법을 안내할게요.
출처: 문서
본문
DSPy에서 Async를 왜 사용할까? (Why Use Async in DSPy?)
DSPy의 비동기 프로그래밍은 여러 이점을 제공해요:
- 동시 작업을 통한 성능 개선
- 더 나은 자원 활용
- I/O 중심 작업의 대기 시간 감소
- 여러 요청 처리를 위한 확장성 향상
언제 Sync를 쓰고 언제 Async를 써야 할까? (When Should I use Sync or Async?)
DSPy에서 동기 프로그래밍과 비동기 프로그래밍 사이의 선택은 특정 사용 사례에 따라 달라져요. 올바른 선택을 돕는 가이드를 정리해볼게요.
다음 경우 동기 프로그래밍을 사용하세요:
- 새로운 아이디어를 탐색하거나 프로토타이핑할 때
- 연구나 실험을 수행할 때
- 소규모~중규모 애플리케이션을 구축할 때
- 더 단순하고 직관적인 코드가 필요할 때
- 디버깅과 오류 추적이 더 쉬워지길 원할 때
다음 경우 비동기 프로그래밍을 사용하세요:
- 높은 처리량의 서비스(높은 QPS)를 구축할 때
- async 동작만 지원하는 도구를 사용할 때
- 여러 동시 요청을 효율적으로 처리해야 할 때
- 높은 확장성이 필요한 프로덕션 서비스를 구축할 때
중요한 고려 사항 (Important Considerations)
async 프로그래밍은 성능 이점을 제공하지만 몇 가지 트레이드오프가 있어요:
- 더 복잡한 오류 처리와 디버깅
- 미묘하고 추적하기 어려운 버그의 가능성
- 더 복잡한 코드 구조
- ipython(Colab, Jupyter lab, Databricks notebook 등)과 일반 python 런타임 사이의 코드 차이
대부분의 개발 시나리오에서는 동기 프로그래밍으로 시작하고, async의 이점이 분명히 필요할 때만 전환하는 것을 권장해요. 이렇게 하면 async 프로그래밍의 추가 복잡성을 처리하기 전에 애플리케이션의 핵심 로직에 집중할 수 있습니다.
내장 모듈을 비동기로 사용하기 (Using Built-in Modules Asynchronously)
대부분의 DSPy 내장 모듈은 acall() 메서드를 통해 비동기 연산을 지원해요. 이 메서드는 동기 __call__ 메서드와 동일한 인터페이스를 유지하지만 비동기로 동작합니다.
dspy.Predict를 사용한 기본 예제를 살펴볼게요:
import dspy
import asyncio
import os
os.environ["OPENAI_API_KEY"] = "your_api_key"
dspy.configure(lm=dspy.LM("openai/gpt-4o-mini"))
predict = dspy.Predict("question->answer")
async def main():
# Use acall() for async execution
output = await predict.acall(question="why did a chicken cross the kitchen?")
print(output)
asyncio.run(main())
Async 도구 사용하기 (Working with Async Tools)
DSPy의 Tool 클래스는 async 함수와 매끄럽게 통합돼요. dspy.Tool에 async 함수를 제공하면 acall()로 실행할 수 있어요. 이는 I/O 중심 작업이나 외부 서비스를 다룰 때 특히 유용합니다.
import asyncio
import dspy
import os
os.environ["OPENAI_API_KEY"] = "your_api_key"
async def foo(x):
# Simulate an async operation
await asyncio.sleep(0.1)
print(f"I get: {x}")
# Create a tool from the async function
tool = dspy.Tool(foo)
async def main():
# Execute the tool asynchronously
await tool.acall(x=2)
asyncio.run(main())
동기 컨텍스트에서 async 도구 사용하기 (Using Async Tools in Synchronous Contexts)
동기 코드에서 async 도구를 호출해야 한다면 자동 async-to-sync 변환을 활성화할 수 있어요:
import dspy
async def async_tool(x: int) -> int:
"""An async tool that doubles a number."""
await asyncio.sleep(0.1)
return x * 2
tool = dspy.Tool(async_tool)
# Option 1: Use context manager for temporary conversion
with dspy.context(allow_tool_async_sync_conversion=True):
result = tool(x=5) # Works in sync context
print(result) # 10
# Option 2: Configure globally
dspy.configure(allow_tool_async_sync_conversion=True)
result = tool(x=5) # Now works everywhere
print(result) # 10
async 도구에 대한 자세한 내용은 Tools 문서를 참고하세요.
참고: dspy.ReAct를 도구와 함께 사용할 때, ReAct 인스턴스에서 acall()을 호출하면 모든 도구가 자동으로 acall() 메서드를 사용해 비동기로 실행됩니다.
커스텀 async DSPy 모듈 만들기 (Creating Custom Async DSPy Modules)
자신만의 async DSPy 모듈을 만들려면 forward() 대신 aforward() 메서드를 구현해야 해요. 이 메서드에 모듈의 async 로직이 들어갑니다. 두 async 연산을 연결하는 커스텀 모듈 예제를 살펴볼게요:
import dspy
import asyncio
import os
os.environ["OPENAI_API_KEY"] = "your_api_key"
dspy.configure(lm=dspy.LM("openai/gpt-4o-mini"))
class MyModule(dspy.Module):
def __init__(self):
self.predict1 = dspy.ChainOfThought("question->answer")
self.predict2 = dspy.ChainOfThought("answer->simplified_answer")
async def aforward(self, question, **kwargs):
# Execute predictions sequentially but asynchronously
answer = await self.predict1.acall(question=question)
return await self.predict2.acall(answer=answer)
async def main():
mod = MyModule()
result = await mod.acall(question="Why did a chicken cross the kitchen?")
print(result)
asyncio.run(main())