LangChain

[MODEL I/O - Prompts] Example selectors

LYShin 2023. 7. 4. 19:30

- 출처 : https://python.langchain.com/docs/modules/model_io/

- 이 블로그 글은 LangChain API document의 글을 기반으로 번역되었으며 이 과정에서 약간의 내용이 추가되었습니다.

- MODEL I/O는 Prompts, Language Model, Output Parser로 이루어져 있습니다.

- 본 글에서는 Example selector 전반에 대해 다룹니다.

 

Example Selectors

 

만약, 프롬프트를 구성하는데 너무 많은 예시가 있다면, 그중 몇 가지를 선택해야 할 필요가 있을 수 있습니다. one shot example을 사용하고 싶거나, 비용적인 측면을 고려해야 한다면 Example Selector가 아주 효과적일 것입니다.

 

 

 

1. Custom Example Selector

 

Custom Example Selector는 사용자가 원하는 방식으로 예시를 선택할 수 있도록 구현할 수 있습니다.

 

이번 섹션에선 주어진 예시로부터 모든 무작위로 예시를 선택하는 Custom Example Selector를 만들 것입니다.

 

ExampleSelector는 두 method를 시행합니다.

첫 번째로, add_example method는 예시를 받아 ExampleSelector 객체에 추가합니다.

두 번째로, select_example method는 input variable을 받아 few shot prompt에 사용할 예시를 반환합니다.

 

 

이번에는 2개의 예시를 무작위로 선택하는 custom ExampleSelector를 만들겠습니다.

from langchain.prompts.example_selector.base import BaseExampleSelector
from typing import Dict, List
import numpy as np


class CustomExampleSelector(BaseExampleSelector):
    
    def __init__(self, examples: List[Dict[str, str]]):
        self.examples = examples
    
    def add_example(self, example: Dict[str, str]) -> None:
        self.examples.append(example)

    def select_examples(self, input_variables: Dict[str, str]) -> List[dict]:
        return np.random.choice(self.examples, size=2, replace=False)

 

 

CustomExampleSelector 객체를 구현해습니다. 이제 몇 개의 예시를 만들어, 그중 2개를 무작위로 선택해 보겠습니다.

examples = [
    {"One": "1"},
    {"Two": "2"},
    {"Three": "3"}
]

example_selector = CustomExampleSelector(examples)

example_selector.select_examples({None})
array([{'One': '1'}, {'Three': '3'}], dtype=object)

 

 

이번에는 CustomExampleSelector에 새로운 예시를 추가하고, 다시 무작위로 예시를 선택하겠습니다. 

example_selector.add_example({"four": "4"})
example_selector.examples

example_selector.select_examples({None})
array([{'Two': '2'}, {'four': '4'}], dtype=object)

 

 

 

2. Select By Length

 

LengthBasedExampleSelector는 길이에 기반하여 예시를 선택합니다. 구성된 프롬프트가 주어진 길이를 넘어가는 경우 유용하게 사용할 수 있습니다. 입력의 길이가 길면 예시를 더 적게 구성하고, 입력의 길이가 짧으면 예시를 더 많이 구성합니다.

 

 

먼저, 예시를 만들고 LengthBasedExampleSelector를 사용하여 입력 길이를 기반으로 예시를 선택하는 템플릿을 만들어보겠습니다.

from langchain.prompts import PromptTemplate
from langchain.prompts import FewShotPromptTemplate
from langchain.prompts.example_selector import LengthBasedExampleSelector


examples = [
    {"input": "happy", "output": "sad"},
    {"input": "tall", "output": "short"},
    {"input": "energetic", "output": "lethargic"},
    {"input": "sunny", "output": "gloomy"},
    {"input": "windy", "output": "calm"}
    ]
example_prompt = PromptTemplate(
    input_variables=["input", "output"],
    template="Input: {input}\nOutput: {output}",
)
example_selector = LengthBasedExampleSelector(
    examples=examples, 
    example_prompt=example_prompt, 
    max_length=25,
)
dynamic_prompt = FewShotPromptTemplate(
    example_selector=example_selector,
    example_prompt=example_prompt,
    prefix="Give the antonym of every input",
    suffix="Input: {adjective}\nOutput:", 
    input_variables=["adjective"],
)

 

 

