리소스 객체 — 워크플로에 의존성 주입하기
리소스 객체 — 워크플로에 의존성 주입하기
스텝마다 LLM 클라이언트나 인덱스, DB 핸들을 어떻게 넘겨줄까요? 이벤트에 실어 나르기엔 무겁고, 상태 저장소에 두기엔 직렬화가 안 되죠. 리소스(Resource) 는 이런 외부 의존성을 워크플로의 스텝에 주입하는 공식적인 방법이에요.
출처: 공식문서
리소스가 필요한 대상
리소스는 런타임이 만들어서 스텝에 넣어줘야 하는 객체를 위한 자리예요. LLM 클라이언트, 리트리버, 인덱스, 데이터베이스 핸들, 모델 설정처럼 비싸거나, 상태를 갖거나, JSON 직렬화가 안 되는 의존성을 여기에 둡니다.
간단한 예로 LlamaIndex의 Memory를 워크플로에서 쓰는 경우를 볼게요.
from typing import Annotated
from llama_index.core.workflow import Workflow, step
from llama_index.core.workflow.events import Event, StartEvent, StopEvent
from llama_index.core.workflow.resource import Resource
from llama_index.core.llms import ChatMessage
from llama_index.core.memory import Memory
def get_memory() -> Memory:
return Memory.from_defaults("user_id_123", token_limit=60000)
class WorkflowWithResource(Workflow):
@step
async def first_step(self, ev: StartEvent,
memory: Annotated[Memory, Resource(get_memory)]) -> Event:
await memory.aput(ChatMessage(role="user", content="첫 번째 스텝"))
return Event(msg="2번 스텝으로 넘길 입력")
@step
async def second_step(self, ev: Event,
memory: Annotated[Memory, Resource(get_memory)]) -> StopEvent:
await memory.aput(ChatMessage(role="user", content=ev.msg))
return StopEvent(result="메모리에 담았어요")
리소스를 주입하려면 스텝 시그니처에 파라미터를 추가하고, 타입을 Annotated로 감싼 뒤 Resource()에 팩토리를 넘기면 돼요.
memory: Annotated[Memory, Resource(get_memory)]
팩토리의 반환 타입은 어노테이션된 파라미터 타입과 일치해야 해요. 기본적으로 리소스는 워크플로 실행 동안 캐시되어, 두 스텝이 같은 Memory 객체를 받고 팩토리는 한 번만 호출돼요. 스텝마다 새 객체가 필요하다면 cache=False를 넘기면 됩니다. 팩토리는 동기일 수도 비동기일 수도 있어요.
memory: Annotated[Memory, Resource(get_memory, cache=False)]
설정 기반 리소스: ResourceConfig
JSON 파일에 저장된 설정 데이터는 Resource 대신 ResourceConfig를 써요. JSON 파일을 로드해서 Pydantic 모델로 파싱해 줍니다.
from typing import Annotated
from pydantic import BaseModel
from llama_index.core.workflow import Workflow, step
from llama_index.core.workflow.events import StartEvent, StopEvent
from llama_index.core.workflow.resource import ResourceConfig
class ClassifierConfig(BaseModel):
categories: list[str]
threshold: float
class DocumentClassifier(Workflow):
@step
async def classify(self, ev: StartEvent,
config: Annotated[ClassifierConfig,
ResourceConfig(config_file="classifier.json")]) -> StopEvent:
return StopEvent(result=f"Using threshold: {config.threshold}")
주요 파라미터는 이래요.
config_file— 설정이 담긴 JSON 파일 경로.path_selector— JSON 파일에서 중첩 값을 뽑을 "."으로 구분된 경로(예:"settings.classifier"). 파일 안에 설정이 여러 개면 특정 구간만 추출해요.label/description— 디버거·시각화 도구에서 설정을 식별하는 데 쓰는 표시 이름과 설명. 생략하면 Pydantic 모델의 타입 이름을 써요.
리소스 체이닝
리소스와 ResourceConfig는 서로 이어 붙일 수 있어요. Resource 팩토리 함수가 같은 Annotated 패턴으로 다른 리소스에 의존함을 선언할 수 있죠.
class LLMConfig(BaseModel):
model: str
temperature: float
max_tokens: int
def get_llm(config: Annotated[LLMConfig, ResourceConfig(config_file="llm.json")]) -> Anthropic:
return Anthropic(model=config.model, temperature=config.temperature,
max_tokens=config.max_tokens)
워크플로가 실행되면 의존성 체인이 자동으로 풀려요. llm.json을 로드해 LLMConfig로 파싱하고, 그 설정으로 get_llm을 호출해 LLM 클라이언트를 만든 뒤, 그 클라이언트를 스텝에 넘겨줍니다. Resource와 ResourceConfig의 어떤 조합이든 이 패턴이 동작해요.
더 알아보기
- 상태 관리 — 상태는 데이터 전용, 의존성은 리소스로
- 커스텀 시작/종료 이벤트 — 무거운 객체는 이벤트가 아닌 리소스로 주입
- 에러 처리(재시도) — 실패한 스텝 재시도하기