본문 바로가기
NLP

reading LangChain docs (3)

by 볼록티 2024. 1. 17.
728x90
반응형

 

 

  지난번에 이어서 본격적으로 코드를 실행 시켜보면서 랭체인이 제공하는 기술들에 대해 익혀볼까 합니다. LCEL 인터페이스와 더 친해지기 위해 반드시 Prompt + LLM 페이지를 먼저 시작하는 걸 추천합니다. 


Cookbook

Prompt + LLM: 프롬프트를 사용해서 LLM과 상호작용하는 방법을 설명합니다. 구체적인 프롬프트를 제공하고 응답을 얻어 LLM과 의사소통하는 기본적인 접근 방식입니다.

 

RAG: 검색 기반 언어 모델과 생성 언어 모델을 결합하여 응답의 품질과 관련성을 향상시키는 기술입니다. 응답을 생성하기 전에 관련 정보를 가져오기 위해 검색기 모델을 사용하는 경우가 많습니다.

 

Multiple chains: 각각 특정 목적이나 기능을 가진 다중 채팅 체인의 사용에 대해 설명합니다. 여러 체인을 사용하여 다양한 유형의 쿼리나 상호 작용을 효과적으로 처리할 수 있습니다.

 

Querying a SQL DB: SQL 데이터베이스를 쿼리할 수 있어 사용자는 자연어 프롬프트와 명령을 사용하여 데이터베이스에서 데이터르르 검색하고 조작할 수 있습니다.

 

Agents: 랭체인을 사용하여 구축할 수 있는 대화형 에이전트 또는 챗봇을 의미합니다. 다양한 에이전트를 만들고 구성하는 방법을 다룹니다.

 

Code writing: 자연어 쿼리 또는 프롬프트를 기반으로 코드 관련 응답을 생성하기 위해 랭체인을 사용하는 방법을 설명합니다.

 

Routing by semantic similarity: 의미적 유사성을 기반으로 프롬프트와 쿼리를 라우팅하거나 처리할 수 있습니다. 의미론적 분석을 사용해 프롬프트를 적절한 응답 체인으로 보내는 방법을 다룹니다.

 

Adding memory: 이전 상호 작용이나 대화의 맥락을 기억하는 랭체인 시스템의 기능을 의미하며, 대화의 연속성을 유지하는데 유용합니다.

 

Adding moderation: 응답 내용을 필터링하고 제어하여 특정 지침이나 정책을 준수하도록 랭체인 시스템에 조정기능을 추가할 수 있습니다.

 

Managing prompt size: 원하는 응답을 효율적으로 얻기 위해 프롬프트의 크기와 복잡성을 관리하고 최적화하는 전략을 논의합니다.

 

Using tools: 전처리, 후처리 및 기타 기능으로 랭체인에서 사용할 수 있는 도구와 유틸리티를 설명합니다.


Prompt + LLM

 - Basic example

 PromptTemplate이나 ChatPromptTemplate으로 프롬프트를 생성해줍니다. 그런 다음 LLM 또는 ChatModel로 모델을 정의해줍니다. 마지막으로 OutputParser로 결괏값을 설정하는 순서로 진행됩니다. 대부분의 형식이 이를 따를겁니다.

프롬프트설정 -> 모델설정 -> 결괏값설정

 

 아래의 코드에서는 ChatPromptTemplate을 사용했고, 템플릿은 str 형태이며  {foo}라는 placeholder를 가지고 있습니다. 모델은 ChatOpenAI()모델을 사용했습니다. OutputParser는 설정하지 않았네요.

from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI

prompt = ChatPromptTemplate.from_template("tell me a joke about {foo}")
model = ChatOpenAI()
chain = prompt | model

 

 chain을 만들었으니 invoke로 체인을 실행시켜 봅니다. 그러면 응답을 AIMessage()로 출력해줍니다.

chain.invoke({"foo": "bears"})

## AIMessage(content="Sure, here's a bear-themed joke for you:\n\nWhy don't bears wear shoes?\n\nBecause they already have bear feet!")

 


 

 - Attaching Stop Sequences

  bind로 추가설정을 합니다. "\n" 부분에서 생성을 멈추라는 뜻입니다.

chain = prompt | model.bind(stop=["\n"])

chain.invoke({"foo": "bears"})