그럼 다음으로 짧은 입력과 긴 입력에 따라 프롬프트가 어떻게 변화하는지 확인해 보겠습니다.

# short input
print(dynamic_prompt.format(adjective="big"))
Give the antonym of every input

Input: happy
Output: sad

Input: tall
Output: short

Input: energetic
Output: lethargic

Input: sunny
Output: gloomy

Input: windy
Output: calm

Input: big
Output:

 

 

# long input
long_string = "big and huge and massive and large and gigantic and tall and much much much much much bigger than everything else"
print(dynamic_prompt.format(adjective=long_string))
Give the antonym of every input

Input: happy
Output: sad

Input: big and huge and massive and large and gigantic and tall and much much much much much bigger than everything else
Output:

 

 

LengthBasedExampleSelector 역시 add_example method를 사용하면 예시를 추가할 수 있습니다. 예시를 추가하고, 프롬프트를 구현해 보겠습니다.

# add_example
new_example = {"input": "big", "output": "small"}
dynamic_prompt.example_selector.add_example(new_example)
print(dynamic_prompt.format(adjective="enthusiastic"))
Give the antonym of every input

Input: happy
Output: sad

Input: tall
Output: short

Input: energetic
Output: lethargic

Input: sunny
Output: gloomy

Input: windy
Output: calm

Input: big
Output: small

Input: enthusiastic
Output:

 

 

 

3. Maximal Marginal Relevance & SemamticSimilarityExampleSelector

 

MaxMarginalRelevanceExampleSelector는 다양성을 최적화하면서 입력과 가장 가까운 예시를 선택합니다.

 

이 방법은 입력과 가장 큰 코사인 유사도를 갖는 예시를 선택하며, 선택하는 과정을 반복하면서 앞서 선택된 예시와의 근접도에 따라 페널티를 적용하며 수행합니다. 따라서 코사인 유사도를 기반으로 다양한 예시를 선택할 수 있습니다.

 

먼저, 예시를 만들고 MaxMarginalRelevanceExampleSelector를 적용하여 2개의 예시를 선택하겠습니다. 

from langchain.prompts.example_selector import (
    MaxMarginalRelevanceExampleSelector,
    SemanticSimilarityExampleSelector,
)
from langchain.vectorstores import Chroma
from langchain.embeddings import OpenAIEmbeddings
from langchain.prompts import FewShotPromptTemplate, PromptTemplate

example_prompt = PromptTemplate(
    input_variables=["input", "output"],
    template="Input: {input}\nOutput: {output}",
)

examples = [
    {"input": "happy", "output": "sad"},
    {"input": "tall", "output": "short"},
    {"input": "energetic", "output": "lethargic"},
    {"input": "sunny", "output": "gloomy"},
    {"input": "windy", "output": "calm"},
]

example_selector = MaxMarginalRelevanceExampleSelector.from_examples(
    examples,
    OpenAIEmbeddings(),
    Chroma,
    k=2,
)

mmr_prompt = FewShotPromptTemplate(
    example_selector=example_selector,
    example_prompt=example_prompt,
    prefix="Give the antonym of every input",
    suffix="Input: {adjective}\nOutput:",
    input_variables=["adjective"],
)

print(mmr_prompt.format(adjective='worried'))
Number of requested results 20 is greater than number of elements in index 5, updating n_results = 5
Give the antonym of every input

Input: happy
Output: sad

Input: windy
Output: calm

Input: worried
Output:

(happy, sad), (windy, calm) 두 개의 예시가 선택된 것을 확인할 수 있습니다. 'worried'가 감정과 관련이 있는 단어이기 때문에 가장 가까운 (happy, sad)가 선택되었고, (happy, sad)과 같이 감정과 관련된 단어에는 페널티가 적용되어 (sunny, gloomy) 대신 (windy, calm)이 선택된 것으로 보입니다.

 

 

다음으로 MaxMarginalRelevanceExampleSelector와 비슷하게 유사도를 사용하여 예시를 선택하는 SemamticSimilarityExampleSelector를 구현하여 비교해 보겠습니다. SemamticSimilarityExampleSelectorMaxMarginalRelevanceExampleSelector와 다르게 오직 유사도만을 고려하여 예시를 선택합니다. 위의 예시에서 유사도만을 고려한다면 아마 (happy, sad)와 (sunny, gloomy)가 선택될 것으로 보입니다. 아래 코드에서 확인해 보겠습니다.

