본문 바로가기
AI/LLM

『Hey, 파이썬! 생성형 AI 활용 앱 만들어 줘』 Part 2. 생성형 AI를 활용한 인공지능 챗봇 제작

by Night Fury 2024. 10. 6.
반응형
김한호, 최태온, 윤택한, 『Hey, 파이썬! 생성형 AI 활용 앱 만들어 줘』, 성안당(2024), p142-333.

 

  • GPT Playground: 프롬프트를 쉽게 테스트하고, API 작동 방식을 익힐 수 있는 웹 기반 서비스
  • getpass.getpass(): 사용자의 입력을 받을 때까지 기다렸다가 사용자의 입력이 완료되면 그 값을 변수에 대입하는 기능
  • OpenAI.chat.completions.create()
    • max tokens: chatGPT가 최대로 답변할 수 있는 토큰 수 (default: 무한)
    • temperature: 답변에 창의적 일지, 정확할지 결정, 값이 높을수록 창의적 (0~2, default: 1)
    • top_p: 토큰의 확률 분포 제한, 응답의 다양성을 제어 (0~1, default: 1)
    • presence_penalty: 이미 나온 내용을 반복하는 것에 대한 페널티 부여 (-2~2, default: 0)
    • frequency_penalty: 자주 나타나는 단어나 구절을 반복하는 것에 대한 페널티 부여 (-2~2, default: 0)
    • n: 생성할 응답의 개수 (default: 1)
    • stop: 특정 문자열이나 문자열 목록을 만나면 응답 중단 (default: null)

 

그라디오(Gradio)

  • 사용자가 UI를 빠르게 제작하여 ML or API 등을 사용할 수 있게 해주는 파이썬 오픈소스 패키지
  • gradio.Interface(): 파이썬으로 작성한 함수를 미리 설정된 UI와 함께 제공하는 역할
    • fn: 그라디오에서 매핑된 함수명 (호출할 함수명)
    • inputs: input에 사용할 컴포넌트
    • outputs: output에 사용할 컴포넌트

 

  • gradio.Blocks(): 유연하게 레이아웃을 구성하는 데 사용
  • gradio.Tab(): 레이아웃을 탭으로 구성할 때 사용
  • gradio.Row(): 앱 안에서 수평으로 컴포넌트를 배치할 때 사용
  • gradio.Column(): 앱 안에서 수직으로 컴포넌트를 배치할 때 사용
  • gradio.ChatInterface(): 챗봇을 구현할 때 사용
    • fn: 버튼 클릭 시, 호출되는 함수
    • textbox: 대화 입력 창 설정
    • title: 챗봇 제목
    • description: 챗봇 설명
    • theme: 테마
    • retry_btn: 마지막에 물어본 대화 다시하기 설정
    • undo_btn: 마지막 대화 삭제
    • clear_btn: 대화 전체 삭제
    • additional_inputs: 추가 블록 정의

  • gradio.Group(): 레이아웃을 구성할 때나 컴포넌트들을 여백 없이 결합할 때 사용
  • 컴포넌트
    • gradio.Text(), gradio.Textbox(): 텍스트 
    • gradio.Dataframe(): pandas dataframe 
    • gradio.Image(): 이미지 
    • gradio.Video(): 비디오 
    • gradio.Audio(): 오디오 
    • gradio.Checkbox(): 체크 박스
    • gradio.CheckboxGroup(): select box
    • gradio.Slider(): 슬라이더 바 (min~max)
    • gradio.Dropdown(): dropdown menu
    • gradio.Button(): 버튼을 클릭할 때 특정 이벤트를 수행할 수 있음
    • gradio.ClearButton(): 내용 초기화 버튼

 

Bot 만들기

1. 상담봇

from openai import OpenAI
import gradio as gr

client = OpenAI(api_key="")

# chat + response
def counseling_bot_chat(message, chat_history):
    if message == "":
        return "", chat_history
    else:
        completion = client.chat.completions.create(
            model="gpt-3.5-turbo",
            messages=[
              {"role": "system", "content": "당신은 헤이마트의 상담원입니다. 마트 상품과 관련되지 않은 질문에는 정중히 거절하세요."},
              {"role": "user", "content": message}
            ])
        chat_history.append([message, completion.choices[0].message.content])

        return "", chat_history

# chat history undo
def counseling_bot_undo(chat_history):
    if len(chat_history) > 1:
        chat_history.pop()

    return chat_history

# chat reset
def counseling_bot_reset(chat_history):
    chat_history = [[None, "안녕하세요, 헤이마트입니다. 상담을 도와드리겠습니다."]]

    return chat_history


# Layout
with gr.Blocks(theme=gr.themes.Default()) as app:
    with gr.Tab("상담봇"):
        gr.Markdown(
            value="""
            # <center>상담봇</center>
            <center>헤이마트 상담봇입니다. 마트에서 판매하는 상품과 관련된 질문에 답변드립니다.</center>
            """
        )

        cb_chatbot = gr.Chatbot(
            value = [[None, "안녕하세요, 헤이마트입니다. 상담을 도와드리겠습니다"]], # 사용자 입력 데이터, 챗봇 입력 데이터
            show_label=False # 채팅 레이아웃 좌측 상단의 작은 Chatbot label 제거
        )

        with gr.Row():
            cb_user_input = gr.Text(
                lines=1, # 화면에 표시할 입력칸의 행 개수
                placeholder="입력 창", # 입력 칸에 예시로 표시할 텍스트
                container=False, # 텍스트 박스의 테두리 표시 여부
                scale=9  # 같은 레이아웃 영역 안의 컴포넌트들 사이에서 차지할 공간의 비중 (e.g. 9 > gr.Row() 영역 내 컴포넌트는 9:1 비중)
            )

            cb_send_btn = gr.Button(
                value="보내기", # 버튼에 들어갈 글자의 값
                scale=1,
                variant="primary", # gr.Blocks()에서 사전 정의된 테마의 설정값을 참조하여 버튼의 스타일을 변경 (primary: 주황, secondary: 회색, neutral: 흰색, stop: 핑크)
                icon="https://cdn-icons-png.flaticon.com/128/12439/12439334.png" # 버튼의 텍스트 앞에 이미지
            )

        with gr.Row():
            # create button, event listener
            gr.Button(value="↩ 되돌리기").click(fn=counseling_bot_undo, inputs=cb_chatbot, outputs=cb_chatbot)
            gr.Button(value="🔄 초기화").click(fn=counseling_bot_reset, inputs=cb_chatbot, outputs=cb_chatbot)

            # define event
            cb_send_btn.click(fn=counseling_bot_chat, inputs=[cb_user_input, cb_chatbot], outputs=[cb_user_input, cb_chatbot]) # 보내기 버튼 클릭 이벤트 정의
            cb_user_input.submit(fn=counseling_bot_chat, inputs=[cb_user_input, cb_chatbot], outputs=[cb_user_input, cb_chatbot]) # 입력 창에 글을 쓴 후 키보드 enter를 눌렀을 때 발생할 이벤트 정의
        
        pass
    with gr.Tab("번역봇"):
        pass
    with gr.Tab("소설봇"):
        pass

app.launch()​

구현 결과

 

2. 번역봇

import gradio as gr
from openai import OpenAI

client = OpenAI(api_key="")

def translate_bot(output_conditions, output_language, input_text):
    if input_text == "":
        return ""
    else:
        if output_conditions != "":
            output_conditions == "번역할 때의 조건은 다음과 같습니다. " + output_conditions
            
        completion = client.chat.completions.create(
            model="gpt-3.5-turbo",
            messages=[
                {"role": "system", "content": "당신은 번역가입니다. 입력한 언어를 다른 설명없이 곧바로 {0}로 번역해서 알려 주세요. 번역이 불가능한 언어라면 번역이 불가능하다고 말한 후 그 이유를 설명해 주세요. {1}".format(output_language, output_conditions)},
                {"role": "user", "content": input_text}
            ])
        
        return completion.choices[0].message.content
    
#레이아웃
with gr.Blocks(theme=gr.themes.Default()) as app:
    with gr.Tab("번역봇"):
        gr.Markdown(
            value="""
            # <center>번역봇</center>
            <center>다국어 번역봇입니다.</center>
            """
        )

        with gr.Row():
            tb_output_conditions = gr.Text(
                label="번역 조건",
                placeholder="예시: 자연스럽게",
                lines=1,
                max_lines=3 # 최대 입력할 수 있는 줄 개수
            )

            tb_output_language = gr.Dropdown(
                label="출력 언어",
                choices=["한국어", "영어", "일본어", "중국어"], # dropdown list 설정
                value="한국어", # default value
                allow_custom_value=True, # 번역 가능한 국가 리스트 외의 국가도 직접 설정 가능하도록 설정
                interactive=True # 상호 작용 활성화
            )

        tb_submit = gr.Button(
            value="번역하기",
            variant="stop"
        )

        with gr.Row():
            tb_input_text = gr.Text(
                placeholder="번역할 내용을 적어 주세요.",
                lines=10,
                max_lines=20, # 유동적인 UI (10~20 lines)
                show_copy_button=True,
                label=""
            )
            
            tb_output_text = gr.Text(
                lines=10,
                max_lines=20, # 유동적인 UI (10~20 lines)
                show_copy_button=True,
                label="",
                interactive=False # 번역된 내용이 편집되지 않도록 설정
            )
            
        # event listener
        tb_submit.click(
            fn=translate_bot,
            inputs=[tb_output_conditions, tb_output_language, tb_input_text],
            outputs=tb_output_text
        )

# Execution
app.launch()

구현 결과

 

3. 소설봇

import gradio as gr
from openai import OpenAI

client = OpenAI(api_key="")

def novel_bot(model, temperature, detail):
    completion = client.chat.completions.create(
        model=model,
        temperature=temperature,
        messages=[
            {"role": "system", "content": "당신은 소설가입니다. 요청하는 조건에 맞춰 소설을 작성해 주세요."},
            {"role": "user", "content": detail}
        ]
    )
    
    return completion.choices[0].message.content
    
