https://www.yes24.com/Product/Goods/124922985
책 내용 중 기록하고 싶은 내용을 작성합니다.
1. 챗지피티와 랭체인
기존에는 용도별로 각각의 모델을 준비해야 했지만, GPT는 엄청나게 많은 다양한 종류의 텍스트로 학습해 다양한 작업에 댕응할 수 있는 언어 모델이다.
언어 모델을 크게 두가지로 분류됨
- Chat : 대화형 상호작용 생성에 특화된 모델
- Complete : 주어진 텍스트에 이어 텍스트를 생성
모델 별 허용 컨텍스트
- gpt-3.5-turbo-16k : 16k의 컨텍스트 길이를 처리할 수 있음
- suffix가 없으면 일반적으로 4k
gpt-3.5-turbo-0613 와 같이 날짜가 suffix로 있는 경우는 특정 버전으로 고정된 것
API 요금은 송신 입력 토큰과 수신 출력 토큰에 모두에 대해 발생함
http://poe.com/ 에서 다양한 언어 모델 사용해보기
RAG(Retrieval-Augmented Generation)
- 언어 모델이 알지 못하는 정보에 대해 대답하게 함
ReAct(Reasoning and Acting)
- 추론과 행동을 언어 모델 스스로 판단해 인터넷 검색이나 파일 저장등을 자율적으로 수행
-> 얘네도 랭체은을 통해 쉽게 구현할 수 있음
랭체인에 준비된 6개 모듈
- Model I/O - 언어 모델을 쉽게 다룰 수 있다.
- Retrieval - 알 수 없는 데이터를 다룰 수 있다.
- Memory - 과거의 대화를 장-단기전으로 기억한다.
- Chains - 여러 프로세스를 통합한다.
- Agents - 자율적으로 외부와 상호작용해 언어 모델을 한계를 뛰어넘는다.
- Callbacks - 다양한 이벤트 발생 시 처리한다.
finish_reason
- Chat 모델의 결과 필드 중 하나
- 모델이 응답을 종료한 이유를 나타낸다.
- stop은 자연스러운 끝을 찾은 경우
- length는 max_tokens에 도달한 경우
2. Model I/O - 언어 모델을 다루기 쉽게 만들기
언어 모델의 활용한 애플리케이션은 좋은 결과를 얻기 위해 시행착오를 거쳐야 함
ex) 아이폰8 출시일을 알려줘라고 했을 때 2017/09/22 로 출력할지, 2017년 9월 22일로 출력할지 알 수 없다. 하지만 yyyy-mm/dd 라는 형식으로 알려주세요 라고 입력하면 원하는 결과를 출력할 수 있다.
Model I/O를 구성하는 3개의 서브 모듈
- Language Models : 다양한 언어 모델을 동일한 인터페이스에서 호출
- Prompts : 언어 모델을 호출하기 위한 프롬프트를 구축하는 데 유용한 기능 제공
- Output parsers : 언어 모델을 통해 받은 결과를 분석해 사용하기 좋게 변환
과거의 응답을 바탕으로 답변하게 하려면 매번 소스코드를 다시 작성해야 하므로 매우 번거로움 -> 상호 작용을 지원하기 위해 Memory 모듈이 준비되어 있음
Language Models에서 크게 두가지 모듈을 지원함
- Chat Models
- LLMs (Complete 모델)
Language Models의 편리한 기능
- 캐싱
langchain.llm_cache = InMemoryCache()
와 같이 캐시를 지정하면 동일 입력에 대한 캐싱 로직 동작- InMemory는 프로세스에서 동작하므로 휘발됨
- SQLite도 손쉽게 가능
- 스트리밍
- CallBacks 모듈을 통해 동작함 (ChatGPT도 결과를 지속적으로 보여주니 스트리밍을 하고 있다고 볼 수 있을거 같음)
프롬프트 엔지니어링
- 프롬프트를 최적화하면 단순한 명령어로는 어려웠던 작업을 수행할 수 있게 되거나 더 나은 결과를 얻을 수 있음
- Templates 모듈이 프롬프트 엔지니어링을 도움
퓨샷 프롬프트
- 효과가 높다고 알려짐 프롬프트 엔지니어링 기법중 하나
- Steps
- 언어 모델을 수행할 작업을 간결하게 지시 (Prompts)
- 작업의 입력과 출력의 예시를 제시 (Example)
langchain.prompts.FewShotPromptTemplate
Output Parser
- 모델의 출력 형식을 지정
- 랭체인 제공 Parser도 여러가지가 있지만 사용자 정의 형식도 정의할 수 있음 (Pydantic)
PydanticOutputParser
- 출력 규칙을 유연하게 정의할 수 있다.
- 파싱된 데이터의 무결성을 보장할 수 있다.
- 파싱 결과를 쉽게 처리할 수 있다.
@validator를 통해 검증을 할 수도 있음
-> 검증에 실패하는 경우 에러 발생
-> 이때 OutputFixingParser를 사용해 재시도할 수 있게 지원한다. (링크)
-> max_retries 필드로 몇번 재시도할 지 선택할 수 있음
3. Retrieval - 알지 못하는 데이터를 다루기
Retrieval은 언어 모델이 학습하지 않은 개념이나 정보를 처리할 수 있게 하는 모듈
GPT
- 학습한 정보 -> 답변 생성
- 학습하지 않은 정보 -> 답변 생성 X
해결책이 Retrieval
답변에 필요한 문장을 찾는 방법이 중요함
- 검색이 어려운 정보원에 대응하기 위해 텍스트를 벡터화해 검색을 가능하게 함
텍스트를 벡터화하면 벡터간의 유사도를 구할 수 있음 (cosine 유사도)
어떻게 벡터화하는지가 중요함 -> 여기선 OpenAI의 text-embedding-ada-002 를 사용
from langchain.embeddings import OpenAIEmbeddings
from numpy import dot
from numpy.linalg import norm
embeddings = OpenAIEmbeddings(model="text-embedding-ada-002") # 임베딩 정의
query_vector = embeddings.embed_query("비행 자동차의 최대 속도는?")
document_1_vector = embeddings.embed_query("비행 자동차의 최고 속도는 시속 150km입니다.")
document_2_vector = embeddings.embed_query("내 이름은 철수입니다.")
# query_vector와 서로다른 document vector 간의 유사도 검색
cos_sim_1 = dot(query_vector, document_1_vector) / (norm(query_vector) * norm(document_1_vector))
cos_sim_2 = dot(query_vector, document_2_vector) / (norm(query_vector) * norm(document_2_vector))
print(f"1문장 유사도: {cos_sim_1:.2f}, 2문장 유사도: {cos_sim_2:.2f}")
# 1문장 유사도: 0.92, 2문장 유사도: 0.75
RAG를 수행하는 절차
- 사전 준비: 여기서 사전은 dictionary를 의미
문서에서 텍스트를 추출한 뒤 벡터화해서 DB에 저장해야 함 - 검색 및 프롬프트 구축
DB에서 유사학 벡터를 검색해서 가져온 뒤 질문과 조합해서 프롬프트 구성
사전준비
사전준비의 단계
- 텍스트 추출 (ex, pdf로 부터 텍스트를 가져옴)
- 텍스트 분할 (모델이 처리할 수 있는 텍스트 길이의 한계가 있음)
- 텍스트의 벡터화 (아까 했던 것처럼 Embedding 활용)
- 텍스트와 벡터를 벡터 DB에 저장 (Chroma DB등 벡터를 저장하기에 특화되어 있는 DB)
검색 및 프롬프트 구축
- 사용자의 입력을 벡터화
- 해당 벡터를 DB에서 검색해 문장 가져옴
- 검색된 문장과 질문을 조합해서 프롬프트 작성
- 언어 모델에 입력
임베딩 정의하고 벡터 DB에 넣는 예시
embeddings = OpenAIEmbeddings(model="text-embedding-ada-002") # 임베딩 정의
database = Chroma(persist_directory="data", embedding_function=embeddings) # DB 정의
database.add_documents(documents) # documents를 임베팅한 뒤 DB에 넣음
그리고 이렇게 준비된 DB에 유사도 검색을 하는 예시
documents = database.similarity_search("비행 자동차의 최고 속도는?")
챗봇을 쉽게 만들기 위한 chainlit이라는 라이브러리가 있음
RetrievalQA 모듈을 사용하면 RAG를 위해 반복되는 작업을 단순화할 수 있음
4. Memory - 과거의 대화를 장, 단기 기억하기
언어 모델 자체는 Stateless 하다. State를 전달해줘야 한다.
이때 도움이 되는 것이 Memory 모듈이다.
history를 전달하기 위해 다음과 같은 과정이 필요하다
- 메모리 내용을 로드
- 메모리에서 메시지 가져옴
- 메시지에 사용자 메시지 추가
- 언어 모델 호출
- 결과를 메모리에 저장
랭체인에선 이러한 작동을 쉽게 구현할 수 있는 ConversationChain을 제공한다.
chat = ChatOpenAI(model=<채팅 모델>) # chat을 위한 모델 불러
memory = ConversationBufferMemory()
chain = ConversationChain(memory=memory, llm=chat) # memory와 llm을 chaining
result = chain(message) # 사용자 메시지를 chain에 넣음
위 예시는 버퍼 메모리를 사용해서 프로세스가 죽으면 memory에 있는 대화들은 사라진다.
대화 기록을 저장하기 위한 DB
이 책에선 Redis를 사용한다. Redis를 쉽게 사용하기 위한 방법은 간단히 포스팅해두었다.
Langchain에서 제공하는 RedisChatMessageHistory 모듈을 사용하면 Redis에 대화를 손쉽게 저장 및 관리할 수 있다.
session_id 를 argument로 넣으면 f"message_store:{session}" 와 같이 Redis에 Key가 생성되는 걸 확인했다.
대화가 너무 길어지면 언어 모델을 호출할 수 없음 대응법
- ConversationBufferWindowMemory - 오래된 메시지 삭제
- ConversationSummaryMemory - 대화 요약함 (llm을 인자로 넣어야 함)
5. Chains - 여러 프로세스를 통합
일련의 처리를 하나의 묶음으로 처리할 수 있는 모듈
(앞서 사용했던 ConversationChain도 Chain 중 하나)
여러개의 체인을 순서대로 실행할 수도 있음
특정 URL에 접속해서 정보를 가져와 그 정보를 바탕으로 답변을 생성할 수 있는 LLMRequestsChain도 있음
언어 모델한테 복수개의 작업을 실행 시키면 그 결과가 안정적이지 못할 수 있다. 이때 SimpleSequentialChain을 사용해 작업을 분할해서 순서대로 실행하도록 할 수 있다.