From 39986c492bee3862d46260cda07178414d981a27 Mon Sep 17 00:00:00 2001 From: jinkimmy <145090267+jinkimmy@users.noreply.github.com> Date: Sat, 18 May 2024 08:45:47 +0900 Subject: [PATCH] =?UTF-8?q?[5=EC=9B=94]=20=EA=B9=80=ED=98=95=EC=A7=84=20/?= =?UTF-8?q?=20=EB=82=98=EB=A7=8C=EC=9D=98=20=EB=8F=84=EC=BB=A4=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EC=A6=88=20=ED=8C=8C=EC=9D=BC=20=EC=BD=98=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20(#32)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * docker-compose contest 작업 추가 * Update PR with added and removed files * Update README.md * Update docker-compose.yaml * Update README.md --- contest/docker-compose/jin/README.md | 36 ++++++++++++ contest/docker-compose/jin/chat.py | 57 +++++++++++++++++++ .../docker-compose/jin/docker-compose.yaml | 29 ++++++++++ contest/docker-compose/jin/requirements.txt | 3 + contest/docker-compose/jin/utils.py | 22 +++++++ 5 files changed, 147 insertions(+) create mode 100644 contest/docker-compose/jin/README.md create mode 100644 contest/docker-compose/jin/chat.py create mode 100644 contest/docker-compose/jin/docker-compose.yaml create mode 100644 contest/docker-compose/jin/requirements.txt create mode 100644 contest/docker-compose/jin/utils.py diff --git a/contest/docker-compose/jin/README.md b/contest/docker-compose/jin/README.md new file mode 100644 index 0000000..002cf14 --- /dev/null +++ b/contest/docker-compose/jin/README.md @@ -0,0 +1,36 @@ +## LLM chatbot +### 소개 +chat-gpt와 비슷하게 local LLM(Llama3) 과 대화를 할 수 있는 간단한 웹 애플리케이션입니다. +- LLM(Large Language Models)은 대규모 언어 모델이라는 뜻으로, 사용자 질문을 입력하면 질문에 대한 답변을 생성하는 언어 AI 모델입니다. + - OpenAI의 chat-gpt, Meta의 Llama, Google의 Gemma 등이 대표적인 예입니다. +- 구현된 앱에는 비교적 작은 Meta의 Llama3-8B 모델을 사용했기 때문에 한국어에 대한 성능은 좋지 않습니다. +스크린샷 2024-05-15 오후 10 34 17 + + +### 실행 방법 +`docker-compose.yml`파일이 있는 폴더로 이동하여 Docker Compose를 사용하여 실행합니다. +~~~sh +docker-compose up +~~~ +- `streamlit` 컨테이너가 실행되고 있는 모습 +스크린샷 2024-05-15 오후 10 18 05 + +터미널에서 streamlit 컨테이너 실행되고 있으면 아래의 명령어를 통해 Llama3를 ollama 컨테이너에 실행합니다. +~~~sh +docker-compose exec ollama ollama run llama3 +~~~ + +Llama3-8B 모델은 용량이 크기 때문에 실행되는데 약간의 시간이 소요됩니다. +- llama3가 ollama 컨테이너에서 안정적으로 서빙되고 있다면 다음과 같은 문구가 나타납니다. +~~~sh +>>> Send a message (/? for help) +~~~ + +이제 웹브라우저에서 `localhost:8502`로 접속하여 채팅을 시작합니다. + +### 참고 +- 이 프로젝트는 streamlit 프레임워크를 사용하여 개발되었습니다. +- 챗봇은 사용자가 질문을 하면 llm이 대답하는 간단한 기능이 포함됐습니다. +- Ollama는 다양한 LLM을 로컬PC 환경에서 쉽고 빠르게 배포할 수 있게 도와주는 오픈소스입니다. + - 따라서, 개인 PC 리소스가 매우 제한적이라면 모델이 실행되지 않거나 답변이 느립니다. +- 이 프로젝트에서 사용되는 llama3-8B는 최소 8GB의 RAM을 필요로 합니다. diff --git a/contest/docker-compose/jin/chat.py b/contest/docker-compose/jin/chat.py new file mode 100644 index 0000000..3c019b3 --- /dev/null +++ b/contest/docker-compose/jin/chat.py @@ -0,0 +1,57 @@ +import streamlit as st +from langchain.callbacks.streaming_stdout import StreamingStdOutCallbackHandler +from langchain.callbacks.manager import CallbackManager +from langchain_community.llms import ollama +from langchain_community.chat_models import ollama +from langchain_core.output_parsers import StrOutputParser +from langchain_core.prompts import ChatPromptTemplate +from utils import print_messages, StreamHandler + + +# llama3-8B 모델 생성 +llm =ollama.ChatOllama( + model="llama3", + base_url="http://ollama:11434", + verbose=True, + callback_manager=CallbackManager([StreamingStdOutCallbackHandler()]) +) + +def get_response(user_input): + global llm + # Prompt 생성 + prompt = ChatPromptTemplate.from_messages( + [ + ("system", "You are helpful ai assistant. If possible, please answer in Korean "), + ("user", "{question}") + ] + ) + chain = prompt | llm | StrOutputParser() + # LLM 답변 생성 + response = chain.invoke({"question": user_input}) + return response + + +st.title("Chat with Llama3") +if "messages" not in st.session_state.keys(): + st.session_state["messages"] = [ + {"role": "assistant", "content": "저에게 질문을 해주세요."} + ] + +if user_input := st.chat_input("메세지를 입력해 주세요."): + # 사용자 입력 + st.session_state.messages.append({"role": "user", "content": user_input}) + # st.chat_message("user").write(f"{user_input}") + # st.session_state["messages"].append(ChatMessage(role="user", content=user_input)) + +for message in st.session_state.messages: + with st.chat_message(message["role"]): + st.write(message["content"]) + +if st.session_state.messages[-1]["role"] != "assistant": + with st.chat_message("assistant"): + with st.spinner("답변 생성중..."): + stream_handler = StreamHandler(st.empty()) + response = get_response(user_input) + st.write(response) + message = {"role": "assistant", "content": response} + st.session_state.messages.append(message) \ No newline at end of file diff --git a/contest/docker-compose/jin/docker-compose.yaml b/contest/docker-compose/jin/docker-compose.yaml new file mode 100644 index 0000000..d5d69d1 --- /dev/null +++ b/contest/docker-compose/jin/docker-compose.yaml @@ -0,0 +1,29 @@ +version: '3' +name: docker-ai-chatbot +services: + streamlit: + image: python:latest + ports: + - '8502:8501' + networks: + - internal-net + volumes: + - ./chat:/app + working_dir: /app + command: bash -c "pip install -r requirements.txt && streamlit run chat.py" + restart: unless-stopped + + ollama: + image: ollama/ollama + ports: + - '11434:11434' + networks: + - internal-net + volumes: + - ./llm:/root/.ollama + restart: unless-stopped + +networks: + internal-net: + driver: bridge + diff --git a/contest/docker-compose/jin/requirements.txt b/contest/docker-compose/jin/requirements.txt new file mode 100644 index 0000000..fbf7bb2 --- /dev/null +++ b/contest/docker-compose/jin/requirements.txt @@ -0,0 +1,3 @@ +streamlit +langchain +langchain-community \ No newline at end of file diff --git a/contest/docker-compose/jin/utils.py b/contest/docker-compose/jin/utils.py new file mode 100644 index 0000000..847e8b9 --- /dev/null +++ b/contest/docker-compose/jin/utils.py @@ -0,0 +1,22 @@ +from typing import Any +from uuid import UUID +from langchain_core.outputs import ChatGenerationChunk, GenerationChunk +import streamlit as st +from langchain_core.callbacks.base import BaseCallbackHandler + + +class StreamHandler(BaseCallbackHandler): + def __init__(self, container, initial_text=""): + self.container = container + self.text = initial_text + + def on_llm_new_token(self, token: str, **kwargs) -> None: + self.text += token + self.container.markdown(self.text) + + +def print_messages(): + # 이전 대화기록 출력 + if "messages" in st.session_state and len(st.session_state["messages"]) > 0: + for chat_message in st.session_state["message"]: + st.chat_message(chat_message.role).write(chat_message.content) \ No newline at end of file