왜 지금 봐야 하나
LlamaIndex Workflows 1.0은 2026년 6월 22일에 공개됐다. 표면적으로는 기존 LlamaIndex 코어 안에 있던 워크플로우 기능을 별도 패키지로 분리한 것처럼 보이지만, 세 가지 구조적 전환이 들어 있다.
- 워크플로우 런타임이
llama-index메인 패키지 의존성에서 독립해llama-index-workflows라는 독자 패키지와 저장소를 가지게 됐다. - Typed State와 Resource Injection이 1.0 API로 안정화됐다. DB 클라이언트·설정 객체·API 키를 Step에 주입하는 방식이 공식 패턴이 됐다.
llama-deploy가 Workflows를 분산 마이크로서비스로 올리는 프로덕션 런타임으로 성숙해, Control Plane + Message Queue 구조가 1.0과 함께 검증됐다.
이 세 변화는 하나의 방향을 가리킨다. RAG·멀티에이전트 파이프라인을 전체 LlamaIndex 의존성 없이 경량으로 구성하고, 프로덕션에서는 분산 런타임으로 올리는 경로가 처음으로 공식화됐다. RAG 파이프라인을 직접 운영하거나 멀티스텝 에이전트 워크플로우를 설계하는 팀이라면, 이 분리가 "문서 업데이트"가 아닌 배포 구조 결정이다.
핵심 변화 한눈에 보기
| 축 | 1.0 이전 | 1.0 이후 | 운영 영향 |
|---|---|---|---|
| 패키지 경계 | llama-index 코어 내장 | llama-index-workflows 독립 패키지 | 의존성 최소화; import 경로 마이그레이션 필요 |
| 상태 타입 안전 | 비구조화 dict 기반 | TypedDict / Pydantic typed state | 정적 분석으로 키 오타·타입 불일치 조기 탐지 |
| 리소스 주입 | 생성자 의존 또는 글로벌 | @inject dynamic injection | DB 클라이언트·설정을 Step에 느슨하게 전달 |
| 병렬 실행 | 수동 구현 | 이벤트 팬아웃으로 자연스럽게 구성 | 동일 이벤트 타입을 수신하는 Step이 병렬로 실행 |
| 프로덕션 서빙 | 외부 방식 자체 구현 | llama-deploy: Control Plane + MQ | Redis/Kafka/RabbitMQ 위에 분산 서비스로 배포 |
Workflows 1.0 아키텍처: 이벤트, 스텝, 컨텍스트
1. 패키지 분리: 무엇이 달라지나
Workflows 1.0의 첫 번째 변화는 설치 방식이다.
# 이전: llama-index 전체를 설치해야 워크플로우 사용 가능
pip install llama-index
# 1.0 이후: 워크플로우만 독립 설치 가능
pip install llama-index-workflowsTypeScript도 마찬가지다.
npm install @llamaindex/workflow-core이 분리가 중요한 이유는 단순한 패키지 이름 변경이 아니기 때문이다. llama-index 메인 패키지는 벡터 스토어 커넥터, 임베딩 모델, 데이터 로더 등 수십 개의 의존성을 끌고 온다. 워크플로우 오케스트레이션 로직만 필요한 경우에는 이 의존성이 불필요한 설치 비용과 충돌 가능성으로 이어진다.
독립 패키지는 다음을 의미한다.
- 기존 FastAPI·Flask 애플리케이션에 워크플로우 로직만 추가할 때 전체 LlamaIndex를 끌어들이지 않아도 된다.
- CI/CD에서 컨테이너 이미지 크기를 줄일 수 있다.
- 릴리스 주기가 분리되므로 llama-index 코어 변경의 영향을 직접 받지 않는다.
기존 코드 마이그레이션
1.0 이전 코드를 사용 중이라면 import 경로가 달라진다.
# 이전
from llama_index.core.workflow import Workflow, StartEvent, StopEvent, step
# 1.0 이후
from llama_index.workflows import Workflow, StartEvent, StopEvent, step공식 패키지 이름이 바뀌었으므로, CI 파이프라인에서 requirements.txt나 pyproject.toml의 의존성도 함께 갱신해야 한다.
2. 이벤트·스텝·컨텍스트: 핵심 실행 모델
Workflows 1.0의 실행 모델은 세 개의 원시 요소로 이루어진다.
이벤트(Event)
이벤트는 워크플로우 안에서 데이터를 운반하는 메시지다. Pydantic 모델로 정의한다.
from llama_index.workflows import Event
class RetrieveEvent(Event):
nodes: list[str]
class GenerateEvent(Event):
context: str
query: strStartEvent와 StopEvent는 프레임워크가 제공하는 특수 이벤트다. StartEvent는 워크플로우 실행을 시작하고, StopEvent는 최종 결과를 반환한다. 그 사이의 모든 이벤트는 커스텀으로 정의한다.
스텝(Step)
스텝은 이벤트를 받아 처리하고 새 이벤트를 발행하는 비동기 함수다.
from llama_index.workflows import Workflow, step, StartEvent, StopEvent, Context
class RAGWorkflow(Workflow):
@step
async def retrieve(self, ctx: Context, ev: StartEvent) -> RetrieveEvent:
# 벡터 검색 수행
query = ev.query
nodes = await self.retriever.aretrieve(query)
return RetrieveEvent(nodes=[n.text for n in nodes])
@step
async def generate(self, ctx: Context, ev: RetrieveEvent) -> StopEvent:
context_str = "\n".join(ev.nodes)
response = await self.llm.acomplete(f"{context_str}\n\nQuery: {ev.query}")
return StopEvent(result=str(response))같은 이벤트 타입을 수신하는 Step이 여럿이면 병렬로 실행된다. 이 특성이 Workflows의 핵심이다. 예를 들어 RetrieveEvent를 수신하는 Step이 두 개 있으면, 두 Step이 동시에 실행되고 각자 다른 이벤트를 발행할 수 있다.
컨텍스트(Context)
Context는 워크플로우 실행 전체에 걸쳐 공유되는 상태 저장소다.
@step
async def retrieve(self, ctx: Context, ev: StartEvent) -> RetrieveEvent:
# 원본 쿼리를 컨텍스트에 저장
await ctx.set("original_query", ev.query)
...
@step
async def generate(self, ctx: Context, ev: RetrieveEvent) -> StopEvent:
# 이전 스텝에서 저장한 값 꺼내기
query = await ctx.get("original_query")
...이 구조가 중요한 이유는 Step 간에 직접 함수 호출이 없기 때문이다. Step은 서로 이벤트를 통해 소통하고, 공유가 필요한 상태는 Context를 경유한다. 이 덕분에 Step의 실행 순서를 이벤트 타입만으로 선언할 수 있고, 동시 실행과 브랜치 분기도 자연스럽게 표현된다.
3. Typed State와 Resource Injection
1.0에서 안정화된 두 기능이다.
Typed State
Typed State는 Context에 저장하는 값의 구조를 미리 선언한다.
from typing import TypedDict
class WorkflowState(TypedDict):
query: str
retrieved_nodes: list[str]
response: str
class RAGWorkflow(Workflow):
@step
async def retrieve(
self, ctx: Context[WorkflowState], ev: StartEvent
) -> RetrieveEvent:
# state는 이제 타입이 지정되어 있다
ctx.data["query"] = ev.query
...Pydantic v2 기반 Workflow도 지원된다. 중요한 효과는 두 가지다.
- 잘못된 키 이름이나 타입 불일치를 정적 분석(mypy, Pyright)으로 실행 전에 잡는다.
- 팀원이 워크플로우 상태 구조를 코드에서 바로 읽을 수 있다.
Resource Injection
Resource Injection은 DB 클라이언트·설정 객체 같은 인프라 의존성을 Step에 주입한다.
from llama_index.workflows import inject
class RAGWorkflow(Workflow):
@step
async def retrieve(
self,
ctx: Context,
ev: StartEvent,
db: VectorStoreClient = inject(), # 주입된 DB 클라이언트
config: AppConfig = inject(), # 주입된 설정 객체
) -> RetrieveEvent:
nodes = await db.search(ev.query, top_k=config.top_k)
return RetrieveEvent(nodes=[n.text for n in nodes])실행 시에는 inject_resource()로 주입할 객체를 등록한다.
from llama_index.workflows import inject_resource
workflow = RAGWorkflow()
inject_resource(workflow, db=VectorStoreClient(url=settings.DB_URL))
inject_resource(workflow, config=AppConfig(top_k=5))
result = await workflow.run(query="...")이 패턴은 테스트 시에 Mock 객체를 주입하기 쉽게 만든다. 기존 방식처럼 생성자에 의존성을 하드코딩하거나 글로벌 변수에 두지 않아도 된다.
4. llama-deploy: 분산 실행 아키텍처
단일 프로세스 실행으로 충분한 경우도 있지만, 다음 상황에서는 분산 배포가 필요하다.
- 워크플로우 실행이 수 분 이상 걸려 HTTP 타임아웃이 문제가 될 때
- 여러 워크플로우 타입을 동시에 서빙해야 할 때
- 장애 복구나 수평 확장이 필요할 때
이때 쓰는 것이 llama-deploy다.
핵심 구성 요소
llama-deploy는 세 요소로 이루어진다.
Control Plane: 워크플로우 실행 요청을 받아 서비스에 라우팅하는 REST API 서버다. Orchestrator, StateStorage, ServiceMetadata를 내장한다. 여러 워크플로우 서비스를 같은 Control Plane에 등록할 수 있다.
Message Queue: Control Plane과 워크플로우 서비스 간 비동기 통신을 담당한다. Redis, Kafka, RabbitMQ, AWS SQS를 플러그인 방식으로 교체할 수 있다. 워크플로우별로 독립된 큐를 사용하므로 한 워크플로우의 장애가 다른 워크플로우로 전파되지 않는다.
Workflow Services: 실제 Workflow 인스턴스를 실행하는 프로세스다. Control Plane에 등록하면 Message Queue를 통해 실행 요청을 받는다. 서비스를 여러 개 띄워 수평 확장하거나, 특정 서비스만 재시작할 수 있다.
배포 예시
# deploy.py
from llama_deploy import deploy_workflow, WorkflowServiceConfig, ControlPlaneConfig
await deploy_workflow(
workflow=RAGWorkflow(),
workflow_config=WorkflowServiceConfig(
host="0.0.0.0",
port=8002,
service_name="rag-workflow",
),
control_plane_config=ControlPlaneConfig(
host="0.0.0.0",
port=8000,
),
)실행 요청은 Control Plane의 REST API로 전달한다.
# 워크플로우 실행 요청
curl -X POST http://localhost:8000/workflows/run \
-H "Content-Type: application/json" \
-d '{"query": "LlamaIndex Workflows란 무엇인가?"}'5. 병렬 실행과 브랜치 분기 패턴
Workflows 1.0의 이벤트 라우팅 모델은 몇 가지 패턴을 자연스럽게 구현한다.
팬아웃(Fan-out): 여러 소스를 동시에 검색
class MultiSourceRAG(Workflow):
@step
async def start_parallel_retrieve(
self, ctx: Context, ev: StartEvent
) -> RetrieveFromDBEvent | RetrieveFromAPIEvent:
# 두 이벤트를 동시에 발행해 병렬 실행을 유도
await ctx.send_event(RetrieveFromDBEvent(query=ev.query))
await ctx.send_event(RetrieveFromAPIEvent(query=ev.query))
return None # 이벤트를 모아서 다음 스텝에서 처리
@step
async def retrieve_from_db(self, ctx: Context, ev: RetrieveFromDBEvent) -> RetrievedEvent:
nodes = await self.vector_db.search(ev.query)
return RetrievedEvent(source="db", nodes=nodes)
@step
async def retrieve_from_api(self, ctx: Context, ev: RetrieveFromAPIEvent) -> RetrievedEvent:
results = await self.api_client.search(ev.query)
return RetrievedEvent(source="api", nodes=results)
@step(num_workers=2) # 두 RetrievedEvent를 모두 받을 때까지 대기
async def merge_and_generate(
self, ctx: Context, ev: RetrievedEvent
) -> StopEvent:
all_nodes = ev.nodes # 각 이벤트마다 축적됨
...루프(Loop): 결과가 기준을 충족할 때까지 반복
class IterativeRAG(Workflow):
@step
async def check_quality(
self, ctx: Context, ev: GenerateEvent
) -> StopEvent | RetryEvent:
if ev.score >= 0.8:
return StopEvent(result=ev.response)
else:
# 기준 미달이면 다시 retrieve 단계로
return RetryEvent(query=ev.query, attempt=ev.attempt + 1)6. 운영 체크리스트
Workflows 1.0을 도입하거나 기존 코드를 마이그레이션할 때 확인할 항목이다.
패키지 마이그레이션
- [ ]
llama-index의존성을llama-index-workflows로 분리했는가 (불필요한 의존성 제거) - [ ] import 경로가
llama_index.core.workflow→llama_index.workflows로 바뀌었는가 - [ ] 기존 워크플로우 베타 API를 1.0 API로 전환했는가 (breaking change 있음)
- [ ] TypeScript 쪽은
@llamaindex/workflow-core를 별도 설치했는가
Typed State 적용
- [ ] 워크플로우 상태 구조를 TypedDict 또는 Pydantic 모델로 선언했는가
- [ ] mypy/Pyright 정적 분석을 CI에 추가했는가
- [ ] Context key를 문자열 하드코딩 대신 상태 클래스 필드로 참조하는가
Resource Injection
- [ ] DB 클라이언트, API 키, 설정 객체를
@inject로 주입하는가 - [ ] 테스트 시 Mock 객체를 주입하는 픽스처를 작성했는가
- [ ] 의존성 초기화 실패가 워크플로우 실행 전에 드러나는가
llama-deploy (분산 배포)
- [ ] Message Queue 백엔드를 선택했는가 (Redis가 가장 빠르게 시작)
- [ ] Control Plane의 StateStorage 영속성 전략을 정했는가
- [ ] 워크플로우 서비스 재시작 시 실행 중인 작업의 복구 정책을 정했는가
- [ ] 수평 확장 시 같은 워크플로우 서비스가 여러 인스턴스로 실행될 때 Context 공유가 올바른가
- [ ] 장기 실행 워크플로우(수 분 이상)에 타임아웃 정책을 설정했는가
스트리밍과 Human-in-the-Loop
- [ ] 최종 사용자에게 중간 단계 결과를 스트리밍해야 한다면
ctx.write_event_to_stream()을 활용했는가 - [ ] 인간 검토가 필요한 단계에서
HumanResponseEvent를 활용한 일시 중단 패턴을 고려했는가
결론
LlamaIndex Workflows 1.0이 중요한 이유는 기능 수가 아니라 경계 재배치 때문이다.
- 워크플로우 로직이 LlamaIndex 전체 의존성 없이 독립적으로 존재할 수 있게 됐다.
- Typed State와 Resource Injection으로 단계 간 계약이 코드에서 명시적으로 드러난다.
- llama-deploy가 단일 프로세스와 분산 서비스 사이의 공식 경로를 제공한다.
이 세 변화는 데모에서는 잘 보이지 않지만, 운영에서는 바로 드러난다. 의존성 충돌, 타입 오류, 프로덕션 확장 방식이 모두 이 결정에서 나오기 때문이다.
References
- Announcing Workflows 1.0: A Lightweight Agentic Framework — LlamaIndex Blog
- llama-index-workflows on PyPI
- LlamaIndex Workflows Developer Documentation (Python)
- LlamaIndex Workflows Developer Documentation (TypeScript)
- llama-deploy GitHub Repository
- Introducing llama-deploy: A Microservice-Based Way to Deploy LlamaIndex Workflows
- Deep Dive into LlamaIndex Workflow: Event-Driven LLM Architecture — Towards Data Science
- LlamaIndex 2026 Guide: Workflows + llama-deploy + Eval — FutureAGI