### AIMessage(content="Why don't bears wear shoes?")

 


 

 - Attaching Function Call information

  특정 함수를 호출하는 예제 입니다. functions 리스트에 담긴 함수 설명 내용을 살펴보겠습니다. 함수의 이름은 "joke", 설명은 "A joke"로 정의했습니다. 

  매개변수는 "object" 형태를 받고, "setup", "punchline"이라는 두 개의 매개변수를 받습니다. 

  model.bind()에서 function_call에 함수의 이름을 지정하고 functions에 설정한 functions을 지정해줍니다. 

  invoke() 합니다. 이 때 config={}로 추가적인 인자를 줄 수 있지만 여기서는 비어있습니다.

 

  함수 "joke"를 호출하는 부분에서 매개변수 setup, punchline이 필요한데 만약 매개변수에 해당하는 값을 제공하지 않으면 함수 호출이 실패할 수 있습니다. 정확한 입력을 제공하면 "joke" 함수가 실행되고 해당 함수에 정의된 설명에 따라 농담을 생성합니다. 생성된 농담은 모델에서 반환됩니다.

 

  종합적으로 사용자로부터 입력된 함수 이름을 기반으로 함수를 호출하고 필요한 매개변수를 제공해 결과를 얻는 작업입니다. 채팅 시스템에서 함수 호출을 처리하고 해당 함수의 작업을 수행하는 방법을 보여주는데 사용됩니다.

functions = [
    {
        "name": "joke",
        "description": "A joke",
        "parameters": {
            "type": "object",
            "properties": {
                "setup": {"type": "string", "description": "The setup for the joke"},
                "punchline": {"type": "string", "description": "The punchline for the joke",
                },
            },
            "required": ["setup", "punchline"],
        },
    }
]
chain = prompt | model.bind(function_call={"name": "joke"}, functions=functions)

chain.invoke({"foo": "bears"}, config={})

### AIMessage(content='', additional_kwargs={'function_call': {'arguments': '{\n  "setup": "Why don\'t bears wear shoes?",\n  "punchline": "Because they have bear feet!"\n}', 'name': 'joke'}})

 


 

- PromptTemplate + LLM + OutputParser

   output_parsers 모듈에 StrOutputParser() 클래스를 불러와서 사용하면 출력물을 기본 str 형태로 받아볼 수 있습니다.

from langchain_core.output_parsers import StrOutputParser

chain = prompt | model | StrOutputParser()

chain.invoke({'foo':''})

## "Why don't scientists trust atoms?\n\nBecause they make up everything!"

 


- PromptTemplate + LLM + OutputParser

  특정 함수 값을 리턴하도록 하고 싶을 때 openai_functions 모듈에서 JsonOutputFunctionParser 클래스를 활용합니다. 이전에 function 리턴값보다 깔끔하게 나오는 것을 확인할 수 있습니다.

from langchain.output_parsers.openai_functions import JsonOutputFunctionsParser

chain = (
    prompt
    | model.bind(function_call={"name": "joke"}, functions=functions)
    | JsonOutputFunctionsParser()
)

chain.invoke({"foo":"dog"})

'''
{'setup': 'Why did the dog sit in the shade?',
 'punchline': "Because it didn't want to be a hot dog!"}
'''

 

- Functions Output Parser

  특정 매개변수만을 출력하고 싶다면 해당 키 네임을 입력해줍니다. 이 떄 사용되는 클래스는 JsonKeyOutputFunctionsParser입니다.

from langchain.output_parsers.openai_functions import JsonKeyOutputFunctionsParser

chain = (
    prompt
    | model.bind(function_call={"name": "joke"}, functions=functions)
    | JsonKeyOutputFunctionsParser(key_name="setup")
)

chain.invoke({"foo": "dog"})

## "Why don't dogs make good dancers?"

 


 

- Simplifying input

  호출을 더 심플하게 하기 위해 RunnableParallel을 추가했습니다. chain을 설정하는 구간에 프롬프트보다 먼저 변수를 넣어주도록 합니다. foo 를 미리 지정해주는 것입니다. 

from langchain_core.runnables import RunnableParallel, RunnablePassthrough

map_ = RunnableParallel(foo=RunnablePassthrough())
chain = (
    map_
    | prompt
    | model.bind(function_call={"name": "joke"}, functions=functions)
    | JsonKeyOutputFunctionsParser(key_name="setup")
)

chain.invoke("dog")

## "Why don't dogs make good dancers?"

 

 

  또다른 Runnable이 있는 map을 구성해보면 아래와 같이 map_이 아니라 직접 딕셔너리 형식으로 넣어주면 위에서 했던 것처럼 chain.invoke()부분을 간편하게 만들 수 있습니다.

chain = (
    {"foo": RunnablePassthrough()}
    | prompt
    | model.bind(function_call={"name": "joke"}, functions=functions)
    | JsonKeyOutputFunctionsParser(key_name="setup")
)

chain.invoke("dog")

## "Why don't dogs make good dancers?"

 

 

 


RAG

 - Basic example

  LLM과 prompt에서 검색 단계를 한번 추가해봅니다. "retrieval-augmented generation" chain을 추가하는 겁니다.

  retriever = vectorstort.as_retriever()로 검색기를  먼저 생성합니다. 검색에 사용될 context는 template에서 자리를 마련해주도록 합니다. 그런다음 prompt, model, chain 순으로 진행합니다. chain에서 content, question을 retriever, RunnablePassthrough에서 입력받도록 설정합니다. 

from operator import itemgetter

from langchain_community.vectorstores import FAISS
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnableLambda, RunnablePassthrough
from langchain_openai import ChatOpenAI, OpenAIEmbeddings

vectorstore = FAISS.from_texts(
    ["harrison worked at kensho"], embedding=OpenAIEmbeddings()
)
retriever = vectorstore.as_retriever()

template = """Answer the question based only on the following context:
{context}

Question: {question}
"""
prompt = ChatPromptTemplate.from_template(template)

model = ChatOpenAI()

chain = (
    {"context": retriever, "question": RunnablePassthrough()}
    | prompt
    | model
    | StrOutputParser()
)

chain.invoke("where did harrison work?")

## 'Harrison worked at Kensho.'

 


 

 

 - Conversational Retrieval Chain

  대화 내용을 쉽게 추가할 수도 있습니다. 주로 chat_message_history에 추가하는 것을 의미합니다.

 

  _template 템플릿을 우선 생성합니다. PromptTemplate.from_template 클래스를 사용했습니다. 템플릿 내용은 주어진 대화를 기반으로 질문을 팔로우업해서 원래의 언어로 독립적인 질문으로 재구성하라는 의미입니다.

from langchain.schema import format_document
from langchain_core.messages import AIMessage, HumanMessage, get_buffer_string
from langchain_core.runnables import RunnableParallel

from langchain.prompts.prompt import PromptTemplate

_template = """Given the following conversation and a follow up question, rephrase the follow up question to be a standalone question, in its original language.

Chat History:
{chat_history}
Follow Up Input: {question}
Standalone question:"""
CONDENSE_QUESTION_PROMPT = PromptTemplate.from_template(_template)

 

  template 템플릿을 추가로 생성합니다. 템플릿은 주어지는 context를 기반으로 질문에 대답하라는 의미입니다. ChatPromptTemplate.from_template 클래스를 사용했습니다.

template = """Answer the question based only on the following context:
{context}

Question: {question}
"""
ANSWER_PROMPT = ChatPromptTemplate.from_template(template)

 

   여러개의 문서를 특정 템플릿을 기반으로 결합해 하나의 문서로 만드는 작업입니다.

DEFAULT_DOCUMENT_PROMPT = PromptTemplate.from_template(template="{page_content}")


def _combine_documents(
    docs, document_prompt=DEFAULT_DOCUMENT_PROMPT, document_separator="\n\n"
):
    doc_strings = [format_document(doc, document_prompt) for doc in docs]
    return document_separator.join(doc_strings)

 

 

