반응형
블로그 이미지
개발자로서 현장에서 일하면서 새로 접하는 기술들이나 알게된 정보 등을 정리하기 위한 블로그입니다. 운 좋게 미국에서 큰 회사들의 프로젝트에서 컬설턴트로 일하고 있어서 새로운 기술들을 접할 기회가 많이 있습니다. 미국의 IT 프로젝트에서 사용되는 툴들에 대해 많은 분들과 정보를 공유하고 싶습니다.
솔웅

최근에 올라온 글

최근에 달린 댓글

최근에 받은 트랙백

글 보관함

카테고리


반응형

https://python.langchain.com/docs/expression_language/how_to/configure

 

Configuration | 🦜️🔗 Langchain

Oftentimes you may want to experiment with, or even expose to the end user, multiple different ways of doing things.

python.langchain.com

 

Oftentimes you may want to experiment with, or even expose to the end user, multiple different ways of doing things. In order to make this experience as easy as possible, we have defined two methods.

 

종종 작업을 수행하는 여러 가지 다른 방법을 실험하거나 최종 사용자에게 노출시키고 싶을 수도 있습니다. 이 경험을 가능한 한 쉽게 만들기 위해 두 가지 방법을 정의했습니다.

 

First, a configurable_fields method. This lets you configure particular fields of a runnable.

 

첫째, configurable_fields 메소드입니다. 이를 통해 실행 가능 항목의 특정 필드를 구성할 수 있습니다.

 

Second, a configurable_alternatives method. With this method, you can list out alternatives for any particular runnable that can be set during runtime.

 

둘째, configurable_alternatives 방법입니다. 이 방법을 사용하면 런타임 중에 설정할 수 있는 특정 실행 가능 항목에 대한 대안을 나열할 수 있습니다.

 

Configuration Fields

With LLMs

With LLMs we can configure things like temperature

 

LLM을 사용하면 온도와 같은 항목을 구성할 수 있습니다.

 

from langchain.chat_models import ChatOpenAI
from langchain.prompts import PromptTemplate

model = ChatOpenAI(temperature=0).configurable_fields(
    temperature=ConfigurableField(
        id="llm_temperature",
        name="LLM Temperature",
        description="The temperature of the LLM",
    )
)

이 코드는 "langchain" 라이브러리를 사용하여 LLM (Language Model)에 대한 설정을 구성하고 온라인으로 제어할 수 있는 옵션을 추가하는 예시를 제공합니다.

 

  1. 모듈 및 클래스 가져오기:
    • langchain.chat_models 모듈에서 ChatOpenAI 클래스를 가져옵니다. 이 클래스는 OpenAI의 대화 모델을 사용하기 위한 도구를 제공합니다.
    • langchain.prompts 모듈에서 PromptTemplate 클래스를 가져옵니다. 이 클래스는 템플릿을 만들기 위한 도구를 제공합니다.
  2. 모델 설정:
    • "model" 변수에는 "ChatOpenAI(temperature=0)"를 사용하여 대화 모델을 생성합니다. "temperature=0"은 모델의 출력을 보다 결정적으로 만듭니다.
    • .configurable_fields()를 사용하여 모델에 구성 가능한 필드를 추가하고 설정합니다. 이러한 필드는 모델의 동작을 온라인으로 변경할 수 있는 옵션을 제공합니다.
    • 구성 가능한 필드로 "temperature"을 추가합니다.
      • id="llm_temperature": 필드의 고유 식별자로 "llm_temperature"를 설정합니다.
      • name="LLM Temperature": 필드의 이름으로 "LLM Temperature"를 설정합니다.
      • description="The temperature of the LLM": 필드의 설명으로 "The temperature of the LLM"을 설정합니다.

이 코드는 "Langchain" 라이브러리를 사용하여 대화 모델의 설정을 구성 가능한 필드를 통해 정의하고, 이를 온라인으로 변경할 수 있는 옵션으로 제공하는 예시를 나타냅니다. "temperature"은 모델의 동작을 조절하는 중요한 매개변수 중 하나입니다.

 

model.invoke("pick a random number")

이 코드는 "model"을 사용하여 "pick a random number"라는 메시지를 모델에 전달하여 실행하는 부분을 설명하고 있습니다. 

  • "model.invoke("pick a random number")":
    • 이 부분은 "model"을 사용하여 "pick a random number"라는 메시지를 모델에 전달하여 실행하는 부분입니다.
    • 모델은 이 메시지를 처리하고 결과를 생성합니다.

