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
18 changes: 18 additions & 0 deletions .github/workflows/lint.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
name: Lint

on:
push:
paths:
- '**.py'

jobs:
run-linters:
name: Run linters
runs-on: ubuntu-latest

steps:
- name: Check out Git repository
uses: actions/checkout@v4

- name: Lint check ruff
uses: chartboost/ruff-action@v1
24 changes: 24 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-json
- id: check-toml
- id: check-added-large-files
- id: check-merge-conflict

- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.11.4
hooks:
- id: ruff
args: [ --fix ]
- id: ruff-format

- repo: https://github.com/pycqa/isort
rev: 6.0.1
hooks:
- id: isort
args: ["--profile", "black"]
7 changes: 7 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt /app/
RUN pip install --no-cache-dir -r requirements.txt
COPY . /app/
CMD ["uvicorn", "copygo.main:app", "--host", "0.0.0.0", "--port", "8080", "--reload"]
EXPOSE 8080
31 changes: 30 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,30 @@
# copygo
# copygo# CopyGO

## Installation

To get started with this project, follow these steps:

1. **Clone the repository:**
```bash
git clone https://github.com/hanacard-data/copygo.git
cd copygo
```

2. **Set up a virtual environment:**
```
conda create -n copygo python=3.12
conda activate copygo
```

3. **Install dependencies:**
```bash
pip install -r requirements.txt
```

## Usage

To run the application, use the following command:

```sh
$ uvicorn copygo.main:app --port 8080 --reload
```
50 changes: 50 additions & 0 deletions copygo/clients.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
from openai import APIConnectionError, AsyncOpenAI
from retry import retry

from copygo.configs import config
from copygo.errors import OpenAIException
from copygo.logger import logger


class OpenAIClient:
def __init__(self, api_key: str):
self.client = AsyncOpenAI(api_key=api_key)

@retry(tries=5, delay=1, backoff=2, exceptions=APIConnectionError)
async def generate_copy(self, messages: list[dict[str, str]], **kwargs) -> str:
logger.info(f"Generating message completion for message: {messages}")
try:
response = await self.client.chat.completions.create(
model=config.LLM_MODEL,
messages=messages,
temperature=0,
**kwargs,
)
return response.choices[0].message.content
except Exception as e:
logger.error(e)
raise OpenAIException(f"Completion failed: {e}")

@retry(tries=5, delay=1, backoff=2, exceptions=APIConnectionError)
async def generate_image(
self,
prompt: str,
quality: str,
size: str,
style: str,
**kwargs,
) -> str:
logger.info(f"Generating completion for prompt: {prompt}")
try:
response = await self.client.images.generate(
prompt=prompt,
model=config.IMAGE_MODEL,
quality=quality,
size=size,
style=style,
**kwargs,
)
return response.data[0].url
except Exception as e:
logger.error(e)
raise OpenAIException(f"Completion failed: {e}")
45 changes: 45 additions & 0 deletions copygo/configs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import logging

from pydantic_settings import BaseSettings


class Config(BaseSettings):
TITLE: str = "CopyGo"
VERSION: str = "0.0.1"
DOCS_URL: str = "/docs"
REDOC_URL: str = "/redoc"
CONTACT: dict[str, str] = {
"name": "Hanacard CopyGo",
"email": "bigdata_card@hanafn.com",
}
SUMMARY: str = "CopyGo는 마케팅 전문가들이 필요한 모든 카피를 손쉽게 생성할 수 있도록 돕는 언어 모델입니다. 세분화된 마케팅 전략과 문체를 통해 고객의 니즈에 맞춘 맞춤형 콘텐츠를 빠르고 효율적으로 생성해 드립니다."
DESCRIPTION: str = ""
TAGS_METADATA: list[dict[str, str]] = [
{
"name": "copy",
"description": "Operations with copies.",
},
{
"name": "image",
"description": "Operations with images.",
},
]
LOG_LEVEL: int = logging.INFO
LLM_MODEL: str = "gpt-4o"
IMAGE_MODEL: str = "dall-e-3"

@property
def fastapi_kwargs(self) -> dict[str, str, list[dict[str]]]:
return {
"title": self.TITLE,
"version": self.VERSION,
"docs_url": self.DOCS_URL,
"redoc_url": self.REDOC_URL,
"contact": self.CONTACT,
"summary": self.SUMMARY,
"description": self.DESCRIPTION,
"openapi_tags": self.TAGS_METADATA,
}


config: Config = Config()
40 changes: 40 additions & 0 deletions copygo/errors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
from fastapi import Request
from fastapi.responses import ORJSONResponse
from starlette import status


class BaseAPIException(Exception):
def __init__(self, code: str, message: str):
self.code = code
self.message = message


class BaseAuthException(Exception):
def __init__(self, code: str, message: str):
self.code = code
self.message = message


class OpenAIException(BaseAPIException):
def __init__(self, message: str):
super().__init__("OPENAI_ERROR", message)


async def api_error_handler(_: Request, exc: BaseAPIException) -> ORJSONResponse:
return ORJSONResponse(
content={
"statusCode": exc.code,
"message": exc.message,
},
status_code=status.HTTP_400_BAD_REQUEST,
)


async def api_auth_error_handler(_: Request, exc: BaseAuthException) -> ORJSONResponse:
return ORJSONResponse(
content={
"statusCode": exc.code,
"message": exc.message,
},
status_code=status.HTTP_401_UNAUTHORIZED,
)
12 changes: 12 additions & 0 deletions copygo/logger.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import logging

from copygo.configs import config


def init_logger() -> logging.Logger:
logger = logging.getLogger("uvicorn")
logger.setLevel(config.LOG_LEVEL)
return logger


logger = init_logger()
26 changes: 26 additions & 0 deletions copygo/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import ORJSONResponse

from copygo.configs import config
from copygo.errors import (
BaseAPIException,
BaseAuthException,
api_auth_error_handler,
api_error_handler,
)
from copygo.routers.copies import copy_router
from copygo.routers.images import image_router

app = FastAPI(default_response_class=ORJSONResponse, **config.fastapi_kwargs)
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"],
allow_headers=["*"],
)
app.add_exception_handler(BaseAPIException, api_error_handler)
app.add_exception_handler(BaseAuthException, api_auth_error_handler)
app.include_router(copy_router)
app.include_router(image_router)
13 changes: 13 additions & 0 deletions copygo/prompts/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# ruff: noqa
from copygo.prompts.copytone import COPY_TONE_DESCRIPTION_MAPPER, COPY_TONE_MAPPER
from copygo.prompts.image import IMAGE_PROPMT
from copygo.prompts.message import MESSAGE_PROPMT
from copygo.prompts.purpose import (
CAMPAIGN_PURPOSE_DESCRIPTION_MAPPER,
CAMPAIGN_PURPOSE_MAPPER,
)
from copygo.prompts.strategy import (
COPY_STRATEGY_DESCRIPTION_MAPPER,
COPY_STRATEGY_NAME_MAPPER,
EXAMPLE_COPY_MAPPER,
)
51 changes: 51 additions & 0 deletions copygo/prompts/copytone.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
COPY_TONE_MAPPER: dict[int, str] = {
1: "친근한 톤",
2: "긍정적인 톤",
3: "전문적인 톤",
4: "정중한 톤",
5: "하나금융그룹 톤",
6: "마케팅 거장 William (Bill) Bernbach의 카피 톤",
7: "마케팅 거장 Dan Wieden의 카피 톤",
}

COPY_TONE_DESCRIPTION_MAPPER: dict[int, str] = {
1: """
일상적이고 편안한 말투를 사용하여 독자에게 친밀감을 느끼도록 유도하는 어조입니다. 가볍고 부드러운 분위기를 통해 독자가 부담 없이 메시지에 접근하고, 편안하게 내용을 이해하도록 돕습니다.

<핵심 요소>
- 문체: 가벼운 대화체를 사용하며, 질문이나 감탄 등을 활용하여 자연스럽고 부드러운 흐름을 만듭니다.
- 상대높임법: 반말은 지양하고 경어를 사용하되, 격식 없이 친근하게 말을 건네는 듯한 표현을 사용합니다.
- 어조: 따뜻하고 가벼운 어조를 사용하여 마치 친구에게 이야기하듯 편안하게 전달합니다.
- 분위기: 자연스럽고 다정한 분위기를 조성하여 독자에게 편안함과 친근함을 제공합니다.
""",
2: """
밝고 희망찬 분위기를 통해 고객에게 긍정적인 에너지와 동기를 부여하는 어조입니다. 유쾌하고 활기찬 느낌을 주는 동시에, 고객에게 제품/서비스를 통해 좋은 경험을 할 수 있다는 기대감을 심어줍니다. 마치 친근한 사람이 따뜻하게 격려하고 응원하는 듯한 느낌을 전달합니다. 고객에게 제품/서비스를 경험해보도록 부드럽게 권하는 느낌을 주며, 긍정적인 단어와 표현을 적극적으로 사용합니다.

<핵심 요소>
- 문체: 유쾌하고 활기찬 대화체를 사용하며, 짧고 간결한 문장으로 경쾌한 흐름을 유지합니다.
- 상대높임법: 공손한 높임말을 사용하되, 긍정적인 표현을 강조하여 부드럽고 친근한 느낌을 줍니다.
- 어조: 격려하고 응원하는 듯한 밝은 어조를 사용하며, 제품/서비스를 통해 얻을 수 있는 긍정적인 경험을 강조합니다.
- 분위기: 즐겁고 긍정적인 분위기를 조성하여 독자에게 활력과 기쁨을 선사합니다.
""",
3: """
정확한 정보와 논리적인 근거를 바탕으로 신뢰감을 주는 어조입니다. 객관적이고 명확한 설명을 통해 독자가 제품/서비스를 믿고 선택할 수 있도록 돕고, 합리적인 판단을 내릴 수 있도록 지원합니다. 마치 전문가가 차분하고 안정감 있는 말투로 설명하는 듯한 느낌을 전달합니다. 논리적인 근거와 데이터를 제시하여 주장에 대한 신뢰도를 높입니다. 깔끔하고 정확한 용어를 사용하며, 전문 용어 및 구체적인 정보를 활용하여 신뢰감을 더합니다.

<핵심 요소>
- 문체: 간결하고 명확한 서술형 문장을 사용하여 객관적인 정보를 전달합니다.
- 상대높임법: 경어를 사용하며, 논리적이고 단정한 표현으로 격식을 갖춥니다.
- 어조: 이성적이고 신뢰를 주는 어조를 사용하며, 제품의 기능이나 효용을 논리적으로 설명하여 전문성을 강조합니다.
- 분위기: 차분하고 신뢰감을 주는 분위기를 조성하여 독자가 정보에 집중하고 제품/서비스에 대해 신뢰를 느끼도록 합니다.
""",
4: """
공손하고 예의 바른 말투를 사용하여 독자에게 존중감을 느끼도록 하는 어조입니다. 격식을 갖춘 표현과 높임말을 통해 독자를 배려하고 존중하는 마음을 전달하며, 신뢰감을 높여 긍정적인 관계를 형성합니다. 마치 고객에게 최상의 서비스를 제공하려는 듯한 느낌을 전달합니다. 격식을 갖추되, 지나치게 딱딱하거나 부담스럽지 않도록 균형을 유지합니다. 상황에 맞는 적절한 높임말을 사용하는 것이 중요합니다. 정중하고 예의 바른 단어를 선택하여 사용합니다. 간결하고 명확한 문장을 사용하되, 필요에 따라 정중한 표현을 덧붙여 문장을 구성합니다.

<핵심 요소>
- 문체: 공손하고 격식을 갖춘 문장을 사용하여 고객에게 예의를 표현합니다.
- 상대높임법: 높임말을 일관되게 사용하여 고객에게 신뢰감과 존중을 느끼게 합니다.
- 어조: 차분하고 예의 있는 어조로, 정중하게 고객에게 제품/서비스의 가치를 설명합니다.
- 분위기: 신뢰할 수 있고 존중을 느낄 수 있는 격식 있는 분위기를 조성하여 고객이 특별 대우를 받는 듯한 느낌을 전달합니다.
""",
5: "초등학생도 이해할 수 있는 쉬운 단어와 간결한 문장을 사용해, 핵심 내용을 명확하게 전달합니다. 따뜻하고 친절한 어조를 유지해, 마치 친절한 은행원이 옆에서 차근차근 설명해주는 듯한 느낌을 주어 고객에게 편안함과 친밀감을 제공합니다. 한자어 대신 쉬운 한글 단어를 사용하고, 금융 용어나 추상적인 내용을 설명할 때는 비유와 구체적인 예시를 활용해 이해를 돕습니다. 긍정적이고 희망찬 메시지를 통해 고객에게 안정감을 주며, 일관된 어조로 메시지의 전달력을 높입니다.",
6: "William (Bill) Bernbach의 카피 스타일로 작성합니다. 도발적이고 고정관념을 깨는 접근으로 소비자의 흥미를 유발하는 것이 이 카피의 특징입니다. 직관적인 표현으로 고객의 관심과 즉각적인 참여를 유도하도록 구성합니다.",
7: "Dan Wieden의 카피 스타일로 작성합니다. 간결하면서도 행동을 유도하는 강렬한 표현을 사용해 참신하게 구성합니다.",
}
19 changes: 19 additions & 0 deletions copygo/prompts/image.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
IMAGE_PROPMT: str = """
당신은 '하나카드'의 마케팅 이미지를 생성하는 디자이너입니다.
고객의 요구 사항을 분석하고, 이를 기반으로 캠페인 목적을 달성할 수 있는 마케팅 이미지를 작성하십시오.

아래 절차를 단계별로 수행하고 최종 결과를 도출하십시오.

### 절차:
#### 1단계: 고객 요구 조건 분석
아래 고객이 원하는 캠페인의 제목과 설명을 분석하여, 캠페인 목적을 달성하기 위해 어떤 이미지가 효과적일지 고민하십시오.

- 캠페인 제목: {campaign_title}
- 캠페인 설명: {campaign_description}

#### 주의사항:
- 모든 캠페인은 '하나카드'에서 주관하는 캠페인입니다.

#### 2단계: 마케팅 이미지 생성
1단계에서 분석한 정보와 고민한 내용을 토대로, 캠페인 목적을 달성할 수 있는 이미지를 작성하십시오.
"""
Loading