아래의 코드)

  _inputs 을 정의합니다. standalone_question이라는 작업을 생성합니다. RunnablePassthrough.assign을 사용해 chat_history필드를 추출하고 get_buffer_string함수로 문자열로 변환합니다.

  CONDENSE_QUESTION_PROMPT와 모델과 outputparser를 차례대로 실행시켜 줍니다. temperature=0은 같은 질문에 같은 대답이 계속 나오게 합니다(자세히는 온도가 0이면 출력이 매우 결정적이고 집중적입니다. 값이 높을수록 무작위성과 창의성이 더 높아집니다.).

 

  _context 를 정의합니다. context 키는 standalone_question을 retriever와 _combine_documents로 전달해 문맥을 설정합니다. 이부분은 대화 내용과 함께 문맥을 구축하고, 여러 문서를 결합하는 작업을 수행합니다.  question키는 standalone_question을 직접 사용합니다. 

 

  마지막으로 conversational_qa_chain을 정의합니다. _inputs, _context, ANSWER_PROMPT, ChatOpenAI 를 연결해 대화형 질문-답변 체인을 구성합니다. _inputs에서 생성된 standalone_question이 context로 전달되서 이어서 ANSWER_QUESTION를 사용해 모델에 질문을 전달하고 모델의 출력을 반환합니다.

_inputs = RunnableParallel(
    standalone_question=RunnablePassthrough.assign(
        chat_history=lambda x: get_buffer_string(x["chat_history"])
    )
    | CONDENSE_QUESTION_PROMPT
    | ChatOpenAI(temperature=0)
    | StrOutputParser(),
)
_context = {
    "context": itemgetter("standalone_question") | retriever | _combine_documents,
    "question": lambda x: x["standalone_question"],
}
conversational_qa_chain = _inputs | _context | ANSWER_PROMPT | ChatOpenAI()

 

 

  invoke로 전달될 인자인 딕셔너리에서 question은 질문을 포함하고 있습니다. 처음 chat_history 필드는 대화의 내용을 나타냅니다. 하지만 초기에는 빈 리스트로 설정해줍니다.

  _inputs에서 standalone_question을 생성합니다. 이 때 "where did harrison work?" 질문이 standalone_question으로 설정됩니다. _context는 standalone_question를 사용하여 컨텍스트 문맥을 설정하고 질문을 분석합니다. 그 다음 ANSWER_PROMPT를 사용해 모델에 대화 내용과 분석된 질문을 전달합니다. 
  모델은 질문을 받고 내부에서 처리해 출력결과를 반환합니다.

conversational_qa_chain.invoke(
    {
        "question": "where did harrison work?",
        "chat_history": [],
    }
)

## AIMessage(content='Harrison was employed at Kensho.')

 

 

  인칭대명사 he 로 바꿔 질문을 해도 대화의 문맥에 맞게 harrison에 대한 정보를 토대로 답변하는 것을 보실 수 있습니다.

conversational_qa_chain.invoke(
    {
        "question": "where did he work?",
        "chat_history": [
            HumanMessage(content="Who wrote this notebook?"),
            AIMessage(content="Harrison"),
        ],
    }
)

# AIMessage(content='Harrison worked at Kensho.')

  


 

 

 

 - With Memory and returning source documents

  위에서 진행했던 메모리를 어떻게 다룰지를 알려줍니다. 외부에 있는 메모리도 관리할 필요도 있을텐데요. 검색된 문서를 반환하기 위해서 다양한 방법을 사용해봅니다.

 

  langchain.memory의 ConversationBufferMemory()클래스를 사용합니다. ConversationBufferMemory는 대화 내용을 저장하고 로드하는 기능을 제공하는 메모리 객체입니다. return_messages=True는 메모리의 대화내용을 메세지 객체로 저장하고 반환합니다. ouput_key와 input_key는 메모리에 저장되는 대화내용의 출력 및 입력을 지정하는데 사용됩니다.

from operator import itemgetter
from langchain.memory import ConversationBufferMemory

memory = ConversationBufferMemory(
    return_messages=True, output_key="answer", input_key="question"
)

 

 

 

  1. loaded_memory: 메모리를 로드하는 작업을 정의합니다. chat_history를 로드한 다음에 history필드를 반환합니다.

  2. standardalone_question: 대화 내용과 이전 메모리를 기반으로 독립적인 질문을 생성하는 작업을 정의합니다.  이어서 체인 순서대로 CONDENSE_QUESTION_PROMPT와 모델, 아웃풋 파서를 통해 독립적인 질문을 생성합니다.

  3. retrieved_documents: 독립적인 질문을 기반으로 문서를 검색하는 작업을 정의합니다. itemgetter("standalone_question")을 사용해 독립적인 질문을 가져온 후 retriever를 사용해 질문에 대한 문서를 검색합니다.

  4. final_inputs: 검색된 문서와 질문을 결합하여 최종 입력을 생성하는 작업을 정의합니다.

  5. answer: 최종 입력을 기반으로 답변을 생성하는 작업을 정의합니다. ANSWER_PROMPT와 모델을 사용해 답변을 생성합니다.

  6. final_chain: 전체 실행 체인을 생성합니다. 메모리로드, 질문 생성, 문서 검색 및 답변 생성의 단계로 구성됩니다.