# SemanticSimilarityExampleSelector와 비교해봅시다.

example_selector = SemanticSimilarityExampleSelector.from_examples(
    examples,
    OpenAIEmbeddings(),
    Chroma,
    k=2,
)

similar_prompt = FewShotPromptTemplate(
    example_selector=example_selector,
    example_prompt=example_prompt,
    prefix="Give the antonym of every input",
    suffix="Input: {adjective}\nOutput:",
    input_variables=["adjective"],
)

print(similar_prompt.format(adjective="worried"))
Give the antonym of every input

Input: happy
Output: sad

Input: sunny
Output: gloomy

Input: worried
Output:

 

 

 

4. N-gram Overlap

 

NGramOverlapExampleSelector는 ngram overlap score에 따라 입력과 가장 유사한 예시를 선택하고 정렬합니다. ngram overlap score는 0.0 ~ 1.0 사이의 float 객체이며, 지정한 threshold보다 작거나 같은 예시를 제외하게 됩니다. 만약 threshold가 -1이면 예시를 제외하지 않고 정렬만 하게 되며, threshold가 0이면 입력과 ngram overlap이 없는 예시를 제외합니다.

 

 

먼저, 예시를 만들고 NGramOverlapExampleSelector를 적용하여 threshold별 프롬프트를 살펴보겠습니다.

from langchain.prompts import PromptTemplate
from langchain.prompts.example_selector.ngram_overlap import NGramOverlapExampleSelector
from langchain.prompts import FewShotPromptTemplate, PromptTemplate

example_prompt = PromptTemplate(
    input_variables=["input", "output"],
    template="Input: {input}\nOutput: {output}",
)

examples = [
    {"input": "See Spot run.", "output": "Ver correr a Spot."},
    {"input": "My dog barks.", "output": "Mi perro ladra."},
    {"input": "Spot can run.", "output": "Spot puede correr."},
]

 

 

threshold가 -1일 때 프롬프트를 먼저 확인해 보겠습니다. 

example_selector = NGramOverlapExampleSelector(
    examples=examples,
    example_prompt=example_prompt,
    threshold=-1.0,
)

dynamic_prompt = FewShotPromptTemplate(
    example_selector=example_selector,
    example_prompt=example_prompt,
    prefix="Give the Spanish translation of every input",
    suffix="Input: {sentence}\nOutput:",
    input_variables=["sentence"],
)

print(dynamic_prompt.format(sentence="Spot can run fast."))
Give the Spanish translation of every input

Input: Spot can run.
Output: Spot puede correr.

Input: See Spot run.
Output: Ver correr a Spot.

Input: My dog barks.
Output: Mi perro ladra.

Input: Spot can run fast.
Output:

 

 

이번에는 threshold가 0일 때 프롬프트가 어떻게 구성되는지 확인해보겠습니다. 

example_selector.threshold = 0.0
print(dynamic_prompt.format(sentence="Spot can run fast."))
Give the Spanish translation of every input

Input: Spot can run.
Output: Spot puede correr.

Input: See Spot run.
Output: Ver correr a Spot.

Input: Spot plays fetch.
Output: Spot juega a buscar.

Input: Spot can run fast.
Output:

 

 

다음으로 threshold에 0과 1 사이의 어떤 값으로 지정하면 어떤 변화가 생기는지 확인해보겠습니다.

example_selector.threshold = 0.09
print(dynamic_prompt.format(sentence="Spot can play fetch."))
Give the Spanish translation of every input

Input: Spot can run.
Output: Spot puede correr.

Input: Spot plays fetch.
Output: Spot juega a buscar.

Input: Spot can play fetch.
Output:

 

 

마지막으로 threshold가 1보다 큰 경우, 프롬프트가 어떻게 구성되는지 확인해보겠습니다.

example_selector.threshold = 1.0 + 1e-9
print(dynamic_prompt.format(sentence="Spot can play fetch."))
Give the Spanish translation of every input

Input: Spot can play fetch.
Output: