FEAT: First commit

This commit is contained in:
tmddn3070 2024-07-15 08:47:42 +00:00
commit 1bcb5672ad
27 changed files with 2279 additions and 0 deletions

11
.env.example Normal file
View File

@ -0,0 +1,11 @@
#DATABASE_CACHING
REDIS_HOST=
REDIS_PORT=
REDIS_DB=
REDIS_PASSWORD=
#DATABASE SAVING
SQLITE_PATH=
#SYSTEM
SENTRY_DSN=
MASTER_KEY=
CORS_ALLOW_ORIGINS=*

147
.gitignore vendored Normal file
View File

@ -0,0 +1,147 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
.pytest_cache/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
.python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# CMake
CMakeFiles/
CMakeCache.txt
CMakeScripts/
CTestTestfile.cmake
cmake_install.cmake
install_manifest.txt
Makefile
*.cmake
*.make
*.txt
.vscode/
.cache/
.env

19
Craft/__init__.py Normal file
View File

@ -0,0 +1,19 @@
import asyncio
from fastapi import FastAPI
from Craft.route import router
from Craft.middleware.cors_middlewares import init_cors_middleware
from Craft.middleware.logging_middlewares import init_logging_middleware
from Craft.middleware.processtime_middlewares import init_processtimemiddleware
from Craft.database.sqlite import Sqlite
from Craft.module.sentry import Sentry
app = FastAPI()
init_cors_middleware(app)
init_logging_middleware(app)
init_processtimemiddleware(app)
Sentry()
app.include_router(router)

38
Craft/controller/merge.py Normal file
View File

@ -0,0 +1,38 @@
import sentry_sdk
from Craft.module.logging import Logger
from Craft.model.merge import MergeOutput, MergeInput
from Craft.module.ml import Engine
from Craft.database.redis.caching import RedisCaching
from Craft.database.sqlite.data import SqliteDatabase
class MergeController:
def __init__(self):
self.logger = Logger()
self.engine = Engine()
self.redis_caching = RedisCaching()
self.sqlite_database = SqliteDatabase()
async def merge(self, model: MergeInput) -> MergeOutput:
try:
cached = await self.redis_caching.cache_get(model.first_word, model.second_word)
if cached:
return MergeOutput(status=1, response=cached)
data = await self.engine.generate(model.first_word, model.second_word)
await self.redis_caching.cache_set(model.first_word, model.second_word, data)
await self.sqlite_database.sets(model.first_word, model.second_word, data.emoji, data.word)
return MergeOutput(status=1, response={"emoji": data.emoji, "word": data.word})
except Exception as e:
self._log_and_capture_exception(e, "Error in Merge Controller")
return MergeOutput(status=-1, error=str(e))
async def increasevalue_backgroundtasks(self, first_word: str, second_word: str) -> None:
try:
await self.sqlite_database.update(first_word, second_word)
except Exception as e:
self._log_and_capture_exception(e, "Error in Increase Value BackgroundTasks")
raise
def _log_and_capture_exception(self, exception: Exception, message: str) -> None:
self.logger.error(f"{message}: {exception}")
sentry_sdk.capture_exception(exception)

View File

@ -0,0 +1,40 @@
import os
import redis.asyncio as redis
from dotenv import find_dotenv, load_dotenv
from Craft.database.sqlite import Sqlite
load_dotenv(find_dotenv())
class Redis:
def __init__(self):
self.redis_host: str = os.getenv("REDIS_HOST")
self.redis_port: int = os.getenv("REDIS_PORT")
self.redis_db: int = os.getenv("REDIS_DB")
self.redis_password: str = os.getenv("REDIS_PASSWORD")
self.redis_pool = redis.ConnectionPool(
host=self.redis_host,
port=self.redis_port,
db=self.redis_db,
password=self.redis_password
)
self.redis_instance = redis.Redis(connection_pool=self.redis_pool)
async def __aenter__(self):
return self.redis_instance
async def __aexit__(self, exc_type, exc, tb):
await self.redis_instance.close()
async def sync_data(self) -> None:
await self.redis_instance.flushdb()
async with Sqlite() as sqlite:
await sqlite.execute("SELECT * FROM Craft")
data = await sqlite.fetchall()
for row in data:
key1, key2, value_emoji, value_word, _ = row
self.redis_instance.hmset(f"CraftCached:{key1}:{key2}", {"emoji": value_emoji, "word": value_word})

View File

