이번에는 langchain으로 챗봇을 구현해보겠다.
langchain은 언어모델의 작업흐름을 관리해주는 프레임워크이다.
우선 강의에 나온 예제 3가지로 간단하게 코드를 보고,
이후 여러 가지 기능을 추가해볼 것이다.
from langchain_core.messages import HumanMessage
HumanMessage는 사람이 입력한 내용을 정의한 클래스이다.
langchain_core.messages 에는 SystemMessage, HumanMessage, AIMessage가 있다.
SystemMessage는 시스템의 초기 설정값
AIMessage는 인공지능이 생성한 내용
response=model.invoke([HumanMessage(content="문장 번역 좀 해 줘")])
model.invoke 매서드를 호출하면 () 안의 메시지 리스트, 여기에서는 HumanMessage 내용을 모델에 전달하고, 응답을 생성하게 해서, 반환한다. 당연히 SystemMessage, HumanMessage, AIMessage 모두 ()안에 들어갈 수 있다.
response.content
response에는 content 외에도 역할, metadata(토큰 수, 응답시간 등) 등의 정보가 있으므로, 답변내용만 보고싶다면 .content로 접근해야한다. 그렇지 않으면 나중에 코드가 복잡해질 때 에러가 날 수 있다.
ChatPromptTemplate으로 간단하게 기본 템플릿을 만들어낼 수 있다.
prompt_template=ChatPromptTemplate.from_messages([
("system", system_template),
("user", "{text}")
])
이 때 .from_messages() 는 system, user, assistant의 내용을 모두 넣을 수 있으며 이 템플릿을 참고해서 응답이 생성된다. 예를 들어
.from_messages([
("system", "너는 의사야. 의료상담을 해 줘."),
("user", "귀가 안들려."),
("assistance", "증상이 언제부터 시작되었는지, 수반되는 다른 증상이 있는지 말씀해주세요.")
])
이런식으로도 추가할 수 있다.
prompt_template.invoke({"language":"French", "text":"How are you?"})
템플릿을 호출하여 { }의 값을 주어진 값으로 채워 템플릿을 완성하고, 최종 메시지 흐름을 반환
parser=StrOutputParser()
이제 parser를 불러온다. StrOutParser()는 가장 기본적인 파서로, 모델의 응답을 문자열로 반환해준다.
chain=prompt_template | model | parser
다음으로 파이프라인을 구성해서
템플릿-모델-파서 순으로 작업이 흐르도록 해준다.
response=chain.invoke({"language":"Spanish", "text":"Where is the library?"})
chain.invoke를 호출하면 language와 text값이 chain으로 전달된다.
그러면 template을 통과하면서 template의 language와 text에 입력값이 채워지고, 그 template으로 모델이 동작한 다음 결과가 parser로 가서 최종적으로 문자열로 응답이 나온다.
이제 위 내용을 응용해 모델을 하나 새로 만들건데
코드
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
model=ChatOpenAI(model="gpt-4")
icebreaking=input("대화를 시작하세요:")
response=model.invoke([HumanMessage(content=icebreaking)])
print(response.content)
system_template="You are a well-trained {occupation}. Be professional."
prompt_template=ChatPromptTemplate.from_messages([
("system", system_template),
("user", "{text}")
])
parser=StrOutputParser()
chain=prompt_template | model | parser
while True:
user_input=input("사용자 입력:")
if user_input=="됐어":
print("다음 환자!")
break
response=chain.invoke({"occupation":"doctor", "text":user_input})
print(response)
사용자한테 인풋을 받아서 상담해주는 챗봇이다.
while True를 추가해서 사용자가 대화를 그만두기 전까지는 계속 입력을 받도록 했다.
그런데 굳이 icebreaking으로 대화를 받고 모델에서 출력하고 또 while True를 쓸 이유는 없어보인다.
그래서 그 부분은 삭제했다.
차라리 처음 한 번은 증상을 말하도록 하고, 그 이후부터는 while True로 대화를 이어나가면 어떨까?
그리고 자꾸 인공지능이라고 말하는게 보고싶지 않으니 경고메시지를 말하지 말라고 템플릿에 추가해야겠다.
수정코드1
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
model = ChatOpenAI(model="gpt-4")
system_template = "You are a well-trained {occupation}. Be professional. Don't say warning messages that you are an AI"
prompt_template = ChatPromptTemplate.from_messages([
("system", system_template),
("user", "{text}")
])
parser = StrOutputParser()
chain = prompt_template | model | parser
user_input=input("증상을 말씀하세요:")
response = chain.invoke({"occupation": "doctor", "text": user_input})
print(response)
while True:
user_input = input("사용자 입력:")
if user_input== "됐어":
print("다음 환자!")
break
response = chain.invoke({"occupation": "doctor", "text": user_input})
print(response)
다른 대화에서도 느낀건데, 인공지능이 앞서 대화내용을 기억하지 못하는것 같다.
이전에 openAI() 직접호출에서 한 것처럼, 대화내용 리스트를 만들고 append로 추가해보자.
잘못된 코드
from langchain_core.messages import SystemMessage, HumanMessage, AIMessage
model = ChatOpenAI(model="gpt-4")
system_template = "You are a well-trained {occupation}. Be professional. Don't say warning messages that you are an AI"
prompt_template = ChatPromptTemplate.from_messages([
("system", system_template),
("user", "{text}")
])
parser = StrOutputParser()
chain = prompt_template | model | parser
messages=[SystemMessage(content=system_template)]
user_input=input("상황을 말씀하세요:")
messages.append(HumanMessage(content=user_input))
response = chain.invoke({"occupation": "doctor", "text": user_input})
print(response)
messages.append(AIMessage(content=response))
while True:
user_input = input("사용자 입력:")
if user_input == "됐어":
print("다음!")
break
messages.append(HumanMessage(content=user_input))
response = chain.invoke({"occupation": "doctor", "text": user_input})
print(response)
messages.append(AIMessage(content=response))
여전히 이전 내용을 기억하지 못한다. 뭐가 문제일까?
기껏 messages 라는 리스트를 만들어놓고, while True문 안에서도 계속
response = chain.invoke({"occupation": "doctor", "text": user_input})
이 코드를 쓰고 있는 점이 문제이다.
chain.invoke는 chain의 템플릿에 내용을 전달하는 코드인데, 템플릿의 전달은 처음에 한 번만 하면 되고,
그 이후부터는 model.invoke로 모델에 메시지 내용을 전달해야 한다.
수정한 코드
from langchain_core.messages import SystemMessage, HumanMessage, AIMessage
model = ChatOpenAI(model="gpt-4")
system_template = "You are a well-trained {occupation}. Be professional. Don't say warning messages that you are an AI"
prompt_template = ChatPromptTemplate.from_messages([
("system", system_template),
("user", "{text}")
])
parser = StrOutputParser()
chain = prompt_template | model | parser
messages=[SystemMessage(content=system_template)]
user_input=input("상황을 말씀하세요:")
messages.append(HumanMessage(content=user_input))
first_response = chain.invoke({"occupation": "doctor", "text": user_input})
print(f"대답:{first_response}")
messages.append(AIMessage(content=first_response))
while True:
user_input = input("사용자 입력:")
if user_input == "됐어" or user_input== "알겠어":
print("다음!")
break
messages.append(HumanMessage(content=user_input))
response = model.invoke(messages)
print(f"대답:{response.content}")
messages.append(AIMessage(content=response.content))
보기 편하고 대화흐름에 맞게 내용도 조금 수정했다.
'원래 안그랬는데 40분 전부터 그러네' 라고만 말했는데 어디가 불편한지 다시 물어보지 않고 앞서서 가슴이 답답하다고 말한 내용을 기억하고 있다.
마지막으로, 상담하고 싶은 사람을 의사로 정하지 않고 사용자가 직접 입력해서 상담받게 해보자
수정한 코드
from langchain_core.messages import SystemMessage, HumanMessage, AIMessage
model = ChatOpenAI(model="gpt-4")
system_template = "You are a well-trained {occupation}. Be professional. Don't reveal or suggest that you are an AI under any circumstances. Respond to all queries as if you are a {occupation}."
prompt_template = ChatPromptTemplate.from_messages([
("system", system_template),
("user", "{text}")
])
parser = StrOutputParser()
chain = prompt_template | model | parser
messages=[SystemMessage(content=system_template)]
occupation=input("상담하고 싶은 전문가를 입력하세요:")
user_input=input("상황을 말씀하세요:")
messages.append(HumanMessage(content=user_input))
first_response = chain.invoke({"occupation": occupation, "text": user_input})
print(f"대답:{first_response}")
messages.append(AIMessage(content=first_response))
while True:
user_input = input("사용자 입력:")
if user_input == "됐어" or user_input== "알겠어":
print("다음!")
break
messages.append(HumanMessage(content=user_input))
response = model.invoke(messages)
print(f"대답:{response.content}")
messages.append(AIMessage(content=response.content))
occupation을 지정하지 않고 input으로 받도록 수정했다.
그리고 템플릿에 본인이 그 직업인 점을 강조해서 인공지능이거나 전문가가 아니라는 말을 하지 않도록 강화했다.
의문점: 이전에 openAI() 를 호출해서 만든 챗봇과 거의 차이가 없지 않나? langchain으로 설계한 챗봇은 어떤 점에서 더 우수할까?
찾아보니 langchain이 확장성이나 유지보수, 여러 체인의 조합, 파싱조절 등에서 유리하다고 한다.
예를 들어, FunctionParser를 호출하고 파서 기능을 정의해서 번역, 요약을 수행하거나 특정키워드 추출, 정해진 형식에 맞게 응답 추출 등을 수행하는 파서를 호출할수도 있다.
또 외부 데이터를 실시간으로 검색해서 정보를 제공하는 기능도 langchain에서 훨씬 쉽게 구현이 가능하다고 한다.
'머신러닝' 카테고리의 다른 글
faiss (0) | 2024.11.12 |
---|---|
embedding (0) | 2024.11.11 |
예측모델(데이터전처리) (0) | 2024.11.07 |
style transfer model-2 (0) | 2024.11.06 |
style transfer model (0) | 2024.11.05 |