이 코드는 모델을 사용하여 특정 메시지를 모델에 전달하고, 모델이 해당 메시지를 처리한 후에 결과를 얻는 방법을 보여줍니다. "pick a random number" 메시지에 대한 모델의 응답은 다양할 수 있으며, 해당 메시지에 따라 모델이 적절한 응답을 생성할 것입니다.

 

    AIMessage(content='7')
model.with_config(configurable={"llm_temperature": .9}).invoke("pick a random number")

이 코드는 모델 설정을 변경하고, 변경된 설정을 사용하여 "pick a random number" 메시지를 모델에 전달하여 실행하는 부분을 설명하고 있습니다. 

  • model.with_config(configurable={"llm_temperature": .9}):
    • 이 부분은 "model"에 대한 설정을 변경하는 부분입니다.
    • .with_config()를 사용하여 모델의 설정을 변경합니다.
    • configurable={"llm_temperature": .9}는 "llm_temperature"라는 설정 옵션을 변경하며, 온라인에서 모델의 온도(temperature)를 0.9로 설정합니다.
  • .invoke("pick a random number"):
    • 이 부분은 변경된 설정을 가진 모델을 사용하여 "pick a random number"라는 메시지를 모델에 전달하여 실행하는 부분입니다.
    • 모델은 이 메시지를 변경된 설정에 따라 처리하고 결과를 생성합니다.

이 코드는 모델의 설정을 변경하여 모델이 다른 동작을 하도록 만들고, 변경된 설정을 사용하여 "pick a random number" 메시지를 모델에 전달하여 실행하는 방법을 보여줍니다. 변경된 설정은 모델의 동작을 조절하며, 이 경우에는 온도(temperature)를 조정하는 예시입니다.

 

    AIMessage(content='34')

 

We can also do this when its used as part of a chain

 

체인의 일부로 사용될 때도 이 작업을 수행할 수 있습니다.

 

prompt = PromptTemplate.from_template("Pick a random number above {x}")
chain = prompt | model

이 코드는 대화 체인을 설정하고 사용자에게 특정 명령을 전달하기 위한 템플릿을 생성하는 예제를 보여줍니다. 

  • PromptTemplate.from_template("Pick a random number above {x}"):
    • 이 부분은 "PromptTemplate.from_template()"을 사용하여 대화 템플릿을 생성합니다. 이 템플릿은 사용자에게 "Pick a random number above {x}"라는 명령을 전달하고, "{x}" 부분은 나중에 사용자가 지정할 수 있는 매개변수 또는 변수를 나타냅니다.
  • chain = prompt | model:
    • 이 부분은 "chain"이라는 실행 체인을 설정합니다. 이 체인은 "prompt" (대화 템플릿)과 모델을 연결합니다.
    • 사용자에게 명령을 전달하기 위한 템플릿은 "prompt"로 지정되며, 모델은 이 템플릿에 따라 작동합니다.

이 코드는 사용자에게 "Pick a random number above {x}"라는 명령을 전달하기 위한 템플릿을 생성하고, 이 명령을 모델과 연결하여 모델이 해당 명령을 처리하고 응답을 생성할 수 있도록 하는 예시를 보여줍니다. 이때, "{x}" 부분은 나중에 사용자가 지정할 수 있는 값입니다.

 

chain.invoke({"x": 0})
    AIMessage(content='57')
chain.with_config(configurable={"llm_temperature": .9}).invoke({"x": 0})

이 코드는 실행 체인의 설정을 변경하고, 변경된 설정을 사용하여 특정 명령을 실행하는 부분을 설명하고 있습니다. 

  • chain.with_config(configurable={"llm_temperature": .9}):
    • 이 부분은 "chain"에 대한 설정을 변경하는 부분입니다.
    • .with_config()를 사용하여 실행 체인의 설정을 변경합니다.
    • configurable={"llm_temperature": .9}는 "llm_temperature"라는 설정 옵션을 변경하며, 이 경우에는 온라인에서 모델의 온도(temperature)를 0.9로 설정합니다.
  • .invoke({"x": 0}):
    • 이 부분은 변경된 설정을 가진 실행 체인을 사용하여 명령을 실행하는 부분입니다.
    • {"x": 0}는 명령에 필요한 입력 매개변수를 나타내며, 이 경우에는 "x"를 0으로 설정하여 실행될 명령을 지정합니다.
    • 실행 체인은 변경된 설정과 입력 매개변수에 따라 명령을 처리하고 결과를 생성합니다.