with gr.Blocks(theme=gr.themes.Default()) as app:
    with gr.Tab("소설봇"):
        gr.Markdown(
            value="""
            # <center>소설봇</center>
            <center>소설을 생성해주는 봇입니다.</center>
            """
        )
        
        with gr.Accordion(label="사용자 설정"): # 접었다 폈다 할 수 있는 기능
            with gr.Row():
                with gr.Column(scale=1):
                    nb_model = gr.Dropdown(
                        label="모델 선택",
                        choices=["gpt-3.5-turbo", "gpt-3.5.turbo-16k", "gpt-4", "gpt-4-32k", "gpt-4-1106-preview"],
                        value="gpt-4-1106-preview",
                        interactive=True
                    )
                    
                    nb_temperature = gr.Slider(
                        label="창의성",
                        info="숫자가 높을 수록 창의적",
                        minimum=0,
                        maximum=2,
                        step=0.1,
                        value=1,
                        interactive=True
                    )

                nb_detail = gr.Text(
                    container=False, # container를 없애서 공간을 확보
                    placeholder="소설의 세부적인 설정을 작성합니다.",
                    lines=8,
                    scale=4
                )
        
        nb_submit = gr.Button(
            value="생성하기",
            variant="stop"
        )
        
        nb_output = gr.Text(
            label="",
            placeholder="이곳에 소설의 내용이 출력됩니다.",
            lines=10,
            max_lines=200,
            show_copy_button=True
        )
        
        # event listener
        nb_submit.click(
            fn=novel_bot,
            inputs=[nb_model, nb_temperature, nb_detail],
            outputs=nb_output
        )

    
# Execution
app.launch()

구현 결과

 

 

LangChain

LLM과 외부의 도구(web site, pdf reader, etc)를 체인으로 엮은 것처럼 결합해 주는 SDK의 한 종류

  • langchain.callbacks.streaming_stdout.StreamingStdOutCallbackHandler: 실시간으로 타이핑하듯이 답변이 나옴
  • langchain.prompts.PromptTemplate: 프롬프트 템플릿, 생성할 때, {}를 이용하여 매개변수 생성 가능
  • langchain.prompts.ChatPromptTemplate: Chat LLM 모델에 사용되는 SystemMessage, HumanMessage 등의 프롬프트를 생성하는 데 사용
  • Few shot: 언어 모델을 사용하여 결과값을결괏값을 추론할 때 추론된 결괏값을 원하는 형태로 나타나게 하는 것
    • langchain.prompts.few_shot.FewShotPromptTemplate
  • RAG(Retreival Augmented Generation): 외부 소스에서 검색하거나 가져온 정보를 LLM 모델의 인풋으로 적용하여 정확하고 맥락에 맞는 답을 할 수 있도록 LLM의 능력을 보완해 주는 역할
    • document loading
      • text file: langchain.document_loaders.TextLoader
      • csv: langchain.document_loaders.csv_loader.CSVLoader
      • pdf: langchain.document_loaders.PyPDFLoader
    • document transformers: 문서 변환기
      • langchain.text_splitter.RecursiveCharacterTextSplitter: 설정한 사이즈에 맞게 chunk로 분할
      • langchain.embeddings.OpenAIEmbeddings: openAPI에서 제공되는 embedding
      • langchain.vectorstores.Chroma: Chroma Vector DB
  • Agent: LLM에서 Tool을 사용할 순서를 결정하는 역할
    • 최종 답변에 도달할 때까지 아래 과정을 반복
      • Input(작업 할당) ➡️ Action(툴 결정) ➡️ Observation(툴 출력 결과 확인) ➡️ Thought(최종 답변을 얻기 위해 작업 할당)
    • LangChain agent type
      • Zero-shot ReAct: 작업과 도구 설명을 보고 사용할 도구 결정
      • Structured input ReAct: input이 여러 개인 툴을 사용할 때
      • Conversational: 대화 + ReAct로 대화를 저장하기 위한 메모리 필요
      • Self-ask with search: 인터넷 검색 후 답변하는 에이전트, 검색 툴 필요
      • ReAct document store: 문서 저장소 + 리액트, 검색 툴 필요
    • LangChain tool: https://python.langchain.com/docs/integrations/tools/
  • Chain: 모듈을 체인으로 연결할 수 있는 인터페이스를 제공하여 모듈을 호출할 수 있도록 함
    • langchain.chains.SimpleSequentialChain
  • Memory: 대화를 제대로 이어서 하려면 기존의 대화를 기억해야 함
    • langchain.memory.ConversationBufferMemory: 대화 내용을 그대로 저장, 메모리에 직접 데이터를 입력할 수 있음
    • langchain.memory.ConversationBufferWindowMemory: 지정한 개수만큼의 대화만 기억
    • langchain.memory.ConversationTokenBufferMemory: 대화를 기억할 때 토큰의 수로 기억
    • langchain.memory.ConversationSummaryBufferMemory: 이전의 대화 내용을 LLM을 이용해 요약해서 기억
반응형

댓글