@ -0,0 +1,82 @@
import asyncio
import sentry_sdk
from Craft.database.redis import Redis
from types import SimpleNamespace
class RedisCaching:
@staticmethod
def _create_cache_key(key1: str, key2: str) -> str:
return f"CraftCached:{key1}:{key2}"
@staticmethod
def _validate_key(key: str) -> None:
if not isinstance(key, str) or not key:
raise ValueError("Invalid key")
@staticmethod
def _validate_value(value: SimpleNamespace) -> None:
if not isinstance(value, SimpleNamespace) or not hasattr(value, 'emoji') or not hasattr(value, 'word'):
raise ValueError("Invalid value")
@staticmethod
async def cache_get(key1: str, key2: str) -> dict:
RedisCaching._validate_key(key1)
RedisCaching._validate_key(key2)
key = RedisCaching._create_cache_key(key1, key2)
async with Redis() as redis:
try:
data = await redis.hgetall(key)
if not data:
key = RedisCaching._create_cache_key(key2, key1)
data = await redis.hgetall(key)
return data
except Exception as e:
sentry_sdk.capture_exception(e)
raise e
@staticmethod
async def cache_set(key1: str, key2: str, value: SimpleNamespace) -> None:
RedisCaching._validate_key(key1)
RedisCaching._validate_key(key2)
RedisCaching._validate_value(value)
key = RedisCaching._create_cache_key(key1, key2)
async with Redis() as redis:
try:
await redis.hset(key, mapping={"emoji": value.emoji, "word": value.word})
except Exception as e:
sentry_sdk.capture_exception(e)
raise e
@staticmethod
async def cache_delete(key1: str, key2: str) -> None:
RedisCaching._validate_key(key1)
RedisCaching._validate_key(key2)
key = RedisCaching._create_cache_key(key1, key2)
async with Redis() as redis:
try:
await redis.delete(key)
except Exception as e:
sentry_sdk.capture_exception(e)
raise e
@staticmethod
async def cache_keys() -> list:
async with Redis() as redis:
try:
return await redis.keys("CraftCached:*")
except Exception as e:
sentry_sdk.capture_exception(e)
raise e
@staticmethod
async def cache_flush() -> None:
async with Redis() as redis:
try:
await redis.flushdb()
except Exception as e:
sentry_sdk.capture_exception(e)
raise e

View File

@ -0,0 +1,37 @@
import os
import aiosqlite
from dotenv import find_dotenv, load_dotenv
load_dotenv(find_dotenv())
class Sqlite:
def __init__(self):
self.db_path = os.getenv("SQLITE_PATH")
async def __aenter__(self):
self.conn = await aiosqlite.connect(self.db_path)
return await self.conn.cursor()
async def __aexit__(self, exc_type, exc, tb):
await self.conn.commit()
await self.conn.close()
async def init_database(self):
async with self as cursor:
await cursor.execute(
"""CREATE TABLE IF NOT EXISTS Craft (
key1 VARCHAR(255),
key2 VARCHAR(255),
value_emoji VARCHAR(255),
value_word VARCHAR(255),
used_count INTEGER,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (key1, key2)
)"""
)
await self.conn.commit()

View File

@ -0,0 +1,33 @@
from Craft.database.sqlite import Sqlite
from typing import Optional, List, Tuple
class SqliteDatabase:
@staticmethod
async def get(key1: str, key2: str) -> Optional[Tuple]:
async with Sqlite() as sqlite:
await sqlite.execute("SELECT * FROM Craft WHERE key1 = ? AND key2 = ?", (key1, key2))
data = await sqlite.fetchone()
return data
@staticmethod
async def sets(key1: str, key2: str, value_emoji: str, value_word: str) -> None:
async with Sqlite() as sqlite:
await sqlite.execute("INSERT INTO Craft (key1, key2, value_emoji, value_word, used_count) VALUES (?, ?, ?, ?, ?)", (key1, key2, value_emoji, value_word, 1))
@staticmethod
async def update(key1: str, key2: str) -> None:
async with Sqlite() as sqlite:
await sqlite.execute("UPDATE Craft SET used_count = used_count + 1 WHERE key1 = ? AND key2 = ?", (key1, key2))
@staticmethod
async def delete(key1: str, key2: str) -> None:
async with Sqlite() as sqlite:
await sqlite.execute("DELETE FROM Craft WHERE key1 = ? AND key2 = ?", (key1, key2))
@staticmethod
async def keys() -> List[Tuple]:
async with Sqlite() as sqlite:
await sqlite.execute("SELECT * FROM Craft")
data = await sqlite.fetchall()
return data