# First we add a step to load memory
# This adds a "memory" key to the input object
loaded_memory = RunnablePassthrough.assign(
    chat_history=RunnableLambda(memory.load_memory_variables) | itemgetter("history"),
)
# Now we calculate the standalone question
standalone_question = {
    "standalone_question": {
        "question": lambda x: x["question"],
        "chat_history": lambda x: get_buffer_string(x["chat_history"]),
    }
    | CONDENSE_QUESTION_PROMPT
    | ChatOpenAI(temperature=0)
    | StrOutputParser(),
}
# Now we retrieve the documents
retrieved_documents = {
    "docs": itemgetter("standalone_question") | retriever,
    "question": lambda x: x["standalone_question"],
}
# Now we construct the inputs for the final prompt
final_inputs = {
    "context": lambda x: _combine_documents(x["docs"]),
    "question": itemgetter("question"),
}
# And finally, we do the part that returns the answers
answer = {
    "answer": final_inputs | ANSWER_PROMPT | ChatOpenAI(),
    "docs": itemgetter("docs"),
}
# And now we put it all together!
final_chain = loaded_memory | standalone_question | retrieved_documents | answer

 

 

  메모리에는 이전 대화 내용이 저장되고 로드 됩니다. 질문에 대한 답변과 관련 문서를 반환합니다.

inputs = {"question": "where did harrison work?"}
result = final_chain.invoke(inputs)
result

'''
{'answer': AIMessage(content='Harrison was employed at Kensho.'),
 'docs': [Document(page_content='harrison worked at kensho')]}
'''

 

 

  ConversationBufferMemory는 자동으로 저장되지 않으며 사용자가 수동으로 저장해야 합니다. 나중에 개선된다고 합니다.

  memory.save_context(inputs, {"answer": result["answer"].content}) 는 메모리에 대화 상황을 저장하는 부분입니다. inputs는 현재 질문 및 대화 상황에 대한 정보를 포함하는 딕셔너리 입니다.

  result["answer"].content는 질문에 대한 모델의 답변 내용을 나타냅니다.

  이 정보는 나중에 메모리에서 로드하여 대화의 상태를 유지하는데 사용됩니다. 

  memory.load_memory_variables({})는 메모리에서 이전에 저장한 대화 상황을 로드합니다. 추가적인 사항 없이 반환하도록 빈 딕셔너리를 집어 넣었습니다.

# Note that the memory does not save automatically
# This will be improved in the future
# For now you need to save it yourself
memory.save_context(inputs, {"answer": result["answer"].content})

memory.load_memory_variables({})

'''{'history': [HumanMessage(content='where did harrison work?'),
  AIMessage(content='Harrison was employed at Kensho.')]}
'''

 

 

  새로운 질문을 기반으로 대화 체인을 실행시키게 되면 결과로 새로운 질문에 대한 답변과 관련 문서가 반환됩니다. 

inputs = {"question": "but where did he really work?"}
result = final_chain.invoke(inputs)
result

'''
{'answer': AIMessage(content='Harrison actually worked at Kensho.'),
 'docs': [Document(page_content='harrison worked at kensho')]}
'''

 

 

 

 


Multiple chains

 Basic Example

  Runnable은 여러 체인을 쉽게 결합할 수 있습니다. 첫번째 체인 chain1의 결과를 chain2의 필드 "city"의 값으로 사용할 수 있습니다.

from operator import itemgetter

from langchain.schema import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI

prompt1 = ChatPromptTemplate.from_template("what is the city {person} is from?")
prompt2 = ChatPromptTemplate.from_template(
    "what country is the city {city} in? respond in {language}"
)

model = ChatOpenAI()

chain1 = prompt1 | model | StrOutputParser()

chain2 = (
    {"city": chain1, "language": itemgetter("language")}
    | prompt2
    | model
    | StrOutputParser()
)

