나만의 모듈 조합하기
나만의 모듈 조합하기 (Composing your own module)
dspy.ReAct 로 바꾸면서 우리의 단일 단계 프로그램이 다단계 에이전트가 됐어요. 꽤 큰 변화지만, ReAct 자체는 이해하기 아주 간단합니다. 이번 강의에서는 모듈들이 어떻게 서로 조합되는지 보고, 직접 커스텀 모듈을 만들어 볼게요.
출처: 문서
본문
dspy.ReAct 안쪽에는 다른 DSPy 모듈들이 서로 조합되어 있어요. 모델이 입력을 고려하고 다음 도구를 고르는 각 단계는 dspy.Predict 모듈입니다. 모델이 finish 를 호출하거나 max_iters 에 도달할 때까지 Predict 호출을 반복하는 제어 흐름을 관리하는 코드가 조금 있는 것이죠. 그 뒤를 잇는 합성(synthesis) 단계 — 모델이 배운 모든 것으로부터 답을 조립하는 단계 — 는 ChainOfThought 모듈이에요.
모듈은 모듈식이에요. 서로 조합하기만 하면 그냥 동작합니다.
이를 보여주기 위해, 하이쿠 작성기를 위한 커스텀 모듈을 만들어 볼게요. 두 개의 모듈로 구성됩니다. 하나는 여러 후보 하이쿠를 만들고, 다른 하나는 가장 뛰어난 구절을 고르는 역할이에요.
모듈을 조합해 앙상블 만들기
LLM이 쓴 시는 주사위 굴리기와 같아요. 어떤 때는 하이쿠가 감동적이지만, 어떤 때는 예측 가능하고 밋밋합니다. 프로그램의 성공 확률을 높이기 위해 우리는 주사위를 여러 번 굴린 다음 최고의 후보를 고를 거예요.
이렇게 생겼습니다:
class HaikuEnsemble(dspy.Module):
def __init__(self, n: int = 3):
super().__init__()
self.n = n
# Module 1 generates several haikus
self.writer = dspy.ReAct(
"location, season, mood, num_haikus: int -> haikus: list[str]",
tools=[wikipedia_search, get_wikipedia_page],
max_iters=5
)
# Module 2 picks the most evocative
self.judge = dspy.ChainOfThought(
"location, season, mood, candidates: list[str] -> most_evocative_index: int"
)
def forward(self, location: str, season: str, mood: str) -> dspy.Prediction:
candidates = self.writer(
location=location, season=season, mood=mood, num_haikus=self.n,
).haikus
verdict = self.judge(
location=location, season=season, mood=mood, candidates=candidates,
)
return dspy.Prediction(
haiku=candidates[verdict.most_evocative_index],
candidates=candidates,
reasoning=verdict.reasoning,
)
모듈을 만들 때는 두 개의 함수를 작성해야 해요:
__init__은 초기 상태를 설정하고 하위 모듈(submodule)을 정의합니다.forward는 프로그램을 호출할 때 어떤 일이 일어나는지 처리하며, 입력을 받아 하위 모듈들을 거쳐 이끈 뒤 조립된 출력을 반환합니다.
우리 HaikuEnsemble 은 __init__ 에서 두 개의 하위 모듈을 정의해요.
writer는 지난ReAct프로그램과 비슷합니다. 새 입력 필드num_haikus를 추가해 모델이 몇 개의 하이쿠를 초안으로 만들지 지정하고, 출력 필드는 문자열의list를 반환하도록 바꿨어요.judge는 완전히 새것입니다. 후보haikus에 더해location,season,mood입력을 받아, 그중 가장 감동적인 것을 고릅니다.
이 프로그램을 호출하면 forward 메서드가 각 모듈을 순서대로 실행한 뒤, 결과를 담은 단일 dspy.Prediction 객체를 반환합니다.
다음처럼 호출하면:
ensemble = HaikuEnsemble(n=5)
result = ensemble(location="Bodega Bay", season="autumn", mood="inspired")
선택된 하이쿠는 이렇게 나옵니다:
Mist hugs the harbor
Crimson gulls glide over tide
Leaves whisper to fog
그리고 다음과 같은 추론 근거가 나옵니다:
The prompt asks for the candidate that most vividly evokes Bodega Bay in autumn with an inspired mood.
Bodega Bay is characterized by its foggy harbor, sea gulls, and a crisp coastal environment.
The first candidate ("Mist hugs the harbor\nCrimson gulls glide over tide\nLeaves whisper to fog") captures the mist‑laden harbor and the motion of gulls, tying the leaves' whisper to the tide—a direct reference to the coastal setting and autumnal atmosphere.
Other candidates highlight fog or forest imagery, sea‑scapes, or harvest motifs but do not simultaneously convey the harbor, gulls, and mist as strongly. Therefore, the first candidate best aligns with the location, season, and mood.
더 큰 모델을 심사위원으로 쓰기
이 모듈을 진짜 앙상블로 만들기 위해, 하이쿠 작성기의 작품을 채점할 다른 모델을 사용해 볼게요. 한 줄만 추가하면 됩니다:
class HaikuEnsemble(dspy.Module):
def __init__(self, n: int = 3):
super().__init__()
self.n = n
# Module 1 generates several haikus
self.writer = dspy.ReAct(
"location, season, mood, num_haikus: int -> haikus: list[str]",
tools=[wikipedia_search, get_wikipedia_page],
max_iters=5
)
# Module 2 picks the most evocative
self.judge = dspy.ChainOfThought(
"location, season, mood, candidates: list[str] -> most_evocative_index: int"
)
def forward(self, location: str, season: str, mood: str):
candidates = self.writer(
location=location, season=season, mood=mood, num_haikus=self.n,
).haikus
# Call a much larger model to evaluate our haikus
with dspy.context(lm=dspy.LM("openai/gpt-5.4")):
verdict = self.judge(
location=location, season=season, mood=mood, candidates=candidates,
)
return dspy.Prediction(
haiku=candidates[verdict.most_evocative_index],
candidates=candidates,
reasoning=verdict.reasoning,
)
with dspy.context() 문은 judge 호출에 새 모델을 설정하는 새 컨텍스트를 정의할 수 있게 해 줍니다.
분해해서 격리·재사용·통제·최적화하기
우리의 하이쿠 작업은 작은 예시지만, HaikuEnsemble 을 만드는 과정은 필요할 때 프로그램을 얼마나 쉽게 분해할 수 있는지 보여줍니다. 난해한 체이닝 API가 있는 게 아니라, 모듈은 그냥 파이썬과 DSPy의 기본 요소 Signature, Module, LM 일 뿐이에요.
AI 프로그램이 복잡해지고 실패 패턴을 배워가면서 분해할 이유가 생깁니다. 예를 들어 커스텀 모듈을 사용하면:
- 컨텍스트 격리: 길을 잃은 조사 단계는 많은 후보 주제를 다룰 수 있지만, 최종 하이쿠 작성 호출은 선택된 하나만 보아야 해요. 분리하면 각 모듈이 자기 일에만 집중할 수 있습니다.
- 프로그램 간 부품 재사용: 잘 튜닝된 위키백과 조사 하위 모듈은 하이쿠 전용이 아니에요. 신뢰할 수 있는 근거가 필요한 어떤 프로그램이든 보조할 수 있습니다. 분해하면 프로그램 간에 재사용할 수 있어요.
- 쉬운 작업을 더 싼 모델로 라우팅: 수많은 작은 모델 호출이 근거를 빠르고 값싸게 수집하고, 더 강한 모델에 대한 단일 호출이 최종의 섬세한 작문을 처리할 수 있어요.
- 커스텀 제어 흐름 설계: NLP 라이브러리로 최종 하이쿠를 음절 수 검사에 통과시키고, 운율이 어긋나면 writer를 다시 호출할 수도 있어요.
- 단계마다 독립적으로 검사·통제: 각 하위 모듈은 자기만의 객체고, 각 호출은
inspect_history에 따로 기록됩니다. 에이전트가 예상 밖의 행동을 하면 하위 모듈 하나를 단독으로 호출해 정확히 무엇을 반환했는지 볼 수 있어요. 하이쿠 작성에서는 덜 신경 쓸 일이지만, 고위험 작업에서는 이런 감사(audit) 능력이 중요합니다. - 더 쉽게 평가·최적화: 하이쿠 하나를 단독으로 채점하는 건 어려워요. "좋다"는 너무 많은 차원에 걸쳐 있어 깔끔하게 채점하기 어렵습니다. 셋 중 최고를 고르는 것은 훨씬 쉽죠. 분해하면 실제로 점수를 매길 수 있는 하위 작업이 분리돼, 평가와 최적화를 가능하게 합니다.
제어 흐름 패턴(분기, 재시도 루프, 병렬 호출)과 이 예시를 넘어선 조합은 모듈: 나만의 모듈 조합하기를 참고하세요.
평가와 최적화 얘기가 나왔으니, 이제 다음 섹션으로 갈 시간이에요.
다음: 메트릭 →