46
Craft/enums.py Normal file
View File

@ -0,0 +1,46 @@
from enum import Enum
class LoggingLevel(Enum):
DEBUG = "DEBUG"
INFO = "INFO"
WARNING = "WARNING"
ERROR = "ERROR"
CRITICAL = "CRITICAL"
@classmethod
def get_level(cls, level: int):
for key, value in cls.__dict__.items():
if value == level:
return value
return None
@classmethod
def get_level_value(cls, level: str):
return getattr(cls, level.upper(), None)
def __str__(self):
return self.value
def __int__(self):
return self.value
class LoggingColor(Enum):
DEBUG = "blue"
INFO = "green"
WARNING = "yellow"
ERROR = "red"
CRITICAL = "red"
@classmethod
def get_color(cls, level: int):
for key, value in cls.__dict__.items():
if value == level:
return value
return None
@classmethod
def get_color_value(cls, level: str):
return getattr(cls, level.upper(), None)

View File

@ -0,0 +1,18 @@
import os
from dotenv import find_dotenv, load_dotenv
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
load_dotenv(find_dotenv())
def init_cors_middleware(app: FastAPI):
app.add_middleware(
CORSMiddleware,
allow_origins=os.getenv("CORS_ALLOW_ORIGINS").split(","),
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)

View File

@ -0,0 +1,32 @@
from fastapi import FastAPI, Request, HTTPException, Depends, status, Response
from fastapi.responses import JSONResponse
from datetime import datetime
from Craft.enums import LoggingLevel
from Craft.module.logging import Logger
def init_logging_middleware(app: FastAPI):
@app.middleware("http")
async def logging_middleware(request: Request, call_next):
try:
logger = Logger()
request_body = await request.body()
request = Request(
scope=request.scope,
receive=lambda: {"type": "http.request", "body": request_body},
)
response = await call_next(request)
#oneline_body = request_body.decode("utf-8").replace("\n", "")
logger.info(
f"Request: {request.method} {request.url.path} {request.client.host} {request.client.port}"
)
return response
except Exception as e:
print(e)
return JSONResponse(
status_code=500, content={"message": "Internal Server Error"}
)
return app

View File

@ -0,0 +1,26 @@
import fastapi
import datetime
from fastapi import FastAPI, Request
from fastapi.middleware import Middleware
from fastapi.responses import JSONResponse
from typing import Any, Dict, Callable, Coroutine
def init_processtimemiddleware(app: FastAPI):
@app.middleware("http")
async def process_time_middleware(request: Request, call_next):
try:
start_time = datetime.datetime.now()
response = await call_next(request)
end_time = datetime.datetime.now()
response.headers["process-Time"] = (
str(round((end_time - start_time).total_seconds(), 1)) + "s"
)
return response
except Exception as e:
return JSONResponse(
status_code=500, content={"message": "Internal Server Error"}
)
return app

0
Craft/model/__init__.py Normal file
View File

14
Craft/model/merge.py Normal file
View File

@ -0,0 +1,14 @@
from pydantic import BaseModel, Field
from typing import Optional, Dict
class MergeInput(BaseModel):
first_word: str = Field(..., title="First word", description="The first word to be merged")
second_word: str = Field(..., title="Second word", description="The second word to be merged")
class MergeOutput(BaseModel):
status: int = Field(..., title="Status", description="The status of the response")
response: Optional[Dict[str, str]] = Field(None, title="Response", description="The response data if any")
error: Optional[str] = Field(None, title="Error", description="The error message if any")

28
Craft/module/logging.py Normal file
View File

@ -0,0 +1,28 @@
import loguru
from Craft.enums import LoggingLevel, LoggingColor
class Logger:
def __init__(self):
self.logger = loguru.logger
self.logger.add("logs/latest.log", level="DEBUG", colorize=True, format="<green>{time:YYYY-MM-DD HH:mm:ss}</green> | <level>{level: <8}</level> | <cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> - <level>{message}</level>")
def log(self, level: int, message: str):
self.logger.log(level, message, color=LoggingColor.get_color(level))
def debug(self, message: str):
self.log(str(LoggingLevel.DEBUG), message)
def info(self, message: str):
self.log(str(LoggingLevel.INFO), message)
def warning(self, message: str):
self.log(str(LoggingLevel.WARNING), message)
def error(self, message: str):
self.log(str(LoggingLevel.ERROR), message)
def critical(self, message: str):
self.log(str(LoggingLevel.CRITICAL), message)

