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