Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .dockerignore
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
secrets
secrets
secrets.json
.vscode
58 changes: 57 additions & 1 deletion .github/workflows/github_cd.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
name: Upload Python Package
name: Continuous Deployment Workflow

on:
workflow_dispatch:
release:
types: [published]

Expand Down Expand Up @@ -28,6 +29,7 @@ jobs:
with:
name: release-dists
path: dist/

pypi-publish:
runs-on: ubuntu-latest
needs:
Expand All @@ -45,3 +47,57 @@ jobs:
path: dist/
- name: Publish release distributions to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
continue-on-error: true

upload-docker-images:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build Docker images
run: |
# Define public ECR repository URL
PUBLIC_ECR_URL=public.ecr.aws/e7b5q5z5/nam685
# LangGraph API server
docker build -t chatbot -f src/chatbot/Dockerfile .
docker tag chatbot:latest $PUBLIC_ECR_URL/chatbot:${{ github.sha }}
# API
docker build -t chatbot-api -f api/Dockerfile .
docker tag chatbot-api:latest $PUBLIC_ECR_URL/chatbot-api:${{ github.sha }}
# UI
docker build -t chatbot-ui -f ui/Dockerfile ./ui
docker tag chatbot-ui:latest $PUBLIC_ECR_URL/chatbot-ui:${{ github.sha }}
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4.1.0
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ secrets.AWS_REGION }}
- name: Log in to Amazon ECR
run: |
aws ecr-public get-login-password --region us-east-1 | \
docker login --username AWS --password-stdin public.ecr.aws
- name: Push Docker images to ECR
run: |
PUBLIC_ECR_URL=public.ecr.aws/e7b5q5z5/nam685
docker push $PUBLIC_ECR_URL/chatbot:${{ github.sha }}
docker push $PUBLIC_ECR_URL/chatbot-api:${{ github.sha }}
docker push $PUBLIC_ECR_URL/chatbot-ui:${{ github.sha }}

deploy-ecs-by-cloudformation:
runs-on: ubuntu-latest
needs:
- upload-docker-images
steps:
- uses: actions/checkout@v4
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4.1.0
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ secrets.AWS_REGION }}
- name: Deploy CloudFormation stack
uses: aws-actions/aws-cloudformation-github-deploy@v1
with:
name: chatbot-ecs
template: ecs-infrastructure.yaml
parameter-overrides: "GitCommitHash=${{ github.sha }}"
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ __pycache__/
.ruff_cache
.langgraph_api
dist
ui/node_modules
ui/node_modules
secrets.json
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,12 @@ Agent architecture: use 2 chat models for separation of concern
- Helper model to classify if conversation is sensitive or not
- Main model equiped with tools

Added minimal unit tests
I also made FastAPI backend and NextJS frontend to interact with chatbot
Deploy chatbot as standalone container via LangGraph Platform

Prepared for deployment as standalone container via LangGraph Platform
Deploy to AWS ECS as separate services
Current deployment's URL: http://chatbot-alb-468353364.eu-central-1.elb.amazonaws.com/
(I don't keep this URL if I take it down, because I don't pay money for a fixed one))

[![PyPI - Version](https://img.shields.io/pypi/v/chatbot.svg)](https://pypi.org/project/chatbot)
[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/chatbot.svg)](https://pypi.org/project/chatbot)
Expand Down
2 changes: 1 addition & 1 deletion api/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM python:3.12
FROM python:3.12-alpine

WORKDIR /code
COPY ./pyproject.toml /code/pyproject.toml
Expand Down
20 changes: 20 additions & 0 deletions api/healthcheck.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import os
import urllib.request


def health_check():
port = os.getenv("CONTAINER_PORT", "8080")
url = f"http://localhost:{port}/health"
try:
with urllib.request.urlopen(url) as response:
status_code = response.getcode()
body = response.read().decode()
print(f"Status code: {status_code}")
print(f"Response body: {body}")
except Exception as e:
print(f"Health check failed: {e}")
exit(1)


if __name__ == "__main__":
health_check()
16 changes: 0 additions & 16 deletions api/main.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,6 @@
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

from .routers import router

app = FastAPI()

origins = [
"http://127.0.0.1:3000",
"http://localhost:3000",
"http://ui:3000",
]

app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)

app.include_router(router)
13 changes: 12 additions & 1 deletion api/routers.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
import os
from typing import Any

from fastapi import APIRouter
from langgraph_sdk import get_client

from .models import ChatMessage, HumanReview, Thread

langgraph_api_uri = os.getenv("LANGGRAPH_API_URI")
if not langgraph_api_uri:
raise ValueError("LANGGRAPH_API_URI environment variable is not set")
print(f"LANGGRAPH_API_URI: {langgraph_api_uri}")

router = APIRouter()
langgraph_client = get_client(url="http://langgraph-api:8000")
langgraph_client = get_client(url=langgraph_api_uri)


def parse_ai_response(
Expand Down Expand Up @@ -34,6 +40,11 @@ async def read_main() -> dict:
return {"msg": "Hello! Welcome to the LangGraph Chat API"}


@router.get("/health")
async def health_check() -> dict:
return {"status": "ok"}


@router.get("/chat")
async def list_chat_threads() -> list[Thread]:
threads_data = await langgraph_client.threads.search(
Expand Down
4 changes: 4 additions & 0 deletions api/test_main.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import os

from fastapi.testclient import TestClient

os.environ["LANGGRAPH_API_URI"] = "http://localhost:8000"

from .main import app

client = TestClient(app)
Expand Down
2 changes: 2 additions & 0 deletions app.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# For usage in terminal

from pprint import pprint

from langchain_core.runnables.config import RunnableConfig
Expand Down
34 changes: 32 additions & 2 deletions compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,45 @@ services:
env_file:
- .env
environment:
CONTAINER_PORT: 8000
REDIS_URI: redis://langgraph-redis:6379
POSTGRES_URI: postgres://postgres:postgres@langgraph-postgres:5432/postgres?sslmode=disable
DATABASE_URI: postgres://postgres:postgres@langgraph-postgres:5432/postgres?sslmode=disable
healthcheck:
test: [ "CMD", "python", "src/chatbot/healthcheck.py" ]
interval: 10s
timeout: 1s
retries: 5
start_period: 10s
api:
image: chatbot-api:latest
ports:
- "8080:8080"
environment:
CONTAINER_PORT: 8080
LANGGRAPH_API_URI: http://langgraph-api:8000
depends_on:
langgraph-api:
condition: service_healthy
healthcheck:
test: [ "CMD", "python", "api/healthcheck.py" ]
start_period: 3s
timeout: 1s
retries: 5
interval: 5s
ui:
image: chatbot-ui:latest
ports:
- "3000:3000"
environment:
- HOSTNAME=0.0.0.0
HOSTNAME: 0.0.0.0
CONTAINER_PORT: 3000
API_URL: http://api:8080
depends_on:
api:
condition: service_healthy
healthcheck:
test: [ "CMD", "curl", "-f", "http://localhost:3000/health" ]
start_period: 3s
timeout: 1s
retries: 5
interval: 5s
Loading