View File

@ -0,0 +1,54 @@
import asyncio
from concurrent.futures import ThreadPoolExecutor
from dotenv import find_dotenv, load_dotenv
from ollama import generate
from Craft.module.ml.prompt import generate_system, generate_user, generate_system_eng, generate_user_eng
from Craft.module.ml.util import extract_emoji_and_text
load_dotenv(find_dotenv())
class Engine:
def __init__(self):
self.key = None
def _generate(self, first_word: str, second_word: str, eng_result: str) -> dict:
return generate(
model="infcraft:latest",
system=generate_system(first_word, second_word, eng_result),
prompt=generate_user(first_word, second_word),
keep_alive=60*60*24,
context=None,
options={
"seed": 0,
"temperature": 0.4,
"top_p": 0.85,
"top_k": 0.1,
"max_tokens": 32,
"main_gpu": 0,
}
)
async def _generate_eng(self, first_word: str, second_word: str) -> dict:
system = await generate_system_eng(first_word, second_word)
prompt = await generate_user_eng(first_word, second_word)
return generate(
model="mistral:latest",
system=system,
prompt=prompt,
keep_alive=60*60*24,
options={
"seed": 0,
"temperature": 0.8,
"top_p": 1,
"top_k": 0.1,
"max_tokens": 64,
"main_gpu": 0,
}
)
async def generate(self, first_word: str, second_word: str) -> str:
loop = asyncio.get_running_loop()
eng_result = await self._generate_eng(first_word, second_word)
with ThreadPoolExecutor() as executor:
data = await loop.run_in_executor(executor, self._generate, first_word, second_word, eng_result['response'])
return extract_emoji_and_text(data['response'])

63
Craft/module/ml/prompt.py Normal file
View File

@ -0,0 +1,63 @@
import aiogoogletrans
import re
from typing import Optional
translator = aiogoogletrans.Translator()
def generate_system(first_word: str, second_word: str, eng_result: Optional[str] = None) -> str:
first_word = first_word.strip()
second_word = second_word.strip()
prompt = (
"당신은 Infinite Craft라는 게임의 핵심 부분을 담당하는 도우미입니다. 주어진 두 개의 요소와 이모지를 결합 및 활용하여 연관된 새로운 요소를 만들어야 합니다." +
"이 게임은 한국인을 타깃으로 하고 있기 때문에 한국 정서나 문화 또는 사회에 대한 지식을 사용해야합니다." +
"무조건 항상 입력에 따라 이러한 결과를 생성해야 합니다. 입력 형식은 '(이모지) (요소) + (이모지) (요소)'입니다." +
"그리고 당신의 결과는 항상 '(이모지) (요소)' 형식이어야 합니다." +
"그리고 항상 이모지와 요소가 최대한 관련이 있도록 하고 일관되게 유지하세요. " +
"이모지와 요소는 예시에 아무런 제한되지 않으며, 무엇이든 만들 수 있습니다. 주어진 형식으로 항상 응답하세요." +
"가장 중요한 규칙은 답변에 '{},{}'단어 자체를 직접적으로는 사용 할 수 없다는 것입니다. 즉, 단어를 결합한 결과를 답하십시오." +
"답변은 한글로 이루어진 명사(고유명사나 다른 한 요소 가능) 여야 합니다." +
"두 요소의 순서는 중요하지 않으며, 두 요소 모두 똑같이 중요합니다." +
"답변은 무조건 두 요소와 그 요소의 맥락또는 의미와 관련되어야 합니다." +
"답변은 물건, 재료, 사람, 회사, 동물, 직업, 음식, 장소, 객체, 감정, 사건, 개념, 자연 현상, 한국의 사회현상, 한국의 특정 밈, 트랜드, 게임, 신체 부위, 차량, 스포츠, 의류, 가구, 기술, 건물, 악기, 음료, 식물, 학문등 기타 명사일 수 있습니다." +
"답변을 생성할때 정확도를 향상시키기 위해 영어 답변을 참고하여 생성하십시오. " +
"영어 답변: '{}'" +
"답변 예시:" +
"'🌬️ 바람 + 🌱 식물' = 🌼 민들레'" +
"'🌍 지구 + 💧 물' = 🌱 식물" +
"'🌍 지구 + 🔥 불' = 🌋 용암" +
"'🌋 용암 + 🌋 용암' = 🌋 화산" +
"'💧 물 + 🌬️ 바람' = 🌊 파도 " +
"'🍄 버섯 + 🎮 닌텐도' = 🎮 마리오" +
"'💙 파랑 + 💽 저장소' = 🎮 블루아카이브" +
"<end_of_turn>"
).format(re.sub(r'[^\w\s]', '', first_word), re.sub(r'[^\w\s]', '', second_word), eng_result)
return prompt
def generate_user(first_word: str, second_word: str) -> str:
prompt = "{} + {}".format(first_word, second_word)
return prompt
async def generate_system_eng(first_word: str, second_word: str) -> str:
first_word = first_word.strip()
second_word = second_word.strip()
word = f"{first_word},{second_word}"
translated = await translator.translate(word, dest='en')
first_word, second_word = translated.text.split(',')
prompt = (
"You are a helpful assistant that helps people to craft new things by combining two words into a new word. " +
"The most important rules that you have to follow with every single answer that you are not allowed to use the words '{} and {}' as part of your answer and that you are only allowed to answer with one thing. " +
"DO NOT INCLUDE THE WORDS '{} and {}' as part of the answer!!!!! The words '{} and {}' may NOT be part of the answer. " +
"No sentences, no phrases, no multiple words, no punctuation, no special characters, no numbers, no emojis, no URLs, no code, no commands, no programming" +
"The answer has to be a noun. " +
"The order of the both words does not matter, both are equally important. " +
"The answer has to be related to both words and the context of the words. " +
"The answer can either be a combination of the words or the role of one word in relation to the other. " +
"Answers can be things, materials, people, companies, animals, occupations, food, places, objects, emotions, events, concepts, natural phenomena, body parts, vehicles, sports, clothing, furniture, technology, buildings, technology, instruments, beverages, plants, academic subjects and everything else you can think of that is a noun."
).format(first_word, second_word, first_word, second_word, first_word, second_word)
return prompt
async def generate_user_eng(first_word: str, second_word: str) -> str:
word = f"{first_word},{second_word}"
translated = await translator.translate(word, dest='en')
first_word, second_word = translated.text.split(',')
prompt = "Reply with the result of what would happen if you combine '{} and {}'. The answer has to be related to both words and the context of the words and may not contain the words themselves.".format(first_word, second_word)
return prompt

9
Craft/module/ml/util.py Normal file
View File

@ -0,0 +1,9 @@
import re
import emoji
from types import SimpleNamespace
def extract_emoji_and_text(input_string):
emoji_data = emoji.emoji_list(input_string)
emojis = emoji_data[0]['emoji'] if emoji_data else None
text = re.sub(r'[^a-zA-Z0-9ㄱ-ㅎ가-힣]', '', input_string)
return SimpleNamespace(emoji=emojis, word=text)

27
Craft/module/sentry.py Normal file
View File

@ -0,0 +1,27 @@
import os
import sentry_sdk
from dotenv import find_dotenv, load_dotenv
from typing import Union
from sentry_sdk.integrations.fastapi import FastApiIntegration
from sentry_sdk.integrations.loguru import LoguruIntegration
from sentry_sdk.integrations.redis import RedisIntegration
from Craft.enums import LoggingLevel
load_dotenv(find_dotenv())
class Sentry:
def __init__(self):
self.sentry_dsn = os.getenv("SENTRY_DSN")
sentry_sdk.init(
dsn=self.sentry_dsn,
integrations=[
FastApiIntegration(),
LoguruIntegration(),
RedisIntegration(),
],
traces_sample_rate=1.0,
profiles_sample_rate=1.0,
)

16
Craft/module/templates.py Normal file
View File

@ -0,0 +1,16 @@
INDEX_TEMPLATE: str = """
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://cdn.tailwindcss.com"></script>
<title>CraftINF</title>
</head>
<body class="bg-gray-800">
<div class="flex flex-col items-center justify-center h-screen">
<h1 class="text-4xl text-white">Welcome to CraftINF</h1>
</div>
</body>
</html>
"""

9
Craft/route/__init__.py Normal file
View File

@ -0,0 +1,9 @@
from fastapi import APIRouter
from Craft.route.default import app as default_router
from Craft.route.merge import app as merge_router
router = APIRouter()
router.include_router(default_router, tags=["Default"])
router.include_router(merge_router, tags=["AI"], prefix="/v1")

17
Craft/route/default.py Normal file
View File

@ -0,0 +1,17 @@
from fastapi import APIRouter, Request, HTTPException, Depends, status, Response
from fastapi.responses import HTMLResponse
from fastapi.openapi.docs import get_swagger_ui_html
from Craft.module.templates import INDEX_TEMPLATE
app = APIRouter()
@app.get("/", response_class=HTMLResponse)
async def index(request: Request):
return HTMLResponse(INDEX_TEMPLATE)
@app.get("/docs", response_class=HTMLResponse)
async def docs(request: Request):
return get_swagger_ui_html(openapi_url="/openapi.json", title="docs")

14
Craft/route/merge.py Normal file
View File

@ -0,0 +1,14 @@
from fastapi import APIRouter
from fastapi.responses import HTMLResponse
from fastapi.background import BackgroundTasks
from Craft.controller.merge import MergeController
from Craft.model.merge import MergeOutput, MergeInput
app = APIRouter()
@app.post("/merge", response_model=MergeOutput)
async def merge(model: MergeInput, background_tasks: BackgroundTasks):
background_tasks.add_task(MergeController().increasevalue_backgroundtasks, model.first_word, model.second_word)
return await MergeController().merge(model)

10
infcraft.modelfile Normal file
View File

@ -0,0 +1,10 @@
FROM gemma2:27b-instruct-q3_K_M
TEMPLATE "{{ if .System }}<bos><start_of_turn>system
{{ .System }}{{ end }}
<start_of_turn>user
{{ .Prompt }}<end_of_turn>
<start_of_turn>model
{{ .Response }}<end_of_turn>
"
PARAMETER stop <start_of_turn>
PARAMETER stop <end_of_turn>

1397
poetry.lock generated Normal file

File diff suppressed because it is too large Load Diff

25
pyproject.toml Normal file
View File

@ -0,0 +1,25 @@
[tool.poetry]
name = "kocraft"
version = "1.0.0"
description = "Korean Infinite Craft Backend"
authors = ["tmddn3070 <tmddn3070@gmail.com>"]
license = "apache-2"
readme = "README.md"
package-mode = false
[tool.poetry.dependencies]
python = "^3.10"
fastapi = "^0.111.0"
uvicorn = {extras = ["standard"], version = "^0.30.1"}
redis = {extras = ["hiredis"], version = "^5.0.7"}
sentry-sdk = {extras = ["fastapi", "loguru"], version = "^2.9.0"}
python-dotenv = "^1.0.1"
loguru = "^0.7.2"
ollama = "^0.2.1"
emoji = "^2.12.1"
aiosqlite = "^0.20.0"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

67
test.py Normal file
View File

@ -0,0 +1,67 @@
import os
import re
import asyncio
import emoji
import aiogoogletrans
from concurrent.futures import ThreadPoolExecutor
from dotenv import find_dotenv, load_dotenv
from ollama import generate
from Craft.module.ml.prompt import generate_system, generate_user, generate_system_eng, generate_user_eng
from Craft.module.ml.util import extract_emoji_and_text
load_dotenv(find_dotenv())
translator = aiogoogletrans.Translator()
class Engine:
def __init__(self):
self.key = None
def _generate(self, first_word: str, second_word: str, eng_result: str) -> str:
gen_data = generate(
model="infcraft:minial",
system=generate_system(first_word, second_word, eng_result),
prompt=generate_user(first_word, second_word),
keep_alive=60*60*24,
context=None,
options={
"seed" : 0,
"temperature" : 0.2,
"top_p" : 0.85,
"top_k" : 0.1,
"max_tokens" : 32,
"main_gpu" : 0,
}
)
# 반환된 값의 response에서 이모지만 추출
return gen_data
async def _generate_eng(self, first_word: str, second_word: str) -> str:
gen_data = generate(
model="mistral:latest",
system=await generate_system_eng(first_word, second_word),
prompt=await generate_user_eng(first_word, second_word),
keep_alive=60*60*24,
options={
"seed" : 0,
"temperature" : 0.2,
"top_p" : 1,
"top_k" : 0.1,
"max_tokens" : 64,
"main_gpu" : 0,
}
)
return gen_data
async def generate(self, first_word: str, second_word: str) -> str:
with ThreadPoolExecutor() as executor:
loop = asyncio.get_event_loop()
eng_result = await self._generate_eng(first_word, second_word)
data = await loop.run_in_executor(executor, self._generate, first_word, second_word, eng_result['response'])
return data
data = asyncio.run(Engine().generate("👮 윤석열", "👮 이재명"))
print(extract_emoji_and_text(data['response']))