chain2.invoke({"person": "obama", "language": "korean"})

# '바락 오바마, 미국 44대 대통령이 태어난 도시인 홀리뉴루, 하와이는 미국에 위치해 있습니다.'

 

 

  더 복잡한 구조로써 사용도 가능합니다. 모델에 계속 인자를 넣어 프롬프트를 완성해 나가고 마지막으로 완성된 최종 프롬프트를 날려 응답을 얻어냅니다. 

from langchain_core.runnables import RunnablePassthrough

prompt1 = ChatPromptTemplate.from_template(
    "generate a {attribute} color. Return the name of the color and nothing else:"
)
prompt2 = ChatPromptTemplate.from_template(
    "what is a fruit of color: {color}. Return the name of the fruit and nothing else:"
)
prompt3 = ChatPromptTemplate.from_template(
    "what is a country with a flag that has the color: {color}. Return the name of the country and nothing else:"
)
prompt4 = ChatPromptTemplate.from_template(
    "What is the color of {fruit} and the flag of {country}?"
)

model_parser = model | StrOutputParser()

color_generator = (
    {"attribute": RunnablePassthrough()} | prompt1 | {"color": model_parser}
)
color_to_fruit = prompt2 | model_parser
color_to_country = prompt3 | model_parser
question_generator = (
    color_generator | {"fruit": color_to_fruit, "country": color_to_country} | prompt4
)

 

  warm 키워드를 던지면 question_generator에 들어가서 color_generator 체인을 탑니다. color_generator의 최종 결과물은 색이름을 추력합니다. 출력된 색이름은 "color"필드에 저장이 되어 있고, question_generator의 두번째 단계에서 color_to_fruit, color_to_country의 매개변수 color를 기반으로 fruit와 country 필드값을 얻게 됩니다.   마지막 단계인 prompt4에서 fruit, country에 필드값이 삽입되어 최종 프롬프트를 얻어냅니다.

question_generator.invoke("warm")

## ChatPromptValue(messages=[HumanMessage(content='What is the color of orange and the flag of Bhutan?')])

 

  최종 프롬프트를 일반 model에 넣어서 결과를 얻습니다.

prompt = question_generator.invoke("warm")
model.invoke(prompt)
## AIMessage(content='The color of a strawberry is typically red. The flag of China is predominantly red with a large yellow star in the top left corner and four smaller yellow stars surrounding it.')

 

 

Branching and Merging

  아래 그림에 나와있는 것 처럼 하나의 성분을 다른 2개 이상의 성분들에 의해 처리될 수도 있습니다. RunnableParallel을 사용하면 여러 성분들이 병렬로 입력을 처리할 수 있게 체인을 분할하거나 분기할 수 있습니다. 이후 다른 성분들의 결과와 결합하거나 병합되어 최종 응답을 합성할수 있습니다.

 

 

  기본 논쟁거리를 주면 주장과 반론을 들고 final_responder에서 ai에게 찬반 주장에 대해 최종 결과를 받아낸다.

planner = (
    ChatPromptTemplate.from_template("Generate an argument about: {input}")
    | ChatOpenAI()
    | StrOutputParser()
    | {"base_response": RunnablePassthrough()}
)

arguments_for = (
    ChatPromptTemplate.from_template(
        "List the pros or positive aspects of {base_response}"
    )
    | ChatOpenAI()
    | StrOutputParser()
)
arguments_against = (
    ChatPromptTemplate.from_template(
        "List the cons or negative aspects of {base_response}"
    )
    | ChatOpenAI()
    | StrOutputParser()
)

final_responder = (
    ChatPromptTemplate.from_messages(
        [
            ("ai", "{original_response}"),
            ("human", "Pros:\n{results_1}\n\nCons:\n{results_2}"),
            ("system", "Generate a final response given the critique"),
        ]
    )
    | ChatOpenAI()
    | StrOutputParser()
)

chain = (
    planner
    | {
        "results_1": arguments_for,
        "results_2": arguments_against,
        "original_response": itemgetter("base_response"),
    }
    | final_responder
)

chain.invoke({"input": "scrum"})

 

  결과가 꽤나 장황합니다. 그리고 길어서 그런지 추론시간에 꽤 걸립니다. 그래도 1분 이내이긴 합니다.

