모듈 (Modules)
모듈 (Modules)
DSPy 모듈은 LM을 쓰는 프로그램을 만드는 기본 빌딩 블록이에요.
- 내장 모듈 하나하나가 프롬프팅 기법(chain of thought, ReAct 같은)을 추상화합니다. 핵심은 이 모듈들이 어떤 시그니처든 처리하도록 일반화돼 있다는 거예요.
- DSPy 모듈은 학습 가능한 파라미터(프롬프트를 구성하는 조각들, LM 가중치 등)를 갖고 있고, 입력을 처리해 출력을 돌려주도록 **호출(call)**할 수 있습니다.
- 여러 모듈을 조합해 더 큰 모듈(프로그램)로 만들 수 있어요. DSPy 모듈은 PyTorch의 NN 모듈에서 직접 영감을 받았지만, LM 프로그램에 적용된 개념입니다.
내장 모듈(dspy.Predict, dspy.ChainOfThought)은 어떻게 쓸까요?
가장 기본적인 모듈인 dspy.Predict부터 시작해볼게요. 사실 다른 모든 DSPy 모듈은 내부적으로 dspy.Predict 위에 만들어져 있어요. DSPy에서 쓰는 모든 모듈의 동작을 정의하는 선언적 명세인 DSPy 시그니처는 이미 알고 있다고 가정할게요.
모듈을 쓰려면, 먼저 시그니처를 주고 **선언(declare)**하고, 그다음 입력 인자로 **호출(call)**한 뒤 출력 필드를 추출하면 됩니다!
sentence = "it's a charming and often affecting journey." # example from the SST-2 dataset.
# 1) Declare with a signature.
classify = dspy.Predict('sentence -> sentiment: bool')
# 2) Call with input argument(s).
response = classify(sentence=sentence)
# 3) Access the output.
print(response.sentiment)
출력:
True
모듈을 선언할 때 **설정 키(config key)**를 넘길 수 있어요. 아래에서는 temperature 같은 간단한 설정 키를 넘겨볼게요. max_tokens 같은 다른 생성 키도 넘길 수 있습니다.
dspy.ChainOfThought를 써보죠. 많은 경우에 dspy.Predict 대신 dspy.ChainOfThought를 그냥 바꿔 끼우기만 해도 품질이 좋아져요.
question = "What's something great about the ColBERT retrieval model?"
# 1) Declare with a signature, and pass some config.
classify = dspy.ChainOfThought('question -> answer', temperature=0.7)
# 2) Call with input argument.
response = classify(question=question)
# 3) Access the output.
response.answer
가능한 출력:
'One great thing about the ColBERT retrieval model is its superior efficiency and effectiveness compared to other models.'
이제 출력 객체를 살펴볼게요. dspy.ChainOfThought 모듈은 보통 시그니처의 출력 필드 앞에 reasoning을 하나 끼워 넣어요. (첫) reasoning과 answer를 확인해볼까요?
print(f"Reasoning: {response.reasoning}")
print(f"Answer: {response.answer}")
가능한 출력:
Reasoning: We can consider the fact that ColBERT has shown to outperform other state-of-the-art retrieval models in terms of efficiency and effectiveness. It uses contextualized embeddings and performs document retrieval in a way that is both accurate and scalable.
Answer: One great thing about the ColBERT retrieval model is its superior efficiency and effectiveness compared to other models.
다른 DSPy 모듈에는 뭐가 있고 어떻게 쓸까요?
나머지 모듈들도 아주 비슷해요. 주로 시그니처가 구현되는 내부 동작만 달라집니다.
dspy.Predict— 기본 예측기. 시그니처를 수정하지 않아요. 학습의 핵심 형태(지시사항·데모 저장, LM에 대한 업데이트)를 처리합니다.dspy.ChainOfThought— 시그니처의 응답을 내놓기 전에 단계적으로 생각하도록 LM을 가르칩니다.dspy.ProgramOfThought— 코드를 출력하도록 LM을 가르치고, 그 코드 실행 결과가 응답을 결정해요.dspy.ReAct— 주어진 시그니처를 구현하기 위해 도구(tool)를 쓸 수 있는 에이전트입니다.dspy.MultiChainComparison—ChainOfThought의 여러 출력을 비교해 최종 예측을 만들어냅니다.dspy.RLM— 재귀 언어 모델. 샌드박스 처리된 Python REPL과 재귀적 서브-LLM 호출로 큰 컨텍스트를 탐색해요. 컨텍스트가 프롬프트에 담기엔 너무 클 때 씁니다.
함수 스타일 모듈도 있어요.
dspy.majority— 여러 예측 중 가장 인기 있는 응답을 돌려주는 기본 투표를 합니다.
!!! info "간단한 작업에 쓰는 DSPy 모듈 예시들"
아래 예시들은 lm을 설정한 뒤 시도해 보세요. 필드를 조정하며 여러분의 LM이 기본 상태에서 잘 해내는 작업이 뭔지 탐색해 보세요.
=== "Math"
```python linenums="1"
math = dspy.ChainOfThought("question -> answer: float")
math(question="Two dice are tossed. What is the probability that the sum equals two?")
```
**가능한 출력:**
```text
Prediction(
reasoning='When two dice are tossed, each die has 6 faces, resulting in a total of 6 x 6 = 36 possible outcomes. The sum of the numbers on the two dice equals two only when both dice show a 1. This is just one specific outcome: (1, 1). Therefore, there is only 1 favorable outcome. The probability of the sum being two is the number of favorable outcomes divided by the total number of possible outcomes, which is 1/36.',
answer=0.0277776
)
```
=== "Retrieval-Augmented Generation"
```python linenums="1"
def search(query: str) -> list[str]:
"""Retrieves abstracts from Wikipedia."""
results = dspy.ColBERTv2(url='http://20.102.90.50:2017/wiki17_abstracts')(query, k=3)
return [x['text'] for x in results]
rag = dspy.ChainOfThought('context, question -> response')
question = "What's the name of the castle that David Gregory inherited?"
rag(context=search(question), question=question)
```
**가능한 출력:**
```text
Prediction(
reasoning='The context provides information about David Gregory, a Scottish physician and inventor. It specifically mentions that he inherited Kinnairdy Castle in 1664. This detail directly answers the question about the name of the castle that David Gregory inherited.',
response='Kinnairdy Castle'
)
```
=== "Classification"
```python linenums="1"
from typing import Literal
class Classify(dspy.Signature):
"""Classify sentiment of a given sentence."""
sentence: str = dspy.InputField()
sentiment: Literal['positive', 'negative', 'neutral'] = dspy.OutputField()
confidence: float = dspy.OutputField()
classify = dspy.Predict(Classify)
classify(sentence="This book was super fun to read, though not the last chapter.")
```
**가능한 출력:**
```text
Prediction(
sentiment='positive',
confidence=0.75
)
```
=== "Information Extraction"
```python linenums="1"
text = "Apple Inc. announced its latest iPhone 14 today. The CEO, Tim Cook, highlighted its new features in a press release."
module = dspy.Predict("text -> title, headings: list[str], entities_and_metadata: list[dict[str, str]]")
response = module(text=text)
print(response.title)
print(response.headings)
print(response.entities_and_metadata)
```
**가능한 출력:**
```text
Apple Unveils iPhone 14
['Introduction', 'Key Features', "CEO's Statement"]
[{'entity': 'Apple Inc.', 'type': 'Organization'}, {'entity': 'iPhone 14', 'type': 'Product'}, {'entity': 'Tim Cook', 'type': 'Person'}]
```
=== "Agents"
```python linenums="1"
def evaluate_math(expression: str) -> float:
return dspy.PythonInterpreter({}).execute(expression)
def search_wikipedia(query: str) -> str:
results = dspy.ColBERTv2(url='http://20.102.90.50:2017/wiki17_abstracts')(query, k=3)
return [x['text'] for x in results]
react = dspy.ReAct("question -> answer: float", tools=[evaluate_math, search_wikipedia])
pred = react(question="What is 9362158 divided by the year of birth of David Gregory of Kinnairdy castle?")
print(pred.answer)
```
**가능한 출력:**
```text
5761.328
```
여러 모듈을 하나의 큰 프로그램으로 조합하려면?
DSPy는 그냥 Python 코드예요. 어떤 제어 흐름이든 자유롭게 모듈을 쓰면 되고, 내부적으로 compile 시점에 LM 호출을 추적하는 마법이 조금 있을 뿐입니다. 다시 말해, 모듈을 자유롭게 호출하면 됩니다.
multi-hop search 같은 튜토리얼을 보세요. 그 모듈을 예시로 아래에 재현했습니다.
class Hop(dspy.Module):
def __init__(self, num_docs=10, num_hops=4):
self.num_docs, self.num_hops = num_docs, num_hops
self.generate_query = dspy.ChainOfThought('claim, notes -> query')
self.append_notes = dspy.ChainOfThought('claim, notes, context -> new_notes: list[str], titles: list[str]')
def forward(self, claim: str) -> list[str]:
notes = []
titles = []
for _ in range(self.num_hops):
query = self.generate_query(claim=claim, notes=notes).query
context = search(query, k=self.num_docs)
prediction = self.append_notes(claim=claim, notes=notes, context=context)
notes.extend(prediction.new_notes)
titles.extend(prediction.titles)
return dspy.Prediction(notes=notes, titles=list(set(titles)))
그런 다음 커스텀 모듈 클래스 Hop의 인스턴스를 만들고, __call__ 메서드로 호출하면 됩니다.
hop = Hop()
print(hop(claim="Stephen Curry is the best 3 pointer shooter ever in the human history"))
LM 사용량은 어떻게 추적할까요?
!!! note "버전 요구사항" LM 사용량 추적은 DSPy 2.6.16 이상에서 쓸 수 있어요.
DSPy는 모든 모듈 호출에 걸쳐 언어 모델 사용량을 내장 추적합니다. 추적을 켜려면:
dspy.configure(track_usage=True)
켜고 나면 어떤 dspy.Prediction 객체에서든 사용 통계를 가져올 수 있어요.
usage = prediction_instance.get_lm_usage()
사용 데이터는 각 언어 모델 이름을 그 사용 통계로 매핑하는 딕셔너리로 돌아옵니다. 전체 예시를 볼게요.
import dspy
# Configure DSPy with tracking enabled
dspy.configure(
lm=dspy.LM("openai/gpt-4o-mini", cache=False),
track_usage=True
)
# Define a simple program that makes multiple LM calls
class MyProgram(dspy.Module):
def __init__(self):
self.predict1 = dspy.ChainOfThought("question -> answer")
self.predict2 = dspy.ChainOfThought("question, answer -> score")
def __call__(self, question: str) -> str:
answer = self.predict1(question=question)
score = self.predict2(question=question, answer=answer)
return score
# Run the program and check usage
program = MyProgram()
output = program(question="What is the capital of France?")
print(output.get_lm_usage())
이러면 다음과 같은 사용 통계가 출력됩니다.
{
'openai/gpt-4o-mini': {
'completion_tokens': 61,
'prompt_tokens': 260,
'total_tokens': 321,
'completion_tokens_details': {
'accepted_prediction_tokens': 0,
'audio_tokens': 0,
'reasoning_tokens': 0,
'rejected_prediction_tokens': 0,
'text_tokens': None
},
'prompt_tokens_details': {
'audio_tokens': 0,
'cached_tokens': 0,
'text_tokens': None,
'image_tokens': None
}
}
}
DSPy의 캐싱 기능(인메모리든 litellm 통한 디스크든)을 쓰면, 캐시된 응답은 사용 통계에 집계되지 않아요. 예시를 볼게요.
# Enable caching
dspy.configure(
lm=dspy.LM("openai/gpt-4o-mini", cache=True),
track_usage=True
)
program = MyProgram()
# First call - will show usage statistics
output = program(question="What is the capital of Zambia?")
print(output.get_lm_usage()) # Shows token usage
# Second call - same question, will use cache
output = program(question="What is the capital of Zambia?")
print(output.get_lm_usage()) # Shows empty dict: {}