Skip to content

Commit

Permalink
[5월] 김형진 / 나만의 도커 컴포즈 파일 콘테스트 (#32)
Browse files Browse the repository at this point in the history
* docker-compose contest 작업 추가

* Update PR with added and removed files

* Update README.md

* Update docker-compose.yaml

* Update README.md
  • Loading branch information
jinkimmy authored May 17, 2024
1 parent be14ca1 commit 39986c4
Show file tree
Hide file tree
Showing 5 changed files with 147 additions and 0 deletions.
36 changes: 36 additions & 0 deletions contest/docker-compose/jin/README.md
Original file line number Diff line number Diff line change
@@ -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 모델을 사용했기 때문에 한국어에 대한 성능은 좋지 않습니다.
<img width="724" alt="스크린샷 2024-05-15 오후 10 34 17" src="https://github.com/jinkimmy/docker-pro/assets/145090267/c71b69da-48ae-4fd5-9ade-e9042388fc05">


### 실행 방법
`docker-compose.yml`파일이 있는 폴더로 이동하여 Docker Compose를 사용하여 실행합니다.
~~~sh
docker-compose up
~~~
- `streamlit` 컨테이너가 실행되고 있는 모습
<img width="934" alt="스크린샷 2024-05-15 오후 10 18 05" src="https://github.com/jinkimmy/docker-pro/assets/145090267/84a602e9-f60e-4fdf-90b8-acb1cbf6c474">

터미널에서 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을 필요로 합니다.
57 changes: 57 additions & 0 deletions contest/docker-compose/jin/chat.py
Original file line number Diff line number Diff line change
@@ -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)
29 changes: 29 additions & 0 deletions contest/docker-compose/jin/docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -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

3 changes: 3 additions & 0 deletions contest/docker-compose/jin/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
streamlit
langchain
langchain-community
22 changes: 22 additions & 0 deletions contest/docker-compose/jin/utils.py
Original file line number Diff line number Diff line change
@@ -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)

0 comments on commit 39986c4

Please sign in to comment.