이 코드는 실행 체인의 설정을 변경하여 모델의 동작을 조절하고, 변경된 설정과 입력 매개변수를 사용하여 명령을 실행하는 방법을 보여줍니다. 변경된 설정은 모델의 동작을 조절하며, 입력 매개변수는 실행될 명령에 필요한 정보를 제공합니다.

    AIMessage(content='6')

 

With HubRunnables

This is useful to allow for switching of prompts

 

이는 프롬프트 전환을 허용하는 데 유용합니다.

 

from langchain.runnables.hub import HubRunnable
prompt = HubRunnable("rlm/rag-prompt").configurable_fields(
    owner_repo_commit=ConfigurableField(
        id="hub_commit",
        name="Hub Commit",
        description="The Hub commit to pull from",
    )
)
prompt.invoke({"question": "foo", "context": "bar"})
    ChatPromptValue(messages=[HumanMessage(content="You are an assistant for question-answering tasks. Use the following pieces of retrieved context to answer the question. If you don't know the answer, just say that you don't know. Use three sentences maximum and keep the answer concise.\nQuestion: foo \nContext: bar \nAnswer:")])
prompt.with_config(configurable={"hub_commit": "rlm/rag-prompt-llama"}).invoke({"question": "foo", "context": "bar"})
    ChatPromptValue(messages=[HumanMessage(content="[INST]<<SYS>> You are an assistant for question-answering tasks. Use the following pieces of retrieved context to answer the question. If you don't know the answer, just say that you don't know. Use three sentences maximum and keep the answer concise.<</SYS>> \nQuestion: foo \nContext: bar \nAnswer: [/INST]")])

이 코드는 HubRunnable을 사용하여 Hub에서 가져온 리포지토리 커밋(commit)을 설정하고, 설정된 커밋을 기반으로 특정 질문과 컨텍스트를 처리하고 실행하는 예시를 나타냅니다.

 

  1. HubRunnable 및 설정:
    • HubRunnable("rlm/rag-prompt")는 "HubRunnable"을 생성하고, 이 HubRunnable은 "rlm/rag-prompt" 리포지토리에서 컨텐츠를 가져오도록 구성되어 있습니다.
    • .configurable_fields()를 사용하여 설정 가능한 필드를 추가하고 설정합니다. 이 경우 "owner_repo_commit"이라는 설정 옵션을 추가하며, "hub_commit"이라는 식별자와 설명이 포함되어 있습니다.
  2. 첫 번째 실행:
    • prompt.invoke({"question": "foo", "context": "bar"})는 "prompt"를 사용하여 "question"과 "context" 값을 갖는 JSON 객체를 HubRunnable에 전달하고 실행합니다.
  3. 설정 변경 및 두 번째 실행:
    • prompt.with_config(configurable={"hub_commit": "rlm/rag-prompt-llama"})는 "hub_commit" 설정 값을 "rlm/rag-prompt-llama"로 변경하는 부분입니다.
    • .invoke({"question": "foo", "context": "bar"})는 변경된 설정을 가진 "prompt"를 사용하여 두 번째 실행을 수행합니다.

이 코드는 HubRunnable을 사용하여 특정 Hub 리포지토리에서 커밋을 설정하고, 설정된 커밋을 기반으로 특정 질문과 컨텍스트를 처리하고 실행하는 예시를 제공합니다. 설정을 변경함으로써 다른 커밋에서 실행을 수행할 수 있습니다.

 

Configurable Alternatives

With LLMs

Let's take a look at doing this with LLMs

 

LLM을 사용하여 이 작업을 수행하는 방법을 살펴보겠습니다.

 

from langchain.chat_models import ChatOpenAI, ChatAnthropic
from langchain.schema.runnable import ConfigurableField
from langchain.prompts import PromptTemplate
llm = ChatAnthropic(temperature=0).configurable_alternatives(
    # This gives this field an id
    # When configuring the end runnable, we can then use this id to configure this field
    ConfigurableField(id="llm"),
    # This sets a default_key.
    # If we specify this key, the default LLM (ChatAnthropic initialized above) will be used
    default_key="anthropic",
    # This adds a new option, with name `openai` that is equal to `ChatOpenAI()`
    openai=ChatOpenAI(),
    # This adds a new option, with name `gpt4` that is equal to `ChatOpenAI(model="gpt-4")`
    gpt4=ChatOpenAI(model="gpt-4"),
    # You can add more configuration options here
)
prompt = PromptTemplate.from_template("Tell me a joke about {topic}")
chain = prompt | llm
# By default it will call Anthropic
chain.invoke({"topic": "bears"})
    AIMessage(content=" Here's a silly joke about bears:\n\nWhat do you call a bear with no teeth?\nA gummy bear!")
# We can use `.with_config(configurable={"llm": "openai"})` to specify an llm to use
chain.with_config(configurable={"llm": "openai"}).invoke({"topic": "bears"})
    AIMessage(content="Sure, here's a bear joke for you:\n\nWhy don't bears wear shoes?\n\nBecause they already have bear feet!")
# If we use the `default_key` then it uses the default
chain.with_config(configurable={"llm": "anthropic"}).invoke({"topic": "bears"})
    AIMessage(content=" Here's a silly joke about bears:\n\nWhat do you call a bear with no teeth?\nA gummy bear!")

 

이 코드는 ChatAnthropic 및 다른 대화 모델(ChatOpenAI 및 모델 구성)을 사용하여 설정 가능한 대화 모델 체인을 설정하고, 설정을 변경하여 특정 대화 모델을 사용하여 특정 주제에 관한 농담을 생성하는 예시를 제공합니다.

 

  1. 모듈 및 클래스 가져오기:
    • langchain.chat_models 모듈에서 ChatOpenAI 및 ChatAnthropic 클래스를 가져옵니다. 이 클래스들은 대화 모델을 설정하고 사용하는 데 도움을 줍니다.
    • langchain.schema.runnable 모듈에서 ConfigurableField 클래스를 가져옵니다. 이 클래스는 설정 가능한 필드를 정의하는 데 사용됩니다.
    • langchain.prompts 모듈에서 PromptTemplate 클래스를 가져옵니다. 이 클래스는 대화 템플릿을 생성하는 데 사용됩니다.
  2. 대화 모델 및 설정 가능한 대화 모델 체인 설정:
    • ChatAnthropic(temperature=0)는 "ChatAnthropic" 대화 모델을 설정합니다. 이 모델은 온도(temperature)를 0으로 설정합니다.
    • .configurable_alternatives()를 사용하여 설정 가능한 대화 모델 옵션을 정의합니다.
    • "ConfigurableField"를 사용하여 "llm"이라는 설정 옵션을 추가하고 식별자를 지정합니다.
    • "default_key"를 사용하여 기본 LLM(ChatAnthropic 초기화)을 설정합니다.
    • 다른 대화 모델 옵션을 추가합니다. "openai" 및 "gpt4"라는 이름의 옵션을 추가하고, 각각 "ChatOpenAI()" 및 "ChatOpenAI(model="gpt-4")"와 연결합니다.
    • 이외에 필요한 설정 옵션을 추가할 수 있습니다.
  3. 대화 체인 설정:
    • "PromptTemplate.from_template()"를 사용하여 대화 템플릿을 생성합니다. 이 템플릿은 "{topic}" 부분을 나중에 사용자가 지정할 수 있는 변수로 사용하며, "Tell me a joke about {topic}"라는 메시지를 생성합니다.
    • chain = prompt | llm는 템플릿과 설정 가능한 대화 모델을 연결하여 실행 체인을 설정합니다.
  4. 대화 모델 및 설정 변경 및 실행:
    • "chain.invoke({"topic": "bears"})"는 설정 가능한 대화 모델 체인을 사용하여 주제가 "bears"인 농담을 생성합니다.
    • "chain.with_config(configurable={"llm": "openai"})"는 "llm" 설정 옵션을 "openai"로 변경하여 "ChatOpenAI" 대화 모델을 사용하여 동일한 주제에 관한 농담을 생성합니다.
    • "chain.with_config(configurable={"llm": "anthropic"})"는 "llm" 설정 옵션을 "anthropic" (기본 설정)으로 변경하여 "ChatAnthropic" 대화 모델을 사용하여 동일한 주제에 관한 농담을 생성합니다.

이 코드는 다양한 대화 모델 옵션을 설정하고 설정을 변경하여 다른 모델을 사용하여 특정 주제에 관한 농담을 생성하는 방법을 나타냅니다.

 

With Prompts

We can do a similar thing, but alternate between prompts

 

비슷한 작업을 수행할 수 있지만 프롬프트를 번갈아 가며 수행할 수 있습니다.

 

llm = ChatAnthropic(temperature=0)
prompt = PromptTemplate.from_template("Tell me a joke about {topic}").configurable_alternatives(
    # This gives this field an id
    # When configuring the end runnable, we can then use this id to configure this field
    ConfigurableField(id="prompt"),
    # This sets a default_key.
    # If we specify this key, the default LLM (ChatAnthropic initialized above) will be used
    default_key="joke",
    # This adds a new option, with name `poem`
    poem=PromptTemplate.from_template("Write a short poem about {topic}"),
    # You can add more configuration options here
)
chain = prompt | llm
# By default it will write a joke
chain.invoke({"topic": "bears"})
    AIMessage(content=" Here's a silly joke about bears:\n\nWhat do you call a bear with no teeth?\nA gummy bear!")
# We can configure it write a poem
chain.with_config(configurable={"prompt": "poem"}).invoke({"topic": "bears"})
    AIMessage(content=' Here is a short poem about bears:\n\nThe bears awaken from their sleep\nAnd lumber out into the deep\nForests filled with trees so tall\nForaging for food before nightfall \nTheir furry coats and claws so sharp\nSniffing for berries and fish to nab\nLumbering about without a care\nThe mighty grizzly and black bear\nProud creatures, wild and free\nRuling their domain majestically\nWandering the woods they call their own\nBefore returning to their dens alone')

이 코드는 ChatAnthropic 대화 모델을 사용하여 설정 가능한 대화 템플릿 체인을 설정하고, 설정을 변경하여 농담 또는 시를 작성하는 예시를 제공합니다.

 

  1. 대화 모델 및 설정 가능한 대화 템플릿 설정:
    • ChatAnthropic(temperature=0)는 "ChatAnthropic" 대화 모델을 설정합니다. 이 모델은 온도(temperature)를 0으로 설정합니다.
    • PromptTemplate.from_template("Tell me a joke about {topic}")는 대화 템플릿을 생성하고, "{topic}" 부분은 나중에 사용자가 지정할 수 있는 변수로 사용됩니다.
    • .configurable_alternatives()를 사용하여 설정 가능한 대화 템플릿 옵션을 정의합니다.
    • "ConfigurableField"를 사용하여 "prompt"이라는 설정 옵션을 추가하고 식별자를 지정합니다.
    • "default_key"를 사용하여 기본 설정을 "joke"로 설정합니다. 이는 초기에 농담을 작성할 것을 의미합니다.
    • 다른 대화 템플릿 옵션을 추가할 수 있습니다. "poem"이라는 이름의 옵션을 추가하고, 시를 작성하는 템플릿을 연결합니다.
  2. 대화 체인 설정:
    • chain = prompt | llm는 템플릿과 설정 가능한 대화 모델을 연결하여 실행 체인을 설정합니다.
  3. 농담 또는 시 작성:
    • "chain.invoke({"topic": "bears"})"는 설정 가능한 대화 템플릿 체인을 사용하여 주제가 "bears"인 농담을 생성합니다.
    • "chain.with_config(configurable={"prompt": "poem"})"는 "prompt" 설정 옵션을 "poem"으로 변경하여 동일한 주제에 대한 시를 생성합니다.

이 코드는 설정 가능한 대화 템플릿과 대화 모델을 사용하여 사용자가 설정을 변경함으로써 농담 또는 시를 작성하는 방법을 나타냅니다. "joke" 및 "poem" 설정을 변경하여 원하는 유형의 대화를 생성할 수 있습니다.

 

 

With Prompts and LLMs

We can also have multiple things configurable! Here's an example doing that with both prompts and LLMs.

 

여러 가지를 구성할 수도 있습니다! 프롬프트와 LLM을 모두 사용하여 이를 수행하는 예는 다음과 같습니다.

 

llm = ChatAnthropic(temperature=0).configurable_alternatives(
    # This gives this field an id
    # When configuring the end runnable, we can then use this id to configure this field
    ConfigurableField(id="llm"),
    # This sets a default_key.
    # If we specify this key, the default LLM (ChatAnthropic initialized above) will be used
    default_key="anthropic",
    # This adds a new option, with name `openai` that is equal to `ChatOpenAI()`
    openai=ChatOpenAI(),
    # This adds a new option, with name `gpt4` that is equal to `ChatOpenAI(model="gpt-4")`
    gpt4=ChatOpenAI(model="gpt-4"),
    # You can add more configuration options here
)
prompt = PromptTemplate.from_template("Tell me a joke about {topic}").configurable_alternatives(
    # This gives this field an id
    # When configuring the end runnable, we can then use this id to configure this field
    ConfigurableField(id="prompt"),
    # This sets a default_key.
    # If we specify this key, the default LLM (ChatAnthropic initialized above) will be used
    default_key="joke",
    # This adds a new option, with name `poem`
    poem=PromptTemplate.from_template("Write a short poem about {topic}"),
    # You can add more configuration options here
)
chain = prompt | llm
# We can configure it write a poem with OpenAI
chain.with_config(configurable={"prompt": "poem", "llm": "openai"}).invoke({"topic": "bears"})
    AIMessage(content="In the forest, where tall trees sway,\nA creature roams, both fierce and gray.\nWith mighty paws and piercing eyes,\nThe bear, a symbol of strength, defies.\n\nThrough snow-kissed mountains, it does roam,\nA guardian of its woodland home.\nWith fur so thick, a shield of might,\nIt braves the coldest winter night.\n\nA gentle giant, yet wild and free,\nThe bear commands respect, you see.\nWith every step, it leaves a trace,\nOf untamed power and ancient grace.\n\nFrom honeyed feast to salmon's leap,\nIt takes its place, in nature's keep.\nA symbol of untamed delight,\nThe bear, a wonder, day and night.\n\nSo let us honor this noble beast,\nIn forests where its soul finds peace.\nFor in its presence, we come to know,\nThe untamed spirit that in us also flows.")
# We can always just configure only one if we want
chain.with_config(configurable={"llm": "openai"}).invoke({"topic": "bears"})
    AIMessage(content="Sure, here's a bear joke for you:\n\nWhy don't bears wear shoes?\n\nBecause they have bear feet!")

이 코드는 ChatAnthropic 및 ChatOpenAI 대화 모델을 설정 가능한 대화 모델 체인으로 결합하고, 설정을 변경하여 다양한 대화 모델 및 대화 템플릿 옵션을 사용할 수 있는 예시를 제공합니다.

 

Saving configurations

We can also easily save configured chains as their own objects

 

구성된 체인을 자체 객체로 쉽게 저장할 수도 있습니다.

 

openai_poem = chain.with_config(configurable={"llm": "openai"})
openai_poem.invoke({"topic": "bears"})

이 코드는 이전에 설정한 대화 체인에서 "llm" 설정 옵션을 "openai"로 변경하고, 변경된 설정을 사용하여 "bears" 주제에 관한 시를 생성하는 예시를 나타냅니다.

 

  1. openai_poem = chain.with_config(configurable={"llm": "openai"}):
    • 이 부분은 "chain" 대화 체인에서 .with_config()를 사용하여 설정을 변경합니다.
    • configurable={"llm": "openai"}는 "llm" 설정 옵션을 "openai"로 변경하는 설정입니다.
    • 변경된 설정을 가진 새로운 대화 체인을 "openai_poem" 변수에 할당합니다.
  2. openai_poem.invoke({"topic": "bears"}):
    • 이 부분은 변경된 설정을 가진 "openai_poem" 대화 체인을 사용하여 주제가 "bears"인 시를 생성하는 부분입니다.
    • .invoke({"topic": "bears"})는 설정된 대화 모델과 주제 정보를 사용하여 시를 작성하고 결과를 반환합니다.

이 코드는 설정을 변경하여 다른 대화 모델을 사용하고, 변경된 설정을 가진 대화 체인을 사용하여 "bears" 주제에 관한 시를 생성하는 방법을 나타냅니다.

 

    AIMessage(content="Why don't bears wear shoes?\n\nBecause they have bear feet!")
반응형