클래스 기반 시그니처 작성하기
클래스 기반 시그니처 작성하기 (Writing a class-based signature)
문자열 시그니처는 빠르고 간결하지만, 작업에 더 세밀한 설명을 더하고 싶어지면 클래스 기반 시그니처로 전환하는 게 좋아요. 이번 강의에서는 클래스 기반 시그니처가 문자열 시그니처와 똑같은 구조를 표현하면서도 어떤 추가 장치(lever)를 제공하는지 알아볼게요.
출처: 문서
본문
클래스 기반 시그니처로 지시문에 뉘앙스 더하기
클래스 기반 시그니처는 문자열 시그니처가 나타낼 수 있는 것과 동일한 구조를 기술하면서도, 추가적인 뉘앙스를 더할 수 있는 몇 가지 장치를 더해 줍니다. 우리 하이쿠 작성기의 문자열 시그니처를 클래스 기반 시그니처로 바꿔 보면 이래요:
class HaikuBot(dspy.Signature):
"""
Write a classical haiku given the provided inputs.
"""
location: str = dspy.InputField(desc="The setting of the poem")
mood: str = dspy.InputField()
haiku: str = dspy.OutputField()
동일한 필드들(location, mood, 그리고 haiku 출력)이 문자열로 타입 지정되어 있지만, 이제 각 필드에 설명(description) 을 추가할 수 있어요. 필드 설명은 필드 이름 안에 담기 어려운 뉘앙스를 더할 수 있게 해 줍니다.
클래스 기반 시그니처는 또한 docstring(클래스 시작 부분의 문자열)을 쓸 수 있게 해 주는데, DSPy는 이것을 프롬프트를 준비할 때 작업 지시문으로 사용합니다.
클래스 기반 시그니처는 문자열 시그니처를 넘기는 것과 똑같은 방식으로 모듈에 전달해요:
haiku_bot = dspy.Predict(HaikuBot)
result = haiku_bot(location="a quiet library", mood="mysterious")
print(result.haiku)
이 호출은 LM에 비슷한 지시문을 렌더링해 보내는데, 단 두 가지 차이가 있어요. docstring과 필드 설명이 시스템 지시문을 만들 때 사용된다는 점이죠. 다음과 같이요:
Your input fields are:
1. `location` (str): The setting of the poem
2. `mood` (str):
Your output fields are:
1. `haiku` (str):
All interactions will be structured in the following way, with the appropriate values filled in.
[[ ## location ## ]]
{location}
[[ ## mood ## ]]
{mood}
[[ ## haiku ## ]]
{haiku}
[[ ## completed ## ]]
In adhering to this structure, your objective is:
Write a classical haiku given the provided inputs.
시그니처 docstring과 필드 설명은 선택사항이지만, 필드 이름만으로는 작업에 충분한 맥락을 제공하지 못할 때 유용한 장치입니다. 다만 시그니처가 이미 말하고 있는 내용을 되풀이하거나 규칙을 잔뜩 나열하는 "처방식 튜토리얼"을 쓰고 싶은 유혹은 참아 주세요. 방대한 규칙, 주의사항, 가이드는 최적화기(optimizer)의 몫이에요(이에 대해서는 나중에 자세히 다룹니다).
한 가지 짚고 넘어갈 점: 필드 설명은 최적화기가 건드리지 않아요. 그러니 이름 짓기에 신경을 써야 합니다. 잘못 고른 필드 이름은 최적화기가 조정할 수 없어요.
더 풍부한 타입으로 시그니처 필드를 조이기
때로는 그냥 str이 너무 느슨할 때가 있어요. 값이 작고 고정된 집합에서 나와야 한다면, LM(과 호출자)이 그 범위 밖으로 벗어나지 못하도록 확실히 고정(pin down)하고 싶을 거예요. 3장의 유닛 테스트 프레임이 더 엄격해진 형태라고 생각하면 됩니다. 그냥 어떤 문자열이 아니라, 이 특정 문자열들 중 하나인 거죠.
Python 표준 라이브러리의 typing 을 사용하면 더 풍부한 타입을 추가할 수 있어요.
season 을 Literal["spring", "summer", "autumn", "winter"] 로 타입 지정하면 정확히 그렇게 동작해요. 이제 DSPy는 호출 시점과 LM의 응답을 파싱할 때 모두 이 네 값만 받아들입니다.
from typing import Literal
Season = Literal[
"spring", "summer", "autumn", "winter",
]
class HaikuBot(dspy.Signature):
"""
Write a classical haiku given the provided inputs.
"""
location: str = dspy.InputField()
mood: str = dspy.InputField()
season: Season = dspy.InputField()
haiku: str = dspy.OutputField()
haiku_bot = dspy.Predict(HaikuBot)
result = haiku_bot(location="Bodega Bay", mood="mysterious", season="autumn")
print(result.haiku)
결과:
Fog drifts over waves,
Crimson leaves swirl by the pier—
Night whispers secrets.
하지만 season="fall" 을 넘기면 불일치를 설명하는 경고가 표시돼요:
WARNING dspy.predict.predict: Type mismatch for field 'season': expected Literal['spring', 'summer', 'autumn', 'winter'] based on given Signature, but the provided value is incompatible: fall.
아웃풋 검증기(output validators), 다중 출력 조합, 더 풍부한 Pydantic 패턴 같은 나머지 표면은 시그니처 심층 탐구에서 확인할 수 있어요.
다음: 모듈 바꾸기 →