ChatGPT and LangChain: The Complete Developer's Masterclass 강좌의 일부를 요약한 내용이다.
샘플 프로그램
만들어볼 프로그램은 아래와 같다.
위의 시나리오는 CLI로 task와 language를 input으로 받고 파싱을 한 다음 prompt를 생성한 다음 OpenAI에 요청을 한다. 그리고 우리가 원하는 결과(프로그램)를 받는 것이다. 또한 OpenAI의 응답을 Bard에게 프로그램에 버그가 있는지 확인요청을 다시 하고 그 결과를 받는다.
LangChain의 두 가지 목표가 있다.
- 텍스트 생성 파이프라인의 단계를 자동화하는 도구를 제공
- 도구를 연결하기 쉽게 한다
OpenAI는 Llama나 Claude로 변경할 수도 있어야 한다. 즉, 다른 LLM으로 쉽게 바꿀 수 있어야 한다는 뜻이다.
Chain은 무엇인가?
- Chain은 LangChain에서 제공되는 python 클래스이다.
- 재사용 가능한 텍스트 생성 파이프라인을 만들기 위해 Chain을 사용한다.
- Chain은 복잡한 파이프라인을 만들기 위해 결합될 수 있다.
- Chain은 PromptTemplate과 LLM을 연결한다.
PromptTemplate
- LLM에 보내질 최종 prompt를 만든다.
- prompt를 만들 때 필요한 변수를 선언해야 한다.
- 이 경우는
language
와task
가 필요한 변수이다.
Language Model
- 텍스트 생성 파이프라인에 필요한 LLM이다.
- ChatGPT, Bard와 같이 텍스트를 만드는 어떤 것도 될 수 있다.
위의 시나리오에서는 아래 그림과 같이 2개의 체인이 연결되는 형태를 띄게 된다.
Chain A의 Output에 출력되는 language, code 변수값은 Chain B의 Input으로 대입을 해준다.
첫 번째 체인 생성
코드를 작성해보자. 우선 Pipfile에 아래 내용을 추가한다.
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"
[packages]
langchain = "==0.1.16"
openai = "==1.21.2"
langchain-openai = "==0.1.3"
[dev-packages]
[requires]
python_version = "3.12"
그리고 pipenv install을 통해 패키지를 설치하자.
main.py를 만들어 코드를 작성하자.
from langchain_openai import OpenAI
from langchain.prompts import PromptTemplate
from langchain.chains import LLMChain
api_key = "sk-xxx" # api key
llm = OpenAI(
openai_api_key=api_key,
)
# code template을 만든다.
code_prompt = PromptTemplate(
# task, language는 변수로 사용되고 매개변수의 값으로 치환이 된다.
template="Write a very short {language} function that will {task}",
# input_variables로 변수를 나타낸다.
input_variables=["language", "task"],
)
# chain을 만드는 데 code_prompt를 사용한다.
code_chain = LLMChain(
llm=llm,
prompt=code_prompt
)
result = code_chain.invoke({
"language": "python",
"task": "return a list of numbers"
})
print(result)
이렇게 하고 실행해보면 아래와 같이 출력된다.
{
'language': 'python',
'task': 'return a list of numbers',
'text': '\n\ndef get_numbers():\n return [1, 2, 3, 4, 5]'
}
language와 task는 input 값이고 출력값은 text로 나타내어 진다.
매개변수 주입
위의 코드에서 language와 task의 input 값은 소스코드에 하드코딩해서 넣었다. 이를 주입받을 수 있도록 해보자.
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("--task", default="Return a list of numbers")
parser.add_argument("--language", default="python")
args = parser.parse_args()
...
result = code_chain.invoke({
"language": args.language,
"task": args.task
})
print(result['text'])
매개변수를 받기 위해서는 argparse를 사용한다. task와 language를 주입받게 하고 chain을 실행할 때 arg.language, arg.task로 변경하자.
그리고 실행할 때 매개변수를 넘기자.
python main.py --language javascript --task 'print hello'
실행 결과는 아래와 같이 출력된다.
function sayHello() {
console.log("Hello");
}
만일 매개변수를 넘기지 않으면 default로 지정된 문자열이 동작한다. 즉, python에 Return a list of numbers 의 task가 실행된다.
API Key 안전하게 보관
위의 소스코드에는 api_key = "sk-xxx"가 코드 내에 하드코딩이 되어 있다. 이를 git에 커밋을 하는 경우 보안상 문제가 될 수 있다. 그래서 api key를 안전하게 보관하는 방법을 알아본다.
위의 OpenAI() 함수에서는 OPENAI_API_KEY
라는 이름의 변수를 우선 찾는다. 그래서 변수로 선언하고 사용하게 할 것이다.
환경변수를 읽어오는 방법은 여러가지가 있지만 여기서는 dotenv를 사용한다.
.env 파일을 만들고 OPENAI_API_KEY를 추가하자. (.env 파일을 git에 추가하면 절대로 안된다)
OPENAI_API_KEY=sk-xxx
또한 dotenv를 설치하자.
pipenv install python-dotenv
를 실행한다.
그리고 main.py에 아래 로직을 추가한다.
from dotenv import load_dotenv
load_dotenv()
llm = OpenAI() # 기존의 api_key을 지운다.
두 번째 체인 생성
이 시나리오에서는 체인을 2개 만든다. 하나는 코드 생성 체인, 다른 하나는 코드 검증 체인이다. 위에서 코드 생성 체인을 만들었고 이제 코드 검증 체인을 만들어보자. 코드 검증 체인은 첫 번째와 동일한데 코드 생성 체인에서 나온 결과(output)를 코드 검증 체인의 Input으로 넣어준다. 즉 코드 생성 체인의 Output인 language, code를 코드 검증 체인의 Input으로 넣어준다는 것이다.
code_chain = LLMChain(
llm=llm,
prompt=code_prompt
)
...
print(result['text'])
위에서 code_chain의 실행 결과가 text로 넘어왔다. 여기서 두번째 체인에 넘겨줄 값을 code로 변경을 해야 하므로 Output의 key값을 변경을 해보자. 변경하는 방법은 간단한다.
code_chain = LLMChain(
llm=llm,
prompt=code_prompt,
output_key="code"
)
...
print(result['code'])
output_key를 넣어주면 해당 키로 output을 넘겨준다.
이제 검증 체인을 만들어보자.
test_prompt = PromptTemplate(
input_variables=["language", "code"],
template="Write a test for the following {language} function:\n{code}"
)
...
test_chain = LLMChain(
llm=llm,
prompt=test_prompt,
output_key="test"
)
첫 번째 체인과 동일하게 PromptTemplate을 만들고, LLMChain을 만든다.
첫 번째 체인의 Output을 두 번째 체인의 Input으로 넘겨준다. 이를 Sequential Action
이라고 한다. 여러 개의 체인을 순차적으로 실행한다는 것을 나타낸다.
SequentialChain을 사용하는 방법은 아래와 같다.
from langchain.chains import LLMChain, SequentialChain # SequentialChain 추가
...
chain = SequentialChain(
chains=[code_chain, test_chain],
input_variables=["task", "language"],
output_variables=["test", "code"]
)
result = chain.invoke({
"language": args.language,
"task": args.task
})
print(result)