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 모델을 사용했기 때문에 한국어에 대한 성능은 좋지 않습니다.
+
+
+
+### 실행 방법
+`docker-compose.yml`파일이 있는 폴더로 이동하여 Docker Compose를 사용하여 실행합니다.
+~~~sh
+docker-compose up
+~~~
+- `streamlit` 컨테이너가 실행되고 있는 모습
+
+
+터미널에서 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