diff --git a/Dockerfile b/Dockerfile index 8d4bf6d..907f6f0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,30 +1,10 @@ # --------------------------------------- -# Base image for node -FROM node:19-bullseye-slim as node_base - -# --------------------------------------- -# Base image for runtime -FROM python:3.11-slim-bullseye as base - -ENV TZ=Etc/UTC -WORKDIR /usr/src/app - -# Install Redis -RUN apt-get update \ - && apt-get install -y curl wget gnupg cmake lsb-release build-essential dumb-init \ - && curl -fsSL https://packages.redis.io/gpg | gpg --dearmor -o /usr/share/keyrings/redis-archive-keyring.gpg \ - && echo "deb [signed-by=/usr/share/keyrings/redis-archive-keyring.gpg] https://packages.redis.io/deb $(lsb_release -cs) main" | tee /etc/apt/sources.list.d/redis.list \ - && apt-get update \ - && apt-get install -y redis \ - && apt-get clean \ - && mkdir -p /etc/redis /var/redis \ - && pip install --upgrade pip \ - && echo "appendonly yes" >> /etc/redis/redis.conf \ - && echo "dir /data/db/" >> /etc/redis/redis.conf +# Base image for dragonflydb +FROM ghcr.io/dragonflydb/dragonfly:v1.7.1 as dragonfly # --------------------------------------- # Build frontend -FROM node_base as frontend_builder +FROM node:19-bullseye-slim as frontend WORKDIR /usr/src/app COPY ./web/package.json ./web/package-lock.json ./ @@ -36,20 +16,26 @@ RUN npm run build # --------------------------------------- # Runtime environment -FROM base as release +FROM python:3.11-slim-bullseye as release # Set ENV ENV NODE_ENV='production' +ENV TZ=Etc/UTC WORKDIR /usr/src/app # Copy artifacts -COPY --from=frontend_builder /usr/src/app/web/build /usr/src/app/api/static/ +COPY --from=dragonfly /usr/local/bin/dragonfly /usr/local/bin/dragonfly +COPY --from=frontend /usr/src/app/web/build /usr/src/app/api/static/ COPY ./api /usr/src/app/api COPY scripts/deploy.sh /usr/src/app/deploy.sh # Install api dependencies -RUN pip install --no-cache-dir ./api \ - && chmod 755 /usr/src/app/deploy.sh +RUN apt-get update && apt-get install -y cmake build-essential dumb-init \ + && pip install --upgrade pip \ + && pip install --no-cache-dir ./api \ + && apt-get clean \ + && rm -rf /tmp/* \ + && chmod 755 /usr/src/app/deploy.sh /usr/local/bin/dragonfly EXPOSE 8008 ENTRYPOINT ["/usr/bin/dumb-init", "--"] diff --git a/Dockerfile.dev b/Dockerfile.dev index c6617d9..eab3acd 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -3,40 +3,31 @@ FROM node:19-bullseye-slim as node_base # --------------------------------------- -# Base image for runtime -FROM python:3.11-slim-bullseye as base - -ENV TZ=Etc/UTC -WORKDIR /usr/src/app - -# Install Redis -RUN apt-get update \ - && apt-get install -y curl wget gnupg cmake lsb-release build-essential dumb-init \ - && curl -fsSL https://packages.redis.io/gpg | gpg --dearmor -o /usr/share/keyrings/redis-archive-keyring.gpg \ - && echo "deb [signed-by=/usr/share/keyrings/redis-archive-keyring.gpg] https://packages.redis.io/deb $(lsb_release -cs) main" | tee /etc/apt/sources.list.d/redis.list \ - && apt-get update \ - && apt-get install -y redis \ - && apt-get clean \ - && mkdir -p /etc/redis /var/redis \ - && pip install --upgrade pip \ - && echo "appendonly yes" >> /etc/redis/redis.conf \ - && echo "dir /data/db/" >> /etc/redis/redis.conf +# Base image for dragonflydb +FROM ghcr.io/dragonflydb/dragonfly:v1.7.1 as dragonfly # --------------------------------------- # Dev environment -FROM base as dev +FROM python:3.11-slim-bullseye as dev # Set ENV WORKDIR /usr/src/app +ENV TZ=Etc/UTC ENV NODE_ENV='development' -# Install node.js and npm packages +# Install dependencies +RUN apt-get update && apt-get install -y cmake build-essential dumb-init \ + && pip install --upgrade pip + +# Copy database, source code, and scripts +COPY --from=dragonfly /usr/local/bin/dragonfly /usr/local/bin/dragonfly COPY --from=node_base /usr/local /usr/local COPY scripts/dev.sh /usr/src/app/dev.sh COPY ./web/package.json ./web/package-lock.json ./ RUN npm ci \ - && chmod 755 /usr/src/app/dev.sh + && chmod 755 /usr/src/app/dev.sh \ + && chmod 755 /usr/local/bin/dragonfly EXPOSE 8008 EXPOSE 9124 diff --git a/README.md b/README.md index b6e800a..18d8189 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Serge is a chat interface crafted with [llama.cpp](https://github.com/ggerganov/llama.cpp) for running Alpaca models. No API keys, entirely self-hosted! - 🌐 **SvelteKit** frontend -- 💾 **Redis** for storing chat history & parameters +- 💾 **[Dragonfly](https://github.com/dragonflydb/dragonfly)** for storing chat history & parameters - ⚙️ **FastAPI + LangChain** for the API, wrapping calls to [llama.cpp](https://github.com/ggerganov/llama.cpp) using the [python bindings](https://github.com/abetlen/llama-cpp-python) 🎥 Demo: diff --git a/api/src/serge/routers/chat.py b/api/src/serge/routers/chat.py index 1b144ec..0fc8840 100644 --- a/api/src/serge/routers/chat.py +++ b/api/src/serge/routers/chat.py @@ -36,7 +36,8 @@ async def create_new_chat( except Exception as exc: raise ValueError(f"Model can't be found: {exc}") - client = Redis() + client = Redis(host="localhost", port=6379, decode_responses=False) + logger.info(f"Connected to Redis? {client.ping()}") params = ChatParameters( model_path=model, @@ -69,7 +70,8 @@ async def create_new_chat( @chat_router.get("/") async def get_all_chats(): res = [] - client = Redis() + client = Redis(host="localhost", port=6379, decode_responses=False) + logger.info(f"Connected to Redis? {client.ping()}") ids = client.smembers("chats") @@ -98,7 +100,8 @@ async def get_all_chats(): @chat_router.get("/{chat_id}") async def get_specific_chat(chat_id: str): - client = Redis() + client = Redis(host="localhost", port=6379, decode_responses=False) + logger.info(f"Connected to Redis? {client.ping()}") if not client.sismember("chats", chat_id): raise ValueError("Chat does not exist") @@ -115,7 +118,8 @@ async def get_specific_chat(chat_id: str): @chat_router.get("/{chat_id}/history") async def get_chat_history(chat_id: str): - client = Redis() + client = Redis(host="localhost", port=6379, decode_responses=False) + logger.info(f"Connected to Redis? {client.ping()}") if not client.sismember("chats", chat_id): raise ValueError("Chat does not exist") @@ -126,7 +130,8 @@ async def get_chat_history(chat_id: str): @chat_router.delete("/{chat_id}/prompt") async def delete_prompt(chat_id: str, idx: int): - client = Redis() + client = Redis(host="localhost", port=6379, decode_responses=False) + logger.info(f"Connected to Redis? {client.ping()}") if not client.sismember("chats", chat_id): raise ValueError("Chat does not exist") @@ -147,7 +152,8 @@ async def delete_prompt(chat_id: str, idx: int): @chat_router.delete("/{chat_id}") async def delete_chat(chat_id: str): - client = Redis() + client = Redis(host="localhost", port=6379, decode_responses=False) + logger.info(f"Connected to Redis? {client.ping()}") if not client.sismember("chats", chat_id): raise ValueError("Chat does not exist") @@ -162,8 +168,9 @@ async def delete_chat(chat_id: str): @chat_router.get("/{chat_id}/question") def stream_ask_a_question(chat_id: str, prompt: str): - logger.debug("Starting redis client") - client = Redis() + logger.info("Starting redis client") + client = Redis(host="localhost", port=6379, decode_responses=False) + logger.info(f"Connected to Redis? {client.ping()}") if not client.sismember("chats", chat_id): raise ValueError("Chat does not exist") @@ -235,7 +242,8 @@ def stream_ask_a_question(chat_id: str, prompt: str): @chat_router.post("/{chat_id}/question") async def ask_a_question(chat_id: str, prompt: str): - client = Redis() + client = Redis(host="localhost", port=6379, decode_responses=False) + logger.info(f"Connected to Redis? {client.ping()}") if not client.sismember("chats", chat_id): raise ValueError("Chat does not exist") diff --git a/api/src/serge/utils/stream.py b/api/src/serge/utils/stream.py index f1190d6..fdbbcdb 100644 --- a/api/src/serge/utils/stream.py +++ b/api/src/serge/utils/stream.py @@ -17,7 +17,8 @@ class ChainRedisHandler(StreamingStdOutCallbackHandler): logger.debug(f"Setting up ChainRedisHandler with id {id}") super().__init__() self.id = id - self.client = Redis() + self.client = Redis(host="localhost", port=6379, decode_responses=False) + logger.info(f"Connected to Redis? {self.client.ping()}") logger.info(f"Stream key : {self.stream_key}") @property diff --git a/docker-compose.yml b/docker-compose.yml index 78dd226..59ae316 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -11,4 +11,4 @@ services: volumes: weights: - datadb: \ No newline at end of file + datadb: diff --git a/scripts/deploy.sh b/scripts/deploy.sh index 3d5b698..bfdc2ab 100644 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -8,8 +8,9 @@ pip install llama-cpp-python==0.1.77 || { exit 1 } -# Start Redis instance -redis-server /etc/redis/redis.conf & +# Start Dragonfly instance +mkdir -p /data/db +/usr/local/bin/dragonfly --noversion_check --logtostderr --dbnum 0 --bind localhost --port 6379 --save_schedule "*:*" --dbfilename dragonfly --dir /data/db & # Start the API cd /usr/src/app/api || exit 1 diff --git a/scripts/dev.sh b/scripts/dev.sh index d4d71f0..62588ba 100644 --- a/scripts/dev.sh +++ b/scripts/dev.sh @@ -14,8 +14,9 @@ pip install llama-cpp-python==0.1.77 || { exit 1 } -# Start Redis instance -redis-server /etc/redis/redis.conf & +# Start Dragonfly instance +mkdir -p /data/db +/usr/local/bin/dragonfly --noversion_check --logtostderr --dbnum 0 --bind localhost --port 6379 --save_schedule "*:*" --dbfilename dragonfly --dir /data/db & # Start the web server cd /usr/src/app/web || exit 1