병렬 함수 호출
모델이 여러 함수를 동시에 호출하는 응답을 처리하고, asyncio로 함수를 병렬 실행한 뒤 결과를 하나의 후속 API 호출로 묶어 전달합니다.
병렬 함수 호출은(는) CoddyKit의 무료 AI Engineering Academy 강의입니다. 이것은 4개 중 3번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 AI Engineering Academy 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. AI Engineering Academy 강의에는 총 4개의 강의가 포함되어 있습니다.
병렬 함수 호출이란 무엇인가요
답변에 여러 독립적인 출처의 정보가 필요할 때 OpenAI의 모델은 한 번의 응답에서 여러 함수를 동시에 호출할 수 있습니다. 이전 도구 호출이 끝나기를 기다리며 도구 호출을 순차적으로 연결하는 대신, 모델은 여러 도구 호출을 한 번에 생성합니다. 애플리케이션은 이를 병렬로 실행하고 모든 결과를 함께 보내므로 지연 시간이 크게 줄어듭니다.
병렬 도구 호출 인식하기
모델이 병렬 도구 호출을 실행하면 응답 메시지의 tool_calls 목록에 두 개 이상의 항목이 포함됩니다. 각 항목에는 고유한 id, 함수 이름, 인수가 있습니다. 후속 API 호출을 하기 전에 모든 항목을 처리해야 합니다. 모델은 자신이 실행한 모든 도구 호출에 대한 결과를 기대하기 때문입니다.
from openai import OpenAI
import json
client = OpenAI()
# A question that naturally requires two independent lookups
response = client.chat.completions.create(
model='gpt-4o',
messages=[{'role': 'user', 'content': 'Compare the weather in London and Tokyo right now.'}],
tools=tools
)
message = response.choices[0].message
print('Number of tool calls:', len(message.tool_calls))
# Might print: Number of tool calls: 2
for tc in message.tool_calls:
print(f' {tc.function.name}({tc.function.arguments})')
# get_current_weather({"location": "London"})
# get_current_weather({"location": "Tokyo"})asyncio로 도구 호출 실행하기
asyncio.gather()를 사용하여 여러 도구 호출을 동시에 실행하세요. 각 도구 함수는 별도의 코루틴에서 실행되며, 모든 코루틴이 완료되면 모든 결과가 수집됩니다. 각 도구 호출이 네트워크 요청을 수행하는 경우 순차 실행보다 훨씬 빠릅니다.
import asyncio
import json
async def execute_tool_call_async(tool_call) -> tuple:
'''Execute a single tool call and return (tool_call_id, result).'''
name = tool_call.function.name
args = json.loads(tool_call.function.arguments)
# Async version of your tool (uses httpx, aiohttp, etc.)
if name == 'get_current_weather':
result = await async_get_weather(**args)
elif name == 'get_stock_price':
result = await async_get_stock(**args)
else:
result = f'Unknown tool: {name}'
return tool_call.id, str(result)
async def execute_all_parallel(tool_calls) -> list:
'''Execute all tool calls concurrently.'''
tasks = [execute_tool_call_async(tc) for tc in tool_calls]
results = await asyncio.gather(*tasks, return_exceptions=True)
return results모든 결과를 함께 다시 보내기
모든 병렬 도구 호출을 실행한 후 각 결과를 별도의 role='tool' 메시지로 대화에 추가하세요. 각 메시지에는 일치하는 tool_call_id가 포함되어야 합니다. 모델이 모든 결과를 하나의 일관된 답변으로 종합할 수 있도록 이 메시지를 모두 하나의 후속 API 호출로 보내세요.
async def run_parallel_tool_calls(user_message: str) -> str:
messages = [{'role': 'user', 'content': user_message}]
response = client.chat.completions.create(
model='gpt-4o', messages=messages, tools=tools
)
assistant_message = response.choices[0].message
messages.append(assistant_message) # Add assistant's tool_calls
if response.choices[0].finish_reason == 'tool_calls':
# Execute all tool calls in parallel
results = await execute_all_parallel(assistant_message.tool_calls)
# Add all results to conversation
for tool_call_id, result in results:
messages.append({
'role': 'tool',
'tool_call_id': tool_call_id,
'content': result
})
# One more API call to synthesize results
final = client.chat.completions.create(model='gpt-4o', messages=messages)
return final.choices[0].message.content
return assistant_message.content동기식 도구에 ThreadPoolExecutor 사용하기
도구 함수가 비동기 httpx가 아닌 requests를 사용하는 동기식 함수라면 concurrent.futures.ThreadPoolExecutor를 사용하여 여전히 병렬로 실행할 수 있습니다. 설정은 더 간단하지만 순수한 비동기 코드보다는 효율이 약간 떨어집니다.
from concurrent.futures import ThreadPoolExecutor, as_completed
import json
def execute_all_with_threads(tool_calls) -> list:
results = []
with ThreadPoolExecutor(max_workers=len(tool_calls)) as executor:
future_to_id = {
executor.submit(execute_tool_call, tc): tc.id
for tc in tool_calls
}
for future in as_completed(future_to_id):
tool_call_id = future_to_id[future]
try:
result = future.result(timeout=15)
except Exception as e:
result = f'Tool failed: {str(e)}'
results.append((tool_call_id, str(result)))
return results개별 도구 실패 처리하기
도구 호출을 병렬로 실행할 때 하나는 실패하고 다른 호출은 성공할 수 있습니다. 한 번의 실패로 다른 호출이 중단되게 하지 마세요. 일부 코루틴에서 예외가 발생하더라도 모든 결과를 수집하려면 asyncio.gather()에서 return_exceptions=True를 사용하세요. 모델이 모든 결과를 받고 부분적인 실패를 추론할 수 있도록 예외를 오류 문자열로 변환하세요.
async def safe_execute_all(tool_calls) -> list:
tasks = [execute_tool_call_async(tc) for tc in tool_calls]
raw_results = await asyncio.gather(*tasks, return_exceptions=True)
results = []
for tc, result in zip(tool_calls, raw_results):
if isinstance(result, Exception):
results.append((tc.id, f'Tool error: {str(result)}'))
else:
tool_call_id, output = result
results.append((tool_call_id, output))
return results모델이 병렬 호출을 사용하는 경우
모델은 필요한 정보를 독립적으로 수집할 수 있다고 판단할 때 병렬 도구 호출을 실행합니다. 즉, 한 결과가 다른 결과에 의존하지 않는 경우입니다. 예를 들면 여러 종목의 주가 조회, 여러 도시의 날씨 조회, 여러 데이터베이스 테이블 조회가 있습니다. 결과가 서로 의존하는 경우에는 순차 호출이 이루어집니다. 먼저 사용자 ID를 조회한 다음 해당 ID의 주문을 가져오는 방식입니다.
병렬 호출 속도 제한하기
많은 도구 호출을 병렬로 실행하면 동시에 요청이 발생하여 외부 API에 과부하가 걸릴 수 있습니다. 세마포어를 사용하여 동시에 실행할 수 있는 도구 호출의 최대 수를 제한하세요. 이렇게 하면 API 속도 제한을 준수하면서도 완전히 순차적으로 처리하는 것보다 효율적으로 실행할 수 있습니다.
import asyncio
async def rate_limited_execute_all(tool_calls, max_concurrent: int = 5) -> list:
semaphore = asyncio.Semaphore(max_concurrent)
async def limited_call(tc):
async with semaphore:
return await execute_tool_call_async(tc)
tasks = [limited_call(tc) for tc in tool_calls]
results = await asyncio.gather(*tasks, return_exceptions=True)
return [
(tc.id, str(r) if not isinstance(r, Exception) else f'Error: {r}')
for tc, r in zip(tool_calls, results)
]순차 호출과 병렬 호출 연결하기
실제 에이전트 작업 흐름에서는 순차 호출과 병렬 호출을 함께 사용하는 경우가 많습니다. 모델이 먼저 lookup_user(email)을 호출한 다음 반환된 user_id를 사용하여 get_orders(user_id)와 get_preferences(user_id)를 병렬로 호출할 수 있습니다. 외부 반복문에서 어떤 호출을 병렬화할 수 있는지(독립적인 호출), 어떤 호출을 순차적으로 실행해야 하는지(종속된 호출) 감지하도록 구현하세요.
async def multi_round_agent(user_message: str) -> str:
messages = [{'role': 'user', 'content': user_message}]
MAX_ROUNDS = 5
for _ in range(MAX_ROUNDS):
response = client.chat.completions.create(
model='gpt-4o', messages=messages, tools=tools
)
choice = response.choices[0]
messages.append(choice.message)
if choice.finish_reason == 'stop':
return choice.message.content # Done
if choice.finish_reason == 'tool_calls':
# Execute all tool calls in parallel (may be 1 or many)
results = await safe_execute_all(choice.message.tool_calls)
for tc_id, result in results:
messages.append({'role': 'tool', 'tool_call_id': tc_id, 'content': result})
# Loop continues for potentially sequential next call
return 'Max rounds reached.'지연 시간 개선 측정하기
병렬 도구 호출은 지연 시간을 크게 줄일 수 있습니다. 도구 호출 세 개를 각각 순차적으로 실행하는 데 500ms가 걸린다면 총 시간은 1500ms입니다. 이를 병렬로 실행하면 약 500ms로 줄어들어 3배 빨라집니다. 동시 실행에 따른 오버헤드와 속도 제한 조건을 고려하여 특정 시나리오에서 순차 실행과 병렬 실행을 항상 측정하고 비교하세요.
import time
import asyncio
async def benchmark_parallel_vs_sequential():
tool_calls = [...] # 5 independent tool calls
# Sequential
start = time.time()
for tc in tool_calls:
await execute_tool_call_async(tc)
sequential_time = time.time() - start
# Parallel
start = time.time()
await asyncio.gather(*[execute_tool_call_async(tc) for tc in tool_calls])
parallel_time = time.time() - start
print(f'Sequential: {sequential_time:.2f}s')
print(f'Parallel: {parallel_time:.2f}s')
print(f'Speedup: {sequential_time/parallel_time:.1f}x')병렬 도구 호출 디버깅하기
병렬 도구 호출 문제를 디버깅할 때는 전체 요청-응답 흐름을 기록하세요. 여기에는 모든 tool_calls가 포함된 모델의 메시지, 대화에 추가된 각 결과, 최종 모델 응답이 포함됩니다. 도구 결과가 누락되거나 순서가 뒤바뀌면 모델이 일관되지 않은 답변을 생성할 수 있습니다. tool_call_id를 상관관계 키로 사용하는 구조화된 기록을 활용하면 문제를 훨씬 쉽게 추적할 수 있습니다.
빠른 확인
OpenAI의 병렬 함수 호출에 대한 이해도를 테스트해 보세요.
레슨 요약
이번 레슨에서는 병렬 도구 호출이 tool_calls 목록에 여러 항목으로 나타난다는 점, asyncio.gather가 최대 속도를 위해 이를 동시에 실행한다는 점, 그리고 각 결과를 모델에 돌려보낼 때 일치하는 tool_call_id가 필요하다는 점을 배웠습니다. 다음으로는 함수 호출을 사용해 일반 영어 질의를 SQL로 변환하는 자연어 데이터베이스 인터페이스를 만들어 보겠습니다.
자주 묻는 질문
“병렬 함수 호출” 강의는 무료인가요?
네 — “병렬 함수 호출” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 AI Engineering Academy 강의 전체를 잠금 해제할 수 있습니다. AI Engineering Academy 강의에는 총 4개의 강의가 포함되어 있습니다.
“병렬 함수 호출”에서 뭘 배우나요?
모델이 여러 함수를 동시에 호출하는 응답을 처리하고, asyncio로 함수를 병렬 실행한 뒤 결과를 하나의 후속 API 호출로 묶어 전달합니다. 브라우저에서 직접 실행하는 실습 코드로 AI Engineering Academy을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.
AI Engineering Academy을(를) 시작하는 데 경험이 필요한가요?
사전 경험은 필요하지 않습니다. CoddyKit의 AI Engineering Academy은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 3번째 강의입니다.
“병렬 함수 호출” 강의는 얼마나 걸리나요?
대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.
이 AI Engineering Academy 강의에서 코드를 작성하고 실행할 수 있나요?
네. 모든 AI Engineering Academy 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.