Skip to content

Commit

Permalink
🚀 RELEASE: Version bump to 1.1.0. Added a basic REST-API wrapper. #2
Browse files Browse the repository at this point in the history
* 📦 NEW: Wrapped REST API endpoint around Meeseeks core.
* 📦 NEW: Docker image for API & Build action update
  • Loading branch information
bearlike authored May 13, 2024
2 parents bb73b4a + e5ab51f commit e46c006
Show file tree
Hide file tree
Showing 7 changed files with 246 additions and 69 deletions.
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ VERSION=1.0.0
ENVMODE=dev
LOG_LEVEL=DEBUG
CACHE_DIR='/path/to/cache/directory'
MASTER_API_TOKEN='xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'


# * Home Assistant Configuration
Expand Down
114 changes: 82 additions & 32 deletions .github/workflows/docker-buildx.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@
# If the branch name is 'release/1.0.2-dev', the image is tagged as '1.0.2-dev'.
#
# * The 'latest' and 'stable' tags allow us to easily switch between different versions.
# * The 'dev' tag allows you to have a separate version for development.
# * The 'dev' tag allows you to have a separate version for development.

name: Build Meseeks Chat Docker Image
name: Build and Push Docker Images

on:
workflow_dispatch:
Expand All @@ -25,48 +25,98 @@ on:
- "release/*"

jobs:
build:
docker:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
uses: actions/checkout@v4

- name: Extract version and release type
id: extract_version
run: |
BRANCH_NAME=${{ github.ref_name }}
VERSION=$(echo $BRANCH_NAME | cut -d'/' -f 2 | cut -d'-' -f 1)
RELEASE_TYPE=$(echo $BRANCH_NAME | cut -d'/' -f 2 | cut -d'-' -f 2)
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "release_type=$RELEASE_TYPE" >> $GITHUB_OUTPUT
- name: Docker meta for meeseeks-base
id: meta_base
uses: docker/metadata-action@v5
with:
images: ghcr.io/bearlike/meeseeks-base
tags: |
type=raw,value=${{ steps.extract_version.outputs.version }}${{ steps.extract_version.outputs.release_type == 'dev' && '-dev' || '' }}
type=raw,value=latest,enable=${{ steps.extract_version.outputs.release_type == 'latest' }}
type=raw,value=stable,enable=${{ steps.extract_version.outputs.release_type == 'stable' }}
type=raw,value=dev,enable=${{ steps.extract_version.outputs.release_type == 'dev' }}
- name: Docker meta for meeseeks-chat
id: meta_chat
uses: docker/metadata-action@v5
with:
images: ghcr.io/bearlike/meeseeks-chat
tags: |
type=raw,value=${{ steps.extract_version.outputs.version }}${{ steps.extract_version.outputs.release_type == 'dev' && '-dev' || '' }}
type=raw,value=latest,enable=${{ steps.extract_version.outputs.release_type == 'latest' }}
type=raw,value=stable,enable=${{ steps.extract_version.outputs.release_type == 'stable' }}
type=raw,value=dev,enable=${{ steps.extract_version.outputs.release_type == 'dev' }}
- name: Docker meta for meeseeks-api
id: meta_api
uses: docker/metadata-action@v5
with:
images: ghcr.io/bearlike/meeseeks-api
tags: |
type=raw,value=${{ steps.extract_version.outputs.version }}${{ steps.extract_version.outputs.release_type == 'dev' && '-dev' || '' }}
type=raw,value=latest,enable=${{ steps.extract_version.outputs.release_type == 'latest' }}
type=raw,value=stable,enable=${{ steps.extract_version.outputs.release_type == 'stable' }}
type=raw,value=dev,enable=${{ steps.extract_version.outputs.release_type == 'dev' }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v3

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
uses: docker/setup-buildx-action@v3

- name: Login to DockerHub
uses: docker/login-action@v1
- name: Login to GHCR
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Extract branch name
id: extract_branch
shell: bash
run: |
BRANCH_NAME=$(echo ${{ github.ref }} | sed 's/refs\/heads\///')
echo "branch=$BRANCH_NAME" >> $GITHUB_ENV
echo "Extracted branch name: $BRANCH_NAME"
- name: Build and push meeseeks-base
uses: docker/build-push-action@v5
with:
context: .
file: Dockerfile.base
platforms: linux/amd64,linux/arm64
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta_base.outputs.tags }}
labels: ${{ steps.meta_base.outputs.labels }}

- name: Set version and channel
id: version_channel
run: |
BRANCH_NAME=${{ env.branch }}
VERSION=$(echo $BRANCH_NAME | cut -d'/' -f 2 | cut -d'-' -f 1)
CHANNEL=$(echo $BRANCH_NAME | cut -d'/' -f 2 | cut -d'-' -f 2)
echo "version=$VERSION" >> $GITHUB_ENV
echo "channel=$CHANNEL" >> $GITHUB_ENV
echo "Extracted version: $VERSION"
echo "Extracted channel: $CHANNEL"
echo "Extracted branch name: $BRANCH_NAME"
- name: Build and push meeseeks-chat
uses: docker/build-push-action@v5
with:
context: .
file: Dockerfile.chat
platforms: linux/amd64,linux/arm64
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta_chat.outputs.tags }}
labels: ${{ steps.meta_chat.outputs.labels }}
build-args: |
BASE_IMAGE=ghcr.io/bearlike/meeseeks-base:${{ steps.extract_version.outputs.version }}${{ steps.extract_version.outputs.release_type == 'dev' && '-dev' || '' }}
- name: Build and push
uses: docker/build-push-action@v2
- name: Build and push meeseeks-api
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: |
ghcr.io/bearlike/meeseeks-chat:${{ env.version }}${{ env.channel == 'dev' && '-dev' || '' }}
ghcr.io/bearlike/meeseeks-chat:${{ env.channel == 'latest' && 'latest' || env.channel == 'stable' && 'stable' || 'dev' }}
file: Dockerfile.api
platforms: linux/amd64,linux/arm64
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta_api.outputs.tags }}
labels: ${{ steps.meta_api.outputs.labels }}
build-args: |
BASE_IMAGE=ghcr.io/bearlike/meeseeks-base:${{ steps.extract_version.outputs.version }}${{ steps.extract_version.outputs.release_type == 'dev' && '-dev' || '' }}
22 changes: 22 additions & 0 deletions Dockerfile.api
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# syntax=docker/dockerfile:1

# Dockerfile.api
ARG BASE_IMAGE="ghcr.io/bearlike/meeseeks-base:latest"
FROM $BASE_IMAGE

ARG TITLE="Meeseeks API: Personal Assistant"

LABEL title=$TITLE

# Install the meeseeks-api dependencies
WORKDIR /app/meeseeks-api
RUN poetry install

# Set API specific environment variable
ENV MASTER_API_TOKEN='msk-strong-password'

# Expose port 5123 for the API
EXPOSE 5123

# Run the API application
ENTRYPOINT ["poetry", "run", "python", "backend.py"]
21 changes: 4 additions & 17 deletions Dockerfile → Dockerfile.base
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
# Dockerfile to build meeseeks-chat with core dependencies.
# syntax=docker/dockerfile:1

# Dockerfile.base
FROM python:3.11-buster

# Set the title, GitHub repo URL, version, and author
ARG TITLE="Meeseeks Chat: Personal Assistant" \
ARG TITLE="Meeseeks Base" \
VERSION="1.0.0" \
AUTHOR="Krishnakanth Alagiri"

Expand Down Expand Up @@ -43,11 +44,7 @@ RUN pip install 'poetry>=1.8,<1.9'
# Install the core dependencies
RUN poetry install

# Install the meeseeks-chat dependencies
WORKDIR /app/meeseeks-chat
RUN poetry install

# Set default environment variablesfor Meeseeks
# Set default environment variables for Meeseeks (common ones)
ENV CACHE_DIR='/tmp/meeseeks_cache' \
DEFAULT_MODEL='gpt-3.5-turbo' \
LOG_LEVEL=DEBUG \
Expand All @@ -56,13 +53,3 @@ ENV CACHE_DIR='/tmp/meeseeks_cache' \
COLOREDLOGS_FIELD_STYLES='asctime=color=240;name=45,inverse' \
COLOREDLOGS_LEVEL_STYLES='info=220;spam=22;debug=34;verbose=34;notice=220;warning=202;success=118,bold;error=124;critical=background=red' \
COLOREDLOGS_LOG_FORMAT='%(asctime)s [%(name)s] %(levelname)s %(message)s'


# Expose port 8501 for Streamlit
EXPOSE 8502

# Healthcheck to ensure the Streamlit server is running
HEALTHCHECK CMD curl --fail http://localhost:8502/_stcore/health

# Run the Streamlit application
ENTRYPOINT ["poetry", "run", "python", "-m", "streamlit", "run", "chat_master.py", "--server.port=8502", "--server.address=0.0.0.0"]
22 changes: 22 additions & 0 deletions Dockerfile.chat
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# syntax=docker/dockerfile:1

# Dockerfile.chat
ARG BASE_IMAGE="ghcr.io/bearlike/meeseeks-base:latest"
FROM $BASE_IMAGE

ARG TITLE="Meeseeks Chat: Personal Assistant"

LABEL title=$TITLE

# Install the meeseeks-chat dependencies
WORKDIR /app/meeseeks-chat
RUN poetry install

# Expose port 8502 for Streamlit
EXPOSE 8502

# Healthcheck to ensure the Streamlit server is running
HEALTHCHECK CMD curl --fail http://localhost:8502/_stcore/health

# Run the Streamlit application
ENTRYPOINT ["poetry", "run", "python", "-m", "streamlit", "run", "chat_master.py", "--server.port=8502", "--server.address=0.0.0.0"]
3 changes: 2 additions & 1 deletion meeseeks-api/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# meeseeks-api

REST API Engine wrapped around the meeseeks-core
- REST API Engine wrapped around the meeseeks-core.
- No components are explicitly tested for safety or security. Use with caution in a production environment.

[Link to GitHub Repository](https://github.com/bearlike/Personal-Assistant/edit/main/README.md)
132 changes: 113 additions & 19 deletions meeseeks-api/backend.py
Original file line number Diff line number Diff line change
@@ -1,34 +1,128 @@
#!/usr/bin/env python3
# TODO: Complete the API submodule by wrapping around the Meeseeks core.
"""
Meeseeks API
This module implements a REST API for Meeseeks using Flask-RESTX.
It provides a single endpoint to interact with the Meeseeks core,
allowing users to submit queries and receive the executed action plan
as a JSON response.
"""
# TODO: API key authentication and rate limiting not implemented yet.
# Standard library modules
import os
import sys
from flask import Flask, request
from flask_restx import Api, Resource
# TODO: Need to package the application and import it as module
from typing import Dict

# Third-party modules
from flask import Flask, request, jsonify
from flask_restx import Api, Resource, fields
from dotenv import load_dotenv

# Adding the parent directory to the path before importing the custom modules
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), '..'))

# Custom imports - Meeseeks core modules
from core.task_master import generate_action_plan, run_action_plan
if True:
from core.task_master import generate_action_plan, run_action_plan
from core.classes import TaskQueue
from core.common import get_logger

# Load environment variables
load_dotenv()
# Get the API token from the environment variables
# The default API token is "msk-strong-password"
MASTER_API_TOKEN = os.getenv("MASTER_API_TOKEN", "msk-strong-password")

# Initialize logger
logging = get_logger(name="meeseeks-api")
logging.debug("Starting API server with API token: %s", MASTER_API_TOKEN)
# Create Flask application
app = Flask(__name__)
api = Api(app)

authorizations = {
'apikey': {
'type': 'apiKey',
'in': 'header',
'name': 'X-API-KEY'
}
}
VERSION = os.getenv("VERSION", "(Dev)")
# Create API instance with Swagger documentation
api = Api(app, version=VERSION, title='Meeseeks API',
description='Interact with Meeseeks through a REST API',
doc='/swagger-ui/', authorizations=authorizations, security='apikey')

# Define API namespace
ns = api.namespace('api', description='Meeseeks operations')

# Define API model for request and response
task_queue_model = api.model('TaskQueue', {
'human_message': fields.String(
required=True, description='The original user query'),
'action_steps': fields.List(fields.Nested(api.model('ActionStep', {
'action_consumer': fields.String(
required=True,
description='The tool responsible for executing the action'),
'action_type': fields.String(
required=True,
description='The type of action to be performed (get/set)'),
'action_argument': fields.String(
required=True,
description='The specific argument for the action'),
'result': fields.String(
description='The result of the executed action')
}))),
})


@ns.route('/query')
class MeeseeksQuery(Resource):
"""
Endpoint to submit a query to Meeseeks and receive the executed
action plan as a JSON response.
"""

@api.doc(security='apikey')
@api.expect(api.model('Query', {'query': fields.String(
required=True, description='The user query')}))
@api.response(200, 'Success', task_queue_model)
@api.response(400, 'Invalid input')
@api.response(401, 'Unauthorized')
def post(self) -> Dict:
"""
Process a user query, generate and execute the action plan,
and return the result as a JSON.
Requires a valid API token for authorization.
"""
# Get API token from headers
api_token = request.headers.get('X-API-Key', None)

# Validate API token
if api_token is None:
return {"message": "API token is not provided."}, 401
if api_token != MASTER_API_TOKEN:
logging.warning(
"Unauthorized API call attempt with token: %s", api_token)
return {"message": "Unauthorized"}, 401

# Get user query from request data
user_query = request.json.get('query')
if not user_query:
return {"message": "Invalid input: 'query' is required"}, 400

logging.info("Received user query: %s", user_query)

@api.route('/generate_action_plan')
class GenerateActionPlanResource(Resource):
def post(self):
user_input = request.json.get('user_input')
action_plan_list, task_queue = generate_action_plan(user_input)
return {'action_plan_list': action_plan_list, 'task_queue': task_queue}
# Generate action plan from user query
task_queue: TaskQueue = generate_action_plan(user_query=user_query)

# Execute action plan
task_queue = run_action_plan(task_queue)

@api.route('/run_action_plan')
class RunActionPlanResource(Resource):
def post(self):
task_queue = request.json.get('task_queue')
ai_response = run_action_plan(task_queue)
return {'ai_response': ai_response}
# Return TaskQueue as JSON
logging.info("Returning executed action plan.")
return task_queue.dict(), 200


if __name__ == '__main__':
app.run(debug=True)
app.run(debug=True, host='0.0.0.0', port=5123)

0 comments on commit e46c006

Please sign in to comment.