첫 DSPy 프로그램 작성하기

첫 DSPy 프로그램 작성하기

프레임워크가 실제로 어떻게 동작하는지는 작은 프로그램으로 직접 부딪혀 보는 게 가장 빨라요. 여기서는 하이쿠(시)를 생성하는 프로그램을 네 줄로 작성하고, 그 뒤 DSPy가 무대 뒤에서 뭘 하는지 하나씩 설명할게요.

출처: 공식문서

가장 단순한 버전부터

가장 간단한 버전을 작성해서 실행해 봅시다.

haiku_signature = "subject -> haiku"
haiku_generator = dspy.Predict(haiku_signature)
result = haiku_generator(subject="computer science")
print(result.haiku)

첫 줄은 우리의 시그니처(Signature) 를 지정해요. 시그니처는 DSPy의 핵심 개념으로, 프로그래밍에서 함수 시그니처가 입력과 출력을 기술하듯이 작업의 입력과 출력을 정의하는 방법입니다.

DSPy 시그니처를 정의하는 가장 간단한 방법은 "inputs -> outputs" 형식의 문자열이에요. 여기서는 subject를 받아 haiku를 돌려주도록 한 거죠.

언어 모델로 프로그래밍할 때는 변수 이름이 중요해요. 이름은 프로그램의 인터페이스를 정의할 뿐 아니라 모델에 의도를 알려주는 힌트가 됩니다. haikulimerick으로 바꾸면 모델은 그 신호를 알아차리고 대신 리머릭을 만들어요. 그리고 프로그램의 출력도 result.haiku가 아니라 result.limerick으로 접근하게 됩니다.

시그니처를 호출 가능한 함수로: dspy.Predict

시그니처를 호출 가능한 함수로 만드는 데는 dspy.Predict를 써요. Predict는 일종의 DSPy Module입니다. 시그니처가 무엇을 원하는지를 정한다면, 모듈은 어떻게 달성할지, 즉 호출 시점의 전략, 제어 흐름, 도구 등을 관리해요.

Predict는 기반이 되는 모듈입니다. dspy.Predict(haiku_signature)를 호출하면 무슨 일이 일어나는지 볼까요.

  1. "subject -> haiku" 문자열이 입력·출력 필드가 모두 str 타입인 Signature 클래스로 파싱돼요.
  2. Signature 인스턴스에 기본 지시 문자열이 생성돼요. 이 경우엔 "Given the fields subject, produce the fields haiku."입니다.
  3. SignaturePredict 모듈이 인스턴스화됩니다.

이제 haiku_generator는 호출 가능해졌어요. haiku_generator(subject="computer science")를 호출하면 다음 과정이 시작됩니다.

  1. DSPy 설정을 확인해서 LM이 설정돼 있는지 검사해요.
  2. AdapterSignature와 입력을 LM이 소비할 수 있는 메시지로 렌더링해요. 기본값은 ChatAdapter인데, 메시지 형식에 따라 JSON, XML 등 다른 변형도 있어요.
  3. ChatAdapter가 프롬프트를 구성해요. 여기엔 시그니처 지시, 입력·출력을 설명하는 필드 스키마, 형식 지시, 그리고 실제 입력("computer science")이 포함됩니다.
  4. 메시지가 LM으로 전송돼요. 캐싱이 기본으로 켜져 있어서 동일한 호출은 캐시된 응답을 돌려줘요.
  5. 응답이 돌아오고 ChatAdapter가 파싱해 출력 필드를 추출해요.
  6. Predict 모듈이 출력 필드를 담은 Prediction 객체를 반환해요. result.haiku를 호출하면 생성된 하이쿠가 나옵니다.
  7. 이 호출은 LM 히스토리에 기록돼 나중에 dspy.inspect_history()로 확인할 수 있어요.

print(result.haiku)를 실행하면 이런 결과가 나옵니다.

Silent code unfolds
Logic threads through hidden paths
Bugs bloom, then resolve

네 줄로, 소프트웨어처럼 읽히고 함수처럼 동작하는 AI 프로그램을 만든 거예요.

프롬프트가 어떻게 만들어졌는지 들여다보기

DSPy가 프롬프팅과 템플릿을 전부 관리해요. dspy.inspect_history(n=1)를 호출하면 우리 프로그램이 만든 실제 프롬프트와 lm이 돌려준 문자열을 볼 수 있습니다.

먼저 DSPy가 시스템 지시를 생성했어요.

Your input fields are:
1. `subject` (str):
Your output fields are:
1. `haiku` (str):
All interactions will be structured in the following way, with the appropriate values filled in.

[[ ## subject ## ]]
{subject}

[[ ## haiku ## ]]
{haiku}

[[ ## completed ## ]]
In adhering to this structure, your objective is: 
    Given the fields `subject`, produce the fields `haiku`.

대괄호와 해시 표기([[ ## ... ## ]])는 기본 ChatAdapter가 프롬프트를 구조화하는 방식이에요. 입력과 섹션을 구분해 주지요.

다음으로 adapter가 우리 입력을 사용자 메시지로 템플릿화했어요.

[[ ## subject ## ]]
computer science

Respond with the corresponding output fields, starting with the field `[[ ## haiku ## ]]`, and then ending with the marker for `[[ ## completed ## ]]`.

DSPy는 이 두 개를 lm으로 보냈고, 모델은 이렇게 응답했어요.

[[ ## haiku ## ]]
Silent code unfolds
Logic threads through hidden paths
Bugs bloom, then resolve

[[ ## completed ## ]]

DSPy에서는 시그니처 하나를 조합하면 메시지는 알아서 작성됩니다.

더 알아보기 (Learn more)