"While it's important to consider the potential downsides of Scrum, it's worth noting that many of these issues can be mitigated with proper implementation and team dynamics. The complexity of Scrum can be addressed by providing adequate training and support to team members. Lack of predictability can be managed by implementing effective estimation techniques and regularly reviewing and adjusting the project plan. Dependency on team collaboration can be addressed through team building and fostering a collaborative culture. \n\nThe overemphasis on short-term goals can be mitigated by ensuring that the project vision and long-term objectives are clearly communicated and understood by the team. Time-consuming meetings can be optimized by following Scrum rituals and setting clear guidelines for meeting structure and duration. \n\nWhile Scrum may not be suitable for all project types, it can still be adapted to fit various scenarios. For projects that require a more sequential approach, a hybrid model like Scrum-ban or Kanban can be considered. \n\nScope creep can be managed by implementing a robust change management process and maintaining a prioritized product backlog. The dependency on a dedicated Scrum Master can be addressed by ensuring that the Scrum Master is adequately trained and supported, and by fostering a culture of self-organization within the team.\n\nIn conclusion, while Scrum has its limitations, many of these concerns can be overcome with proper implementation, training, and adaptation to specific project needs. With the right approach and team dynamics, Scrum can still provide significant benefits and advantages in project management."

 

 

 

 

 


Adding memory

 

    대화를 기억하면서 채팅을 할 수 있는 챗봇을 구현합니다. 기본 프레임워크는 아래와 같습니다. 먼저 모델 설정이 있고, 이어서 ChatPromptTemplate으로 프롬프트 양식인 템플릿과 프롬프트 설정을 해줍니다. ConversationBufferMemory를 이용해서 버퍼 메모리를 만들어 줍니다. 초기에는 당연히 메모리가 비어있습니다.

from operator import itemgetter

from langchain.memory import ConversationBufferMemory
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.runnables import RunnableLambda, RunnablePassthrough
from langchain_openai import ChatOpenAI

model = ChatOpenAI()
prompt = ChatPromptTemplate.from_messages(
    [
        ("system", "You are a helpful chatbot"),
        MessagesPlaceholder(variable_name="history"),
        ("human", "{input}"),
    ]
)

memory = ConversationBufferMemory(return_messages=True)
memory.load_memory_variables({})


## {'history': []}

 

 

 

    RunnablePassthrough.assign 작업을 통해서 메모리에서 얻은 값을 history변수에 할당합니다. 그리고 memory.load_memory_variables 함수를 사용하며 history를 로드하는데 사용합니다. 결과는 itemgetter("history")를 사용해서 액세스합니다. 메모리에서 대화 기록을 효과적으로 로드하죠. 

chain = (
    RunnablePassthrough.assign(
        history=RunnableLambda(memory.load_memory_variables) | itemgetter("history")
    )
    | prompt
    | model
)

inputs = {"input": "hi im bob"}
response = chain.invoke(inputs)
response

## AIMessage(content='Hello Bob! How can I assist you today?')

 

 

  대화의 컨텍스트를 저장합니다. 이 때 두가지 인수가 필요합니다. inputs의 경우에는 이미 입력 했던 inputs의 값이 들어가고, output은 저장할 컨텍스트를 지정합니다. 챗봇의 응답을 저장하죠.

  컨텍스트를 로드했다면 메모리 변수를 로드해서 메모리가 저장되어 있는지 확인합니다.

memory.save_context(inputs, {"output": response.content})

memory.load_memory_variables({})

''' {'history': [HumanMessage(content='hi im bob'),
  AIMessage(content='Hello Bob! How can I assist you today?')]}
'''

 

  이전 기록에 답이 있는 질문을 던져 저장된 컨텍스트를 활용하는지 확인합니다.

inputs = {"input": "whats my name"}
response = chain.invoke(inputs)
response

## AIMessage(content='Your name is Bob, as you mentioned earlier. How can I help you, Bob?')

 

 

 

 

 

 

 

728x90
반응형

'NLP' 카테고리의 다른 글

reading LangChain docs (2)  (0) 2024.01.17
reading LangChain docs (1)  (0) 2024.01.16
BertSum 뉴스 추출 요약 모델  (0) 2022.09.15
트위터 데이터 수집 (a.k.a twitterscraper)  (0) 2020.02.05
비정형 데이터 - Doc2Vec  (0) 2019.12.19

댓글