
소개
언어 모델(LM)은 자연어 이해 및 생성에 있어 방대한 기능을 제공하면서 기계 학습과 상호 작용하는 방식을 변화시켰습니다. 그러나 이러한 모델이 도메인별 제약 조건을 준수하도록 하는 것은 여전히 어려운 과제입니다. 미세 조정이나 ‘프롬프트 엔지니어링’과 같은 기술의 성장에도 불구하고 이러한 접근 방식은 매우 지루하며, LM이 특정 제약 조건을 준수하도록 안내하기 위해 무거운 수작업에 의존합니다. DSPy의 모듈식 프로그래밍 프롬프트 파이프라인에도 이러한 제약 조건을 효과적이고 자동으로 적용할 수 있는 메커니즘이 부족합니다.
이 문제를 해결하기 위해 LM에 대한 계산 제약 조건의 적용을 자동화하도록 설계된 DSPy 프레임워크의 기능인 DSPy 어설션을 소개합니다. 개발자는 최소한의 수동 개입으로 LM을 원하는 결과로 유도하여 LM 출력의 신뢰성, 예측 가능성 및 정확성을 향상시킬 수 있습니다.
dspy.Assert and dspy.Suggest API
DSPy Assertions에서 두 가지 주요 구조를 소개합니다:
1. dspy.Assert:
- Parameters:
- 제약 조건(bool): 파이썬으로 정의된 부울 유효성 검사 결과.
- msg (Optional[str]): 피드백 또는 수정 지침을 제공하는 사용자 정의 오류 메시지.
- backtrack (Optional[module]): 제약 조건 실패 시 재시도 시도를 위한 대상 모듈을 지정합니다. 기본 백트래킹 모듈은 어설션 이전의 마지막 모듈입니다.
- 동작: 실패 시 재시도를 시작하여 파이프라인의 실행을 동적으로 조정합니다. 실패가 지속되면 실행을 중단하고 dspy.AssertionError를 발생시킵니다.
- Behavior: 실패 시 재시도를 시작하여 파이프라인의 실행을 동적으로 조정합니다. 실패가 지속되면 실행을 중단하고 dspy.AssertionError를 발생시킵니다.
2. dspy.Suggest
:
- Parameters: dspy.Assert와 유사합니다.
- Behavior: 하드 스톱을 강제하지 않고 재시도를 통해 자체 개선을 장려합니다. 최대 역추적 시도 후 실패를 기록하고 실행을 계속합니다.
dspy.Assert vs. Python Assertions: 실패 시 프로그램을 종료하는 기존의 파이썬 어서트 문과 달리, dspy.Assert는 정교한 재시도 메커니즘을 수행하여 파이프라인이 조정될 수 있도록 합니다.
특히 제약 조건이 충족되지 않는 경우입니다:
- 백트래킹 메커니즘: 내부 백트래킹이 시작되어 모델이 자체적으로 수정하고 진행할 수 있는 기회를 제공하며, 이는 다음을 통해 수행됩니다.
- Dynamic Signature Modification: 다음 필드를 추가하여 DSPy 프로그램의 서명을 내부적으로 수정합니다:
- 과거 출력: 유효성 검사를 통과하지 못한 모델의 과거 출력입니다.
- 지침: 무엇이 잘못되었고 무엇을 수정해야 하는지에 대한 사용자 정의 피드백 메시지
오류가 max_backtracking_attempts를 넘어 계속되면 dspy.Assert는 파이프라인 실행을 중단하고 dspy.AssertionError로 사용자에게 경고합니다. 이렇게 하면 프로그램이 “bad” LM 동작으로 계속 실행되지 않고 사용자가 평가할 수 있도록 샘플 실패 출력을 즉시 강조 표시합니다.
- dspy.Suggest vs. dspy.Assert: dspy.Suggest는 더 부드러운 접근 방식을 제공합니다. dspy.Assert와 동일한 재시도 백트래킹을 유지하지만 대신 부드럽게 넛지 역할을 합니다. 최대 백트래킹 시도 이후에도 모델 출력이 모델 제약 조건을 통과할 수 없는 경우, dspy.Suggest는 지속적인 실패를 기록하고 나머지 데이터에 대해 프로그램 실행을 계속합니다. 이렇게 하면 LM 파이프라인이 실행을 중단하지 않고 “최선의 노력” 방식으로 작동합니다.
- dspy.Suggest는 평가 단계에서 파이프라인을 중단하지 않고 지침과 잠재적인 수정 사항을 제공하는 “도우미”로 활용하는 것이 가장 좋습니다.
- dspy.Assert는 개발 단계에서 LM이 예상대로 작동하는지 확인하기 위한 ‘검사기’로 권장되며, 개발 주기 초기에 오류를 식별하고 해결하기 위한 강력한 메커니즘을 제공합니다.
사용 사례: DSPy 프로그램에 Assertions 포함하기
먼저 인트로 워크스루에 정의된 multi-hop QA SimplifiedBaleen 파이프라인의 예제를 사용합니다.
class SimplifiedBaleen(dspy.Module):
def __init__(self, passages_per_hop=2, max_hops=2):
super().__init__()
self.generate_query = [dspy.ChainOfThought(GenerateSearchQuery) for _ in range(max_hops)]
self.retrieve = dspy.Retrieve(k=passages_per_hop)
self.generate_answer = dspy.ChainOfThought(GenerateAnswer)
self.max_hops = max_hops
def forward(self, question):
context = []
prev_queries = [question]
for hop in range(self.max_hops):
query = self.generate_query[hop](context=context, question=question).query
prev_queries.append(query)
passages = self.retrieve(query).passages
context = deduplicate(context + passages)
pred = self.generate_answer(context=context, question=question)
pred = dspy.Prediction(context=context, answer=pred.answer)
return pred
baleen = SimplifiedBaleen()
baleen(question = "Gary Zukav의 첫 번째 책은 어떤 상을 받았나요?")
DSPy Assertions을 포함하려면 유효성 검사 함수를 정의하고 각 모델 생성 후에 어설션을 선언하기만 하면 됩니다.
이 사용 사례의 경우 다음과 같은 제약 조건을 적용한다고 가정해 보겠습니다:
- Length – 각 쿼리는 100자 미만이어야 합니다.
- Uniqueness – 생성된 각 쿼리는 이전에 생성된 쿼리와 달라야 합니다.
이러한 유효성 검사를 부울 함수로 정의할 수 있습니다:
#simplistic boolean check for query length
len(query) <= 100
#Python function for validating distinct queries
def validate_query_distinction_local(previous_queries, query):
"""check if query is distinct from previous queries"""
if previous_queries == []:
return True
if dspy.evaluate.answer_exact_match_str(query, previous_queries, frac=0.8):
return False
return True
이러한 유효성 검사는 (최선의 노력 데모에서 프로그램을 테스트하고 싶기 때문에) dspy.Suggest 문을 통해 선언할 수 있습니다. 쿼리 생성 후에도 이를 유지하려고 합니다. query = self.generate_query[hop](context=context, question=question).query.
dspy.Suggest(
len(query) <= 100,
"Query should be short and less than 100 characters",
)
dspy.Suggest(
validate_query_distinction_local(prev_queries, query),
"Query should be distinct from: "
+ "; ".join(f"{i+1}) {q}" for i, q in enumerate(prev_queries)),
)
assertions의 효과를 비교 평가하는 경우 원래 프로그램과 별도로 어설션이 있는 프로그램을 정의하는 것이 좋습니다. 그렇지 않다면 assertions을 자유롭게 설정하세요!
assertions이 포함된 SimplifiedBaleen 프로그램이 어떻게 보이는지 살펴봅시다:
class SimplifiedBaleenAssertions(dspy.Module):
def __init__(self, passages_per_hop=2, max_hops=2):
super().__init__()
self.generate_query = [dspy.ChainOfThought(GenerateSearchQuery) for _ in range(max_hops)]
self.retrieve = dspy.Retrieve(k=passages_per_hop)
self.generate_answer = dspy.ChainOfThought(GenerateAnswer)
self.max_hops = max_hops
def forward(self, question):
context = []
prev_queries = [question]
for hop in range(self.max_hops):
query = self.generate_query[hop](context=context, question=question).query
dspy.Suggest(
len(query) <= 100,
"Query should be short and less than 100 characters",
)
dspy.Suggest(
validate_query_distinction_local(prev_queries, query),
"Query should be distinct from: "
+ "; ".join(f"{i+1}) {q}" for i, q in enumerate(prev_queries)),
)
prev_queries.append(query)
passages = self.retrieve(query).passages
context = deduplicate(context + passages)
if all_queries_distinct(prev_queries):
self.passed_suggestions += 1
pred = self.generate_answer(context=context, question=question)
pred = dspy.Prediction(context=context, answer=pred.answer)
return pred
이제 DSPy 어설션으로 프로그램을 호출하려면 마지막 단계가 필요하며, 이는 프로그램을 내부 어설션 역추적 및 재시도 로직으로 래핑하도록 변환하는 것입니다.
from dspy.primitives.assertions import assert_transform_module, backtrack_handler
baleen_with_assertions = assert_transform_module(SimplifiedBaleenAssertions(), backtrack_handler)
# backtrack_handler is parameterized over a few settings for the backtracking mechanism
# To change the number of max retry attempts, you can do
baleen_with_assertions_retry_once = assert_transform_module(SimplifiedBaleenAssertions(),
functools.partial(backtrack_handler, max_backtracks=1))
또는 기본 백트래킹 메커니즘(max_backtracks=2)을 사용하여 dspy.Assert/Suggest 문을 사용하여 프로그램에서 직접 activate_assertions를 호출할 수도 있습니다:
baleen_with_assertions = SimplifiedBaleenAssertions().activate_assertions()
이제 LM 쿼리 생성 이력을 검사하여 내부 LM 백트래킹을 살펴보겠습니다. 여기에서 쿼리가 100자 미만이라는 유효성 검사를 통과하지 못하면 내부 GenerateSearchQuery 서명이 역추적+재조회 프로세스 중에 과거 쿼리와 해당 사용자 정의 명령어를 포함하도록 동적으로 수정되는 것을 볼 수 있습니다: “쿼리는 짧고 100자 미만이어야 함”.
Write a simple search query that will help answer a complex question.
---
Follow the following format.
Context: may contain relevant facts
Question: ${question}
Reasoning: Let's think step by step in order to ${produce the query}. We ...
Query: ${query}
---
Context:
[1] «Kerry Condon | Kerry Condon (born 4 January 1983) is [...]»
[2] «Corona Riccardo | Corona Riccardo (c. 1878October 15, 1917) was [...]»
Question: Who acted in the shot film The Shore and is also the youngest actress ever to play Ophelia in a Royal Shakespeare Company production of "Hamlet." ?
Reasoning: Let's think step by step in order to find the answer to this question. First, we need to identify the actress who played Ophelia in a Royal Shakespeare Company production of "Hamlet." Then, we need to find out if this actress also acted in the short film "The Shore."
Query: "actress who played Ophelia in Royal Shakespeare Company production of Hamlet" + "actress in short film The Shore"
Write a simple search query that will help answer a complex question.
---
Follow the following format.
Context: may contain relevant facts
Question: ${question}
Past Query: past output with errors
Instructions: Some instructions you must satisfy
Query: ${query}
---
Context:
[1] «Kerry Condon | Kerry Condon (born 4 January 1983) is an Irish television and film actress, best known for her role as Octavia of the Julii in the HBO/BBC series "Rome," as Stacey Ehrmantraut in AMC's "Better Call Saul" and as the voice of F.R.I.D.A.Y. in various films in the Marvel Cinematic Universe. She is also the youngest actress ever to play Ophelia in a Royal Shakespeare Company production of "Hamlet."»
[2] «Corona Riccardo | Corona Riccardo (c. 1878October 15, 1917) was an Italian born American actress who had a brief Broadway stage career before leaving to become a wife and mother. Born in Naples she came to acting in 1894 playing a Mexican girl in a play at the Empire Theatre. Wilson Barrett engaged her for a role in his play "The Sign of the Cross" which he took on tour of the United States. Riccardo played the role of Ancaria and later played Berenice in the same play. Robert B. Mantell in 1898 who struck by her beauty also cast her in two Shakespeare plays, "Romeo and Juliet" and "Othello". Author Lewis Strang writing in 1899 said Riccardo was the most promising actress in America at the time. Towards the end of 1898 Mantell chose her for another Shakespeare part, Ophelia im Hamlet. Afterwards she was due to join Augustin Daly's Theatre Company but Daly died in 1899. In 1899 she gained her biggest fame by playing Iras in the first stage production of Ben-Hur.»
Question: Who acted in the shot film The Shore and is also the youngest actress ever to play Ophelia in a Royal Shakespeare Company production of "Hamlet." ?
Past Query: "actress who played Ophelia in Royal Shakespeare Company production of Hamlet" + "actress in short film The Shore"
Instructions: Query should be short and less than 100 characters
Query: "actress Ophelia RSC Hamlet" + "actress The Shore"
Assertion-Driven Optimizations
DSPy Assertions은 다음 설정을 포함하여 DSPy가 제공하는 최적화, 특히 BootstrapFewShotWithRandomSearch 와 함께 작동합니다:
- Assertions을사용한 컴파일 여기에는 컴파일 중 Assertions기반 예제 부트스트랩과 반대 예제 부트스트랩이 포함됩니다. 몇 장면 데모를 부트스트랩하는 교사 모델은 DSPy Assertions을 사용하여 학생 모델이 추론하는 동안 학습할 수 있는 강력한 부트스트랩 예제를 제공할 수 있습니다. 이 설정에서 학생 모델은 추론 중에 Assertions 인식 최적화(백트래킹 및 재시도)를 수행하지 않습니다.
- Assertions을 사용한 컴파일 + 추론 – 여기에는 컴파일과 추론 모두에서 Assertions기반 최적화가 포함됩니다. 이제 교사 모델은 Assertions기반 예제를 제공하지만 학생은 추론 시간 동안 자체 Assertions으로 추가 최적화를 수행할 수 있습니다.
teleprompter = BootstrapFewShotWithRandomSearch(
metric=validate_context_and_answer_and_hops,
max_bootstrapped_demos=max_bootstrapped_demos,
num_candidate_programs=6,
)
#Compilation with Assertions
compiled_with_assertions_baleen = teleprompter.compile(student = baleen, teacher = baleen_with_assertions, trainset = trainset, valset = devset)
#Compilation + Inference with Assertions
compiled_baleen_with_assertions = teleprompter.compile(student=baleen_with_assertions, teacher = baleen_with_assertions, trainset=trainset, valset=devset)