diff --git a/.env.example b/.env.example index 175eab9..4a24b31 100644 --- a/.env.example +++ b/.env.example @@ -1,10 +1,4 @@ BOT_NAME= -AWS_ACCESS_KEY_ID=*** -AWS_SECRET_ACCESS_KEY=*** -AWS_REGION=*** -AWS_S3_BUCKET=*** -WINEDB_HOST=*** -WINEDB_PASSWORD=*** OPENAI_API_KEY=*** BING_SEARCH_URL=*** BING_SUBSCRIPTION_KEY=*** diff --git a/.gitignore b/.gitignore index d4d6a87..88ca765 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,6 @@ __pycache__/ image/ audio/ video/ -dataframe/ \ No newline at end of file +dataframe/ + +static/* \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 39587f4..f9374ad 100644 --- a/Dockerfile +++ b/Dockerfile @@ -20,4 +20,6 @@ RUN poetry install --with tools COPY . . -ENTRYPOINT ["poetry", "run", "python3", "-m", "uvicorn", "main:app", "--host=0.0.0.0", "--port=8000"] \ No newline at end of file +ENV PORT 8000 + +ENTRYPOINT ["poetry", "run", "serve"] \ No newline at end of file diff --git a/README.md b/README.md index fb95fb1..b838290 100644 --- a/README.md +++ b/README.md @@ -43,52 +43,37 @@ We also don't know what tools EVAL will create. Every day, It will create the ri ## Usage -1. S3 Settings -2. environments settings -3. `docker-compose up -d` - -### S3 - -1. Create a bucket. -2. Turn off the "Block all public access" setting for the bucket. ![image](assets/block_public_access.png) -3. Add the following text to Bucket Policy. - ```json - { - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "AllowPublicRead", - "Effect": "Allow", - "Principal": { - "AWS": "*" - }, - "Action": "s3:GetObject", - "Resource": "arn:aws:s3:::{your-bucket-name}/*" - } - ] - } - ``` +1. environments settings +2. `docker-compose up -d` ### Environment -These environmental variables are essential, so please set them. +You need to write some environment variables in the `.env` file. Refer [.env.example](.env.example) if you don't know how to format it. -``` -BOT_NAME: your custom bot name -OPENAI_API_KEY: openai api key -AWS_ACCESS_KEY_ID -AWS_SECRET_ACCESS_KEY -AWS_REGION -AWS_S3_BUCKET -``` +**Mandatory** -These environment variables are necessary to use the following tools: -If you want to use it, set it up, and if you don't need it, you don't have to set it up. +Manatory envs are required in order to serve EVAL. -``` -SERPAPI_API_KEY: need to append google search tool -BING_SEARCH_URL, BING_SUBSCRIPTION_KEY: need to append bing search tool -``` +- `OPENAI_API_KEY` - OpenAI api key + +**Optional** + +Each optional env has default value, so you don't need to set unless you want to change it. + +- `PORT` - port (default: 8000) +- `SERVER` - server address (default: http://localhost:8000) +- `LOG_LEVEL` - INFO | DEBUG (default: INFO) +- `BOT_NAME` - give it a name! (default: Orca) + +**For More Tools** + +Some tools requires environment variables. Set envs depend on which tools you want to use. + +- Google search tool + - `SERPAPI_API_KEY` +- Bing search tool + - `BING_SEARCH_URL` + - `BING_SUBSCRIPTION_KEY` ## TODO diff --git a/main.py b/api/main.py similarity index 74% rename from main.py rename to api/main.py index af49891..9817763 100644 --- a/main.py +++ b/api/main.py @@ -1,35 +1,41 @@ from typing import Dict, List, TypedDict import re +import uvicorn from fastapi import FastAPI +from fastapi.staticfiles import StaticFiles + from pydantic import BaseModel -from s3 import upload from env import settings -from prompts.error import ERROR_PROMPT -from agents.manager import AgentManager -from tools.base import BaseToolSet -from tools.cpu import ( +from core.prompts.error import ERROR_PROMPT +from core.agents.manager import AgentManager +from core.tools.base import BaseToolSet +from core.tools.cpu import ( Terminal, CodeEditor, RequestsGet, WineDB, ExitConversation, ) -from tools.gpu import ( +from core.tools.gpu import ( ImageEditing, InstructPix2Pix, Text2Image, VisualQuestionAnswering, ) -from handlers.base import BaseHandler, FileHandler, FileType -from handlers.image import ImageCaptioning -from handlers.dataframe import CsvToDataframe +from core.handlers.base import BaseHandler, FileHandler, FileType +from core.handlers.image import ImageCaptioning +from core.handlers.dataframe import CsvToDataframe +from core.upload import StaticUploader + from logger import logger app = FastAPI() +app.mount("/static", StaticFiles(directory=StaticUploader.STATIC_DIR), name="static") +uploader = StaticUploader.from_settings(settings) toolsets: List[BaseToolSet] = [ Terminal(), @@ -104,6 +110,10 @@ async def command(request: Request) -> Response: return { "response": res["output"], - "files": [upload(image) for image in images] - + [upload(dataframe) for dataframe in dataframes], + "files": [uploader.upload(image) for image in images] + + [uploader.upload(dataframe) for dataframe in dataframes], } + + +def serve(): + uvicorn.run("api.main:app", host="0.0.0.0", port=settings["PORT"]) diff --git a/agents/builder.py b/core/agents/builder.py similarity index 89% rename from agents/builder.py rename to core/agents/builder.py index 1ee93a9..9675102 100644 --- a/agents/builder.py +++ b/core/agents/builder.py @@ -1,15 +1,15 @@ from langchain.chat_models.base import BaseChatModel from langchain.output_parsers.base import BaseOutputParser -from prompts.input import EVAL_PREFIX, EVAL_SUFFIX from env import settings -from tools.base import BaseToolSet -from tools.factory import ToolsFactory +from core.prompts.input import EVAL_PREFIX, EVAL_SUFFIX +from core.tools.base import BaseToolSet +from core.tools.factory import ToolsFactory -from agents.llm import ChatOpenAI -from agents.chat_agent import ConversationalChatAgent -from agents.parser import EvalOutputParser +from .llm import ChatOpenAI +from .chat_agent import ConversationalChatAgent +from .parser import EvalOutputParser class AgentBuilder: diff --git a/agents/chat_agent.py b/core/agents/chat_agent.py similarity index 98% rename from agents/chat_agent.py rename to core/agents/chat_agent.py index 5fccdcc..ad35246 100644 --- a/agents/chat_agent.py +++ b/core/agents/chat_agent.py @@ -20,7 +20,7 @@ from langchain.schema import ( ) from langchain.tools.base import BaseTool -from prompts.input import EVAL_TOOL_RESPONSE +from core.prompts.input import EVAL_TOOL_RESPONSE class ConversationalChatAgent(Agent): diff --git a/agents/llm.py b/core/agents/llm.py similarity index 100% rename from agents/llm.py rename to core/agents/llm.py diff --git a/agents/manager.py b/core/agents/manager.py similarity index 93% rename from agents/manager.py rename to core/agents/manager.py index d675140..ee653e8 100644 --- a/agents/manager.py +++ b/core/agents/manager.py @@ -1,14 +1,14 @@ -from typing import Dict, Any +from typing import Dict from langchain.agents.tools import BaseTool from langchain.agents.agent import Agent, AgentExecutor from langchain.chains.conversation.memory import ConversationBufferMemory from langchain.memory.chat_memory import BaseChatMemory -from tools.base import BaseToolSet -from tools.factory import ToolsFactory +from core.tools.base import BaseToolSet +from core.tools.factory import ToolsFactory -from agents.builder import AgentBuilder +from .builder import AgentBuilder class AgentManager: diff --git a/agents/parser.py b/core/agents/parser.py similarity index 91% rename from agents/parser.py rename to core/agents/parser.py index 3e70ff9..92908e6 100644 --- a/agents/parser.py +++ b/core/agents/parser.py @@ -3,7 +3,7 @@ from typing import Dict from langchain.output_parsers.base import BaseOutputParser -from prompts.input import EVAL_FORMAT_INSTRUCTIONS +from core.prompts.input import EVAL_FORMAT_INSTRUCTIONS class EvalOutputParser(BaseOutputParser): diff --git a/handlers/base.py b/core/handlers/base.py similarity index 100% rename from handlers/base.py rename to core/handlers/base.py diff --git a/handlers/dataframe.py b/core/handlers/dataframe.py similarity index 86% rename from handlers/dataframe.py rename to core/handlers/dataframe.py index 4230d78..07a3187 100644 --- a/handlers/dataframe.py +++ b/core/handlers/dataframe.py @@ -1,8 +1,8 @@ import pandas as pd -from prompts.file import DATAFRAME_PROMPT +from core.prompts.file import DATAFRAME_PROMPT -from handlers.base import BaseHandler +from .base import BaseHandler class CsvToDataframe(BaseHandler): diff --git a/handlers/image.py b/core/handlers/image.py similarity index 95% rename from handlers/image.py rename to core/handlers/image.py index 60b9c06..d4e8577 100644 --- a/handlers/image.py +++ b/core/handlers/image.py @@ -5,9 +5,9 @@ from transformers import ( BlipForConditionalGeneration, ) -from prompts.file import IMAGE_PROMPT +from core.prompts.file import IMAGE_PROMPT -from handlers.base import BaseHandler +from .base import BaseHandler class ImageCaptioning(BaseHandler): diff --git a/prompts/error.py b/core/prompts/error.py similarity index 100% rename from prompts/error.py rename to core/prompts/error.py diff --git a/prompts/file.py b/core/prompts/file.py similarity index 100% rename from prompts/file.py rename to core/prompts/file.py diff --git a/prompts/input.py b/core/prompts/input.py similarity index 100% rename from prompts/input.py rename to core/prompts/input.py diff --git a/tools/base.py b/core/tools/base.py similarity index 97% rename from tools/base.py rename to core/tools/base.py index 14f2f1d..5468a16 100644 --- a/tools/base.py +++ b/core/tools/base.py @@ -1,4 +1,4 @@ -from typing import Optional, Callable, Tuple +from typing import Callable, Tuple from enum import Enum from langchain.agents.tools import Tool, BaseTool diff --git a/tools/cpu.py b/core/tools/cpu.py similarity index 99% rename from tools/cpu.py rename to core/tools/cpu.py index 674eb20..4f17c07 100644 --- a/tools/cpu.py +++ b/core/tools/cpu.py @@ -9,7 +9,7 @@ from bs4 import BeautifulSoup import subprocess -from tools.base import tool, BaseToolSet, ToolScope, SessionGetter +from .base import tool, BaseToolSet, ToolScope, SessionGetter from logger import logger diff --git a/tools/factory.py b/core/tools/factory.py similarity index 97% rename from tools/factory.py rename to core/tools/factory.py index a327959..21dbb3d 100644 --- a/tools/factory.py +++ b/core/tools/factory.py @@ -3,7 +3,7 @@ from langchain.agents import load_tools from langchain.agents.tools import BaseTool from langchain.llms.base import BaseLLM -from tools.base import BaseToolSet, SessionGetter +from .base import BaseToolSet, SessionGetter class ToolsFactory: diff --git a/tools/gpu.py b/core/tools/gpu.py similarity index 99% rename from tools/gpu.py rename to core/tools/gpu.py index 52c6aee..18cd79b 100644 --- a/tools/gpu.py +++ b/core/tools/gpu.py @@ -23,7 +23,7 @@ from diffusers import EulerAncestralDiscreteScheduler from utils import get_new_image_name from logger import logger -from tools.base import tool, BaseToolSet +from .base import tool, BaseToolSet class MaskFormer(BaseToolSet): diff --git a/core/upload/__init__.py b/core/upload/__init__.py new file mode 100644 index 0000000..d820ae5 --- /dev/null +++ b/core/upload/__init__.py @@ -0,0 +1,2 @@ +from .s3 import S3Uploader +from .static import StaticUploader diff --git a/core/upload/base.py b/core/upload/base.py new file mode 100644 index 0000000..9244a81 --- /dev/null +++ b/core/upload/base.py @@ -0,0 +1,15 @@ +from abc import ABC, abstractmethod, abstractstaticmethod + +from env import DotEnv + +STATIC_DIR = "static" + + +class AbstractUploader(ABC): + @abstractmethod + def upload(self, filepath: str) -> str: + pass + + @abstractstaticmethod + def from_settings(settings: DotEnv) -> "AbstractUploader": + pass diff --git a/core/upload/s3.py b/core/upload/s3.py new file mode 100644 index 0000000..98f3335 --- /dev/null +++ b/core/upload/s3.py @@ -0,0 +1,35 @@ +import os +import boto3 + +from env import DotEnv +from .base import AbstractUploader + + +class S3Uploader(AbstractUploader): + def __init__(self, accessKey: str, secretKey: str, region: str, bucket: str): + self.accessKey = accessKey + self.secretKey = secretKey + self.region = region + self.bucket = bucket + self.client = boto3.client( + "s3", + aws_access_key_id=self.accessKey, + aws_secret_access_key=self.secretKey, + ) + + @staticmethod + def from_settings(settings: DotEnv) -> "S3Uploader": + return S3Uploader( + settings["AWS_ACCESS_KEY_ID"], + settings["AWS_SECRET_ACCESS_KEY"], + settings["AWS_REGION"], + settings["AWS_S3_BUCKET"], + ) + + def get_url(self, object_name: str) -> str: + return f"https://{self.bucket}.s3.{self.region}.amazonaws.com/{object_name}" + + def upload(self, filepath: str) -> str: + object_name = os.path.basename(filepath) + self.client.upload_file(filepath, self.bucket, object_name) + return self.get_url(object_name) diff --git a/core/upload/static.py b/core/upload/static.py new file mode 100644 index 0000000..414d517 --- /dev/null +++ b/core/upload/static.py @@ -0,0 +1,25 @@ +import os +import shutil + +from env import DotEnv +from .base import AbstractUploader + + +class StaticUploader(AbstractUploader): + STATIC_DIR = "static" + + def __init__(self, server: str): + self.server = server + + @staticmethod + def from_settings(settings: DotEnv) -> "StaticUploader": + return StaticUploader(settings["SERVER"]) + + def get_url(self, uploaded_path: str) -> str: + return f"{self.server}/{uploaded_path}" + + def upload(self, filepath: str): + upload_path = os.path.join(StaticUploader.STATIC_DIR, filepath) + os.makedirs(os.path.dirname(upload_path), exist_ok=True) + shutil.copy(filepath, upload_path) + return f"{self.server}/{upload_path}" diff --git a/docker-compose.yml b/docker-compose.yml index 49da88d..9b475d8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -9,6 +9,7 @@ services: context: . volumes: # if you want to decrease your model download time, use this. - ../.cache/huggingface/:/root/.cache/huggingface/ + - ./static/:/app/static/ ports: - "8000:8000" env_file: diff --git a/env.py b/env.py index 2bf8a28..103cbfb 100644 --- a/env.py +++ b/env.py @@ -7,30 +7,33 @@ load_dotenv() class DotEnv(TypedDict): + OPENAI_API_KEY: str + + PORT: int + SERVER: str + LOG_LEVEL: str # optional - BOT_NAME: str - AWS_ACCESS_KEY_ID: str - AWS_SECRET_ACCESS_KEY: str - AWS_REGION: str - AWS_S3_BUCKET: str + BOT_NAME: str # optional + AWS_ACCESS_KEY_ID: str # optional + AWS_SECRET_ACCESS_KEY: str # optional + AWS_REGION: str # optional + AWS_S3_BUCKET: str # optional WINEDB_HOST: str # optional WINEDB_PASSWORD: str # optional - OPENAI_API_KEY: str BING_SEARCH_URL: str # optional BING_SUBSCRIPTION_KEY: str # optional SERPAPI_API_KEY: str # optional +PORT = int(os.getenv("PORT", 8000)) settings: DotEnv = { + "PORT": PORT, + "SERVER": os.getenv("SERVER", f"http://localhost:{PORT}"), + "OPENAI_API_KEY": os.getenv("OPENAI_API_KEY"), "LOG_LEVEL": os.getenv("LOG_LEVEL", "INFO"), "BOT_NAME": os.getenv("BOT_NAME", "Orca"), - "AWS_ACCESS_KEY_ID": os.getenv("AWS_ACCESS_KEY_ID"), - "AWS_SECRET_ACCESS_KEY": os.getenv("AWS_SECRET_ACCESS_KEY"), - "AWS_REGION": os.getenv("AWS_REGION"), - "AWS_S3_BUCKET": os.getenv("AWS_S3_BUCKET"), "WINEDB_HOST": os.getenv("WINEDB_HOST"), "WINEDB_PASSWORD": os.getenv("WINEDB_PASSWORD"), - "OPENAI_API_KEY": os.getenv("OPENAI_API_KEY"), "BING_SEARCH_URL": os.getenv("BING_SEARCH_URL"), "BING_SUBSCRIPTION_KEY": os.getenv("BING_SUBSCRIPTION_KEY"), "SERPAPI_API_KEY": os.getenv("SERPAPI_API_KEY"), diff --git a/pyproject.toml b/pyproject.toml index db9ec65..6096f47 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,10 @@ version = "0.1.0" description = "" authors = ["Taeho Lee ", "Chung Hwan Han "] readme = "README.md" +packages = [{include = "api"},{include = "core"}] +[tool.poetry.scripts] +serve = "api.main:serve" [tool.poetry.dependencies] python = "^3.10" diff --git a/s3.py b/s3.py deleted file mode 100644 index fb113fa..0000000 --- a/s3.py +++ /dev/null @@ -1,22 +0,0 @@ -import os -import boto3 - -from env import settings - - -def upload(file_name: str): - return upload_file(file_name, settings["AWS_S3_BUCKET"]) - - -def upload_file(file_name, bucket, object_name=None): - if object_name is None: - object_name = os.path.basename(file_name) - - s3_client = boto3.client( - "s3", - aws_access_key_id=settings["AWS_ACCESS_KEY_ID"], - aws_secret_access_key=settings["AWS_SECRET_ACCESS_KEY"], - ) - s3_client.upload_file(file_name, bucket, object_name) - - return f"https://{bucket}.s3.{settings['AWS_REGION']}.amazonaws.com/{object_name}" diff --git a/static/.gitkeep b/static/.gitkeep new file mode 100644 index 0000000..e69de29