패턴 개요
앞선 두 장은 순차 워크플로를 위한 프롬프트 체이닝과 상황에 따라 경로를 바꾸는 라우팅을 다뤘습니다. 두 패턴 모두 중요하지만, 복잡한 에이전틱 작업은 하위 작업을 차례대로 처리하기보다 동시에 실행할 수 있는 경우가 많습니다. 바로 이 지점에서 병렬화 패턴이 핵심 역할을 합니다.
병렬화란 LLM 호출, 도구 사용, 하위 에이전트 실행 같은 여러 구성 요소를 동시에 실행하는 방식입니다. 한 단계가 끝나길 기다린 뒤 다음 단계를 시작하는 대신, 서로 독립적인 작업이 동시에 실행되도록 해서 여러 부분으로 나눌 수 있는 작업의 전체 실행 시간을 크게 줄입니다.
특정 주제를 조사해 결과를 요약하는 에이전트를 예로 들어 보겠습니다. 순차로 접근하면 이렇습니다.
1. 출처 A를 검색한다
2. 출처 A를 요약한다
3. 출처 B를 검색한다
4. 출처 B를 요약한다
5. 요약 A와 B를 종합해 최종 답변을 생성한다
병렬로 접근하면 이렇게 바뀝니다.
1. 출처 A와 B를 동시에 검색한다
2. 두 검색이 끝나면, 출처 A와 B를 동시에 요약한다
3. 두 요약을 종합해 최종 답변을 생성한다
(이 단계는 보통 순차로 진행하며, 앞선 병렬 작업이 끝나길 기다린다)
┌─ 하위 에이전트 (1) ─► 출력 (1) ─┐
│ │
입력 ────────────────┼─ 하위 에이전트 (2) ─► 출력 (2) ─┤
│ │
└─ 하위 에이전트 (3) ─► 출력 (3) ─┘
└──── 병렬 에이전트 ────┘
그림 3.1 — 하위 에이전트를 활용한 병렬화 예시
핵심은 워크플로에서 다른 부분의 출력에 서로 의존하지 않는 단계를 찾아 병렬로 실행하는 데 있습니다. 이는 지연이 있는 외부 서비스(API나 데이터베이스 등)를 다룰 때 특히 효과가 큰데, 여러 요청을 동시에 보낼 수 있기 때문입니다.
병렬화를 구현하려면 비동기 실행이나 멀티스레딩/멀티프로세싱을 지원하는 프레임워크가 필요한 경우가 많습니다. 최신 에이전틱 프레임워크는 비동기 연산을 염두에 두고 설계되어, 병렬로 실행할 단계를 쉽게 정의할 수 있습니다.
- LangChain(LCEL) —
|같은 연산자(|는 순차 실행을 의미)로 실행 가능한 객체를 결합하고, 체인이나 그래프에 동시 실행 분기를 구성해 병렬로 실행합니다. - LangGraph — 그래프 구조를 활용해 하나의 상태 전이 지점에서 여러 노드를 실행하도록 정의할 수 있어, 워크플로에서 병렬 분기를 구현하기 쉽습니다.
- 구글 ADK — 에이전트를 병렬로 실행·관리하는 강력한 네이티브 기능을 제공해, 복잡한 멀티 에이전트 시스템의 효율과 확장성을 크게 높입니다.
실제 적용 및 활용 사례
| 영역 | 병렬 작업 | 이점 |
|---|---|---|
| 정보 수집 및 조사 — 기업 조사 에이전트 | 뉴스 기사 검색, 주가 데이터 조회, 소셜 미디어 언급 확인, 기업 데이터베이스 질의를 모두 동시에 | 순차 조회보다 훨씬 빠르게 종합 정보를 확보 |
| 데이터 처리 및 분석 — 고객 피드백 분석 에이전트 | 감성 분석, 키워드 추출, 피드백 분류, 긴급 이슈 식별을 동시에 | 다각적 분석 결과를 빠르게 도출 |
| 다중 API·도구 상호작용 — 여행 계획 에이전트 | 항공권 가격 조회, 호텔 예약 가능 여부 검색, 현지 행사 확인, 레스토랑 추천 탐색을 동시에 | 완성된 여행 계획을 더 빠르게 제시 |
| 다중 구성요소 콘텐츠 생성 — 마케팅 이메일 작성 에이전트 | 제목 생성, 본문 초안 작성, 관련 이미지 탐색, 행동 유도(call-to-action) 문구 생성을 동시에 | 이메일 완성본을 훨씬 효율적으로 생성 |
| 검증 및 확인 — 사용자 입력 검증 에이전트 | 이메일 형식 점검, 전화번호 유효성 확인, 데이터베이스 기반 주소 검증, 비속어 검사를 동시에 | 입력 유효성을 더 빠르게 검증 |
| 멀티모달 처리 — 소셜 미디어 게시물 분석 에이전트 | 텍스트 감성·키워드 분석과 이미지 객체 식별·장면 묘사를 동시에 | 여러 모달리티의 인사이트를 빠르게 통합 |
| A/B 테스팅·다중 옵션 생성 — 창작 텍스트 에이전트 | 약간 다른 프롬프트나 모델로 기사 제목 세 가지를 동시에 생성 | 최적안을 빠르게 비교·선택 |
에이전틱 설계에서 병렬화는 기본적인 최적화 기법으로, 독립 작업을 동시에 실행하여 더 높은 성능과 응답성을 갖춘 애플리케이션을 구축하게 해 줍니다.
실습 코드 예제 (LangChain)
LangChain에서 병렬 실행은 LCEL을 통해 이루어집니다. 주된 방법은 여러 실행 가능한 구성 요소를 딕셔너리나 리스트 구조로 배치하는 방식입니다. 이 컬렉션이 체인의 후속 구성 요소에 입력으로 전달되면, LCEL 런타임이 포함된 실행 가능 객체를 동시에 실행합니다.
아래 워크플로는 단일 사용자 질의에 대해 요약·질문 생성·핵심 용어 추출 세 가지 독립 연산을 동시에 실행한 뒤, 결과를 하나로 종합합니다.
pip install langchain langchain-community langchain-openai
import os
import asyncio
from typing import Optional
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import Runnable, RunnableParallel, RunnablePassthrough
# --- 설정 ---
# API 키 환경 변수가 설정되어 있는지 확인한다. (예: OPENAI_API_KEY)
try:
llm: Optional[ChatOpenAI] = ChatOpenAI(model="gpt-4o-mini", temperature=0.7)
except Exception as e:
print(f"Error initializing language model: {e}")
llm = None
# --- 독립 체인 정의 ---
# 아래 세 체인은 병렬로 실행할 수 있는 서로 다른 작업을 나타낸다.
summarize_chain: Runnable = (
ChatPromptTemplate.from_messages([
("system", "Summarize the following topic concisely:"),
("user", "{topic}")
])
| llm
| StrOutputParser()
)
questions_chain: Runnable = (
ChatPromptTemplate.from_messages([
("system", "Generate three interesting questions about the following topic:"),
("user", "{topic}")
])
| llm
| StrOutputParser()
)
terms_chain: Runnable = (
ChatPromptTemplate.from_messages([
("system", "Identify 5-10 key terms from the following topic, separated by commas:"),
("user", "{topic}")
])
| llm
| StrOutputParser()
)
# --- 병렬 + 종합 체인 구성 ---
# 1. 병렬로 실행할 작업 블록을 정의한다.
# 이 작업들의 결과는 원래 주제와 함께 다음 단계의 입력으로 전달된다.
map_chain = RunnableParallel(
{
"summary": summarize_chain,
"questions": questions_chain,
"key_terms": terms_chain,
"topic": RunnablePassthrough(), # 원래 주제를 그대로 전달
}
)
# 2. 병렬 결과를 합쳐 최종 응답을 만들 프롬프트를 정의한다.
synthesis_prompt = ChatPromptTemplate.from_messages([
("system", """Based on the following information:
Summary: {summary}
Related Questions: {questions}
Key Terms: {key_terms}
Synthesize a comprehensive answer."""),
("user", "Original topic: {topic}")
])
# 3. 병렬 결과를 종합 프롬프트에 직접 연결한 뒤,
# LLM과 출력 파서를 이어 붙여 전체 체인을 구성한다.
full_parallel_chain = map_chain | synthesis_prompt | llm | StrOutputParser()
# --- 체인 실행 ---
async def run_parallel_example(topic: str) -> None:
"""
특정 주제를 사용하여 병렬 처리 체인을 비동기적으로 호출하고,
종합된 결과를 출력한다.
Args:
topic: LangChain 체인이 처리할 입력 주제.
"""
if not llm:
print("LLM not initialized. Cannot run example.")
return
print(f"\n--- Running Parallel LangChain Example for Topic: '{topic}' ---")
try:
# 'ainvoke'의 입력은 단일 'topic' 문자열이며,
# 'map_chain' 내 각 실행 가능 객체에 전달된다.
response = await full_parallel_chain.ainvoke(topic)
print("\n--- Final Response ---")
print(response)
except Exception as e:
print(f"\nAn error occurred during chain execution: {e}")
if __name__ == "__main__":
test_topic = "The history of space exploration"
# Python 3.7 이상에서 asyncio.run은 비동기 함수를 실행하는 표준 방식이다.
asyncio.run(run_parallel_example(test_topic))
짚고 넘어갈 점 — asyncio 는 병렬성이 아니라 동시성이다
책이 명시적으로 경고하는 대목입니다. asyncio가 제공하는 기능은 병렬성(parallelism)이 아니라 동시성(concurrency)입니다.
asyncio는 단일 스레드에서 이벤트 루프를 사용해, 한 작업이 유휴 상태(예: 네트워크 요청 대기)일 때 다른 작업으로 지능적으로 전환하는 방식으로 동시성을 달성합니다. 여러 작업이 동시에 진행되는 효과가 나타나지만, 코드 자체는 파이썬 전역 인터프리터 잠금(Global Interpreter Lock, GIL) 제약 아래 단일 스레드로만 실행됩니다.
그래도 이 패턴이 효과적인 이유는, 여기서 병목이 CPU 연산이 아니라 API 응답 대기이기 때문입니다. 세 번의 LLM 호출은 대부분의 시간을 네트워크 응답을 기다리며 보내므로, 이벤트 루프가 그 대기 시간에 다른 호출을 시작해 주는 것만으로 전체 지연 시간이 크게 줄어듭니다.
구성 요소별 역할
| 구성 요소 | 하는 일 |
|---|---|
summarize_chain / questions_chain / terms_chain |
각각 ChatPromptTemplate + LLM + StrOutputParser 로 이루어진 독립 체인 |
RunnableParallel |
딕셔너리에 담긴 실행 가능 객체를 동시에 실행하는 핵심 구성체 |
RunnablePassthrough() |
원래 입력 주제를 가공 없이 그대로 다음 단계로 넘긴다. 후속 종합 단계에서도 원래 주제가 필요하기 때문 |
full_parallel_chain |
map_chain(병렬 블록)을 종합 프롬프트에 연결한 뒤 LLM과 파서를 순차로 이어 붙인 엔드투엔드 체인 |
ainvoke |
비동기 호출. asyncio.run 이 이벤트 루프를 관리한다 |
실습 코드 예제 (구글 ADK)
ADK에서는 사전 정의된 ParallelAgent 와 SequentialAgent 를 조합해 동시 실행 플로를 구축합니다. 세 명의 리서치 에이전트가 서로 다른 주제를 동시에 조사하고, 취합 에이전트가 그 결과를 하나의 보고서로 종합하는 구조입니다.
from google.adk.agents import LlmAgent, ParallelAgent, SequentialAgent
from google.adk.tools import google_search
GEMINI_MODEL = "gemini-2.0-flash"
# --- 1. 리서치 하위 에이전트 정의(병렬 실행용) ---
# 리서치 에이전트 1: 재생 에너지
researcher_agent_1 = LlmAgent(
name="RenewableEnergyResearcher",
model=GEMINI_MODEL,
instruction="""You are an AI Research Assistant specializing in energy.
Research the latest advancements in 'renewable energy sources'.
Use the Google Search tool provided.
Summarize your key findings concisely (1-2 sentences).
Output *only* the summary.
""",
description="Researches renewable energy sources.",
tools=[google_search],
# 취합 에이전트를 위해 결과를 상태에 저장한다
output_key="renewable_energy_result"
)
# 리서치 에이전트 2: 전기차
researcher_agent_2 = LlmAgent(
name="EVResearcher",
model=GEMINI_MODEL,
instruction="""You are an AI Research Assistant specializing in transportation.
Research the latest developments in 'electric vehicle technology'.
Use the Google Search tool provided.
Summarize your key findings concisely (1-2 sentences).
Output *only* the summary.
""",
description="Researches electric vehicle technology.",
tools=[google_search],
output_key="ev_technology_result"
)
# 리서치 에이전트 3: 탄소 포집
researcher_agent_3 = LlmAgent(
name="CarbonCaptureResearcher",
model=GEMINI_MODEL,
instruction="""You are an AI Research Assistant specializing in climate solutions.
Research the current state of 'carbon capture methods'.
Use the Google Search tool provided.
Summarize your key findings concisely (1-2 sentences).
Output *only* the summary.
""",
description="Researches carbon capture methods.",
tools=[google_search],
output_key="carbon_capture_result"
)
# --- 2. ParallelAgent 생성(리서치 에이전트들을 동시에 실행) ---
# 이 에이전트는 리서치 에이전트들의 동시 실행을 오케스트레이션한다.
# 모든 리서치 에이전트가 완료되어 결과를 상태에 저장하면 실행을 마친다.
parallel_research_agent = ParallelAgent(
name="ParallelWebResearchAgent",
sub_agents=[researcher_agent_1, researcher_agent_2, researcher_agent_3],
description="Runs multiple research agents in parallel to gather information."
)
# --- 3. 취합 에이전트 정의(병렬 에이전트 완료 *후* 실행) ---
# 이 에이전트는 병렬 에이전트들이 세션 상태에 저장한 결과를 가져와
# 출처를 명시한 하나의 구조화된 응답으로 종합한다.
merger_agent = LlmAgent(
name="SynthesisAgent",
model=GEMINI_MODEL, # 종합 작업이 어렵거나 더 까다롭다면, 더 강력한 모델로 변경 가능
instruction="""You are an AI Assistant responsible for combining research findings into a structured report.
Your primary task is to synthesize the following research summaries, clearly attributing findings to their source areas.
Structure your response using headings for each topic. Ensure the report is coherent and integrates the key points smoothly.
**Crucially: Your entire response MUST be grounded *exclusively* on the information provided in the 'Input Summaries' below. Do NOT add any external knowledge, facts, or details not present in these specific summaries.**
**Input Summaries:**
* **Renewable Energy:**
{renewable_energy_result}
* **Electric Vehicles:**
{ev_technology_result}
* **Carbon Capture:**
{carbon_capture_result}
**Output Format:**
## Summary of Recent Sustainable Technology Advancements
### Renewable Energy Findings
(Based on RenewableEnergyResearcher's findings)
[Synthesize and elaborate *only* on the renewable energy input summary provided above.]
### Electric Vehicle Findings
(Based on EVResearcher's findings)
[Synthesize and elaborate *only* on the EV input summary provided above.]
### Carbon Capture Findings
(Based on CarbonCaptureResearcher's findings)
[Synthesize and elaborate *only* on the carbon capture input summary provided above.]
### Overall Conclusion
[Provide a brief (1-2 sentence) concluding statement that connects *only* the findings presented above.]
Output *only* the structured report following this format. Do not include introductory or concluding phrases outside this structure, and strictly adhere to using only the provided input summary content.
""",
description="Combines research findings from parallel agents into a structured, cited report, strictly grounded on provided inputs.",
# 결합에는 도구가 필요하지 않다
# 이 에이전트의 직접 응답이 시퀀스의 최종 출력이므로 output_key가 필요하지 않다
)
# --- 4. SequentialAgent 생성(전체 흐름 오케스트레이션) ---
# 이것이 실행될 메인 에이전트다. 먼저 ParallelAgent를 실행하여
# 상태를 채운 뒤, MergerAgent를 실행하여 최종 출력을 생성한다.
sequential_pipeline_agent = SequentialAgent(
name="ResearchAndSynthesisPipeline",
# 병렬 조사를 먼저 실행한 뒤 결합한다
sub_agents=[parallel_research_agent, merger_agent],
description="Coordinates parallel research and synthesizes the results."
)
root_agent = sequential_pipeline_agent
구조를 정리하면 이렇습니다.
- 세
LlmAgent는 각각google_search도구를 쓰고, 조사 결과를 1~2문장으로 요약해output_key를 통해 세션 상태에 저장합니다. ParallelAgent는 세 리서치 에이전트를 동시에 실행하고, 모든 하위 에이전트가 작업을 마치고 상태를 채우면 실행을 끝냅니다.merger_agent는 그 상태에 저장된 요약을 입력으로 받습니다. 지시문에 “출력은 제공된 입력 요약에만 엄격히 근거를 두어야 하며 외부 지식을 덧붙이면 안 된다” 는 점이 명시되어 있습니다.SequentialAgent가 전체를 오케스트레이션합니다. 먼저ParallelAgent로 조사를 수행하고, 끝나면MergerAgent로 종합합니다. 이것이root_agent이자 시스템 진입점입니다.
병렬 → 순차 조합이 이 예제의 핵심입니다. 독립적인 수집은 병렬로, 그 결과를 합치는 종합은 순차로 두는 구조입니다.
정리
병렬화란 무엇인가?
에이전틱 워크플로 상당수는 최종 목표를 달성하려면 여러 하위 작업을 완료해야 합니다. 각 작업이 이전 작업이 끝나기를 기다리는, 오로지 순차적으로만 실행하는 방식은 대체로 비효율적이고 느립니다. 특히 서로 다른 API 호출이나 여러 데이터베이스 질의 같은 외부 I/O 연산에 의존하는 작업에서는 이 지연이 심각한 병목이 됩니다. 동시 실행 수단이 없으면 전체 처리 시간은 개별 작업 소요 시간의 합이 되어, 시스템 전반의 성능과 응답성이 떨어집니다.
왜 사용하는가?
병렬화 패턴은 독립 작업을 동시에 실행할 수 있게 하여 표준화된 해법을 제시합니다. 이 패턴은 도구 사용이나 LLM 호출처럼, 다른 구성 요소의 즉각적인 출력에 의존하지 않는 워크플로 구성 요소를 식별하는 방식으로 작동합니다. LangChain이나 구글 ADK 같은 에이전틱 프레임워크는 이런 동시 연산을 정의하고 관리하는 내장 기능을 제공합니다. 메인 프로세스가 여러 하위 작업을 호출하면 이들이 병렬로 실행되고, 모두 끝날 때까지 기다린 후 다음 단계로 진행할 수 있습니다.
언제 사용해야 하는가?
여러 API에서 데이터를 가져오거나, 서로 다른 데이터 청크를 처리하거나, 이후 종합하려고 여러 콘텐츠를 생성하는 등, 동시에 실행할 수 있는 독립 연산이 워크플로에 포함될 때 이 패턴을 사용합니다.
병렬화 패턴 개요도
┌──► 에이전트 ──► 출력(1) ─┐
│ │
사용자 ──► 프롬프트 ──┼──► · · · │
▲ │ │
│ └──► 에이전트 ──► 출력(2) ─┤
└────────────────────────────────────────────┘
그림 3.2 — 병렬화 디자인 패턴
핵심 정리
- 병렬화는 독립 작업을 동시에 실행하여 효율성을 높이는 패턴입니다.
- API 호출 등 외부 리소스의 응답을 기다려야 하는 작업에서 특히 유용합니다.
- 동시 또는 병렬 아키텍처를 도입하면 설계, 디버깅, 시스템 로깅 같은 주요 개발 단계가 복잡해지고 비용도 늘어납니다.
- LangChain이나 구글 ADK 같은 프레임워크는 병렬 실행을 정의하고 관리하는 기능을 기본으로 내장하고 있습니다.
- LCEL에서
RunnableParallel은 여러 실행 가능 객체를 나란히 실행하는 핵심 구성체입니다. - 구글 ADK는 LLM 기반 위임(LLM-Driven Delegation)을 통해 병렬 실행을 지원하는데, 코디네이터 에이전트 LLM이 독립 하위 작업을 식별하면 전문 하위 에이전트가 이를 동시에 처리합니다.
- 병렬화는 전체 지연 시간을 줄여, 복잡한 작업에서 에이전틱 시스템의 응답성을 높입니다.
결론
병렬화 패턴은 독립 하위 작업을 동시에 실행하여 연산 워크플로를 빠르게 만드는 방법입니다. 덕분에 전체 지연 시간이 줄며, 여러 모델 추론이나 외부 서비스 호출이 얽힌 복잡한 연산에서 특히 효과가 큽니다.
프레임워크마다 이 패턴을 구현하는 메커니즘이 다릅니다.
- LangChain —
RunnableParallel같은 구성체로 여러 처리 체인을 명시적으로 정의하고 동시에 실행합니다. - 구글 ADK — 멀티 에이전트 위임으로 병렬화를 달성합니다. 주 코디네이터 모델이 서로 다른 하위 작업을 전문 에이전트에 맡기면 이들이 동시에 작동합니다.
병렬 처리를 순차(프롬프트 체이닝) 및 조건부(라우팅) 제어 흐름과 통합하면, 다양하고 복잡한 작업을 효율적으로 관리하는 정교한 고성능 연산 시스템을 구축할 수 있습니다.
참고 문헌
- 구글 ADK 공식 문서 (멀티 에이전트 시스템): https://google.github.io/adk-docs/agents/multi-agents/
- LangChain Expression Language(LCEL) 공식 문서 (병렬 처리): https://python.langchain.com/docs/concepts/lcel/
- 파이썬 asyncio 공식 문서: https://docs.python.org/3/library/asyncio.html