NAV
HTTP

aiengine

Table of Contents

First Time Setup

Install Docker for Mac.

Clone lp-osx-setup and run the setup script.

Clone the lead-pages repo and run the bootstrap script.

Get a short-lived auth token from gcloud:

gcloud docker -a

Build and run your containers:

docker-compose up

After building and running your containers, you'll find the following services via HTTP:

Name URL
aiengine http://aiengine.docker
Datastore http://datastore.aiengine.docker
RabbitMQ http://rabbit.aiengine.docker
Profiler http://aiengine.docker/_profiler_

Local Installation

To install dependencies locally run the following commands:

  1. (optional) Install and configure pyenv
    1. brew install pyenv
    2. Run pyenv init and follow the directions
    3. Restart your terminal or run eval "$(pyenv init -)"
  2. brew install graphviz libmemcached
  3. Install poetry
  4. poetry install

Running the App Locally

  1. Update your docker/env file with OPENAI_API_KEY=[insert-api-key-here] (make sure to insert your API key!)
  2. Spin up your local environment from your lp-docker directory via ./up leadpages stargate fe-builder warehouse mandrel aiengine

Working with Poetry

Poetry is the package management solution used for this project. Consult its documentation for more in-depth usage.

Package Management

There are two files used for managing dependencies:

  1. pyproject.toml manages the indexes, packages and Python version required by the project.
  2. poetry.lock helps produce deterministic builds

Management of poetry.lock is automatic, and directly editing it should not be necessary under normal circumstances.

See the documentation for poetry for details on it's commands and using them to manage packages for your project.

Testing

Run the test suite with py.test. These commands assume you have the project virtual environment active, otherwise you will need to prepend them with poetry run

Filtering tests

py.test -k test_name

Running tests in parallel

py.test -nauto

Troubleshooting

Using the remote debugger

You can run the remote debugger at any point by doing

from common import start_debugger

start_debugger()

If you're debugging a handler, make a request to that handler and then connect to the debugger using a command like:

nc -C aiengine.docker 4444

or

socat readline tcp:aiengine.docker:4444

See the manual for a list of debugger commands.

Note: by default this port is not exposed through docker-compose.yml. This avoids clashing ports when running more than one API in parallel, and as a result, you will have to add it yourself. You can change the port by editing config/base.yaml.

Debugging in PyCharm

Note that the default configuration for this project has test coverage forced on. This breaks PyCharm's debugger, so in PyCharm, configure your default run/debug configuration for PyTest to include the --no-cov switch.

Building the Documentation

TODO: Fix this section, as it is outdated and does not function.

To build the documentation:

  1. cd docs/api
  2. bundle install
  3. bundle exec middleman build
  4. bundle exec middleman server
  5. Navigate to http://localhost:4567

Deployment

Pipeline is triggered by tagged push to master. kicking off a process that will result in your code changes deployed to test.

bumpversion patch git push origin master --tags

Test

After the tagged push to master, Cloud Build will fire a trigger entitled aiengine-image-build (which uses the cloudbuild.yaml file in the parent directory of this repo) this trigger builds a new image, runs tests and pushes it to Artifact Repository. The last build step in the cloudbuild.yaml triggers an additional cloud build trigger that handles the deployment ('aiengine-test-deployment'). This handles the deployment to test without any further action from you.

You can view the progress of the this by selecting the build from the leadpage-test Cloud Build Console.

To manually deploy to test with a different image:

  1. Navigate to the aiengine Artifact Registry folder
  2. Copy the value in the 'tags' column for the image that you want to deploy.
  3. Open leadpage-test cloud build trigger console and run the trigger entitled aiengine-test-deployment, pasting in the tag you copied in the previous step into the SHORT_SHA corresponding empty input box (where it says Value 1). then hit 'run trigger'.

Screen Shot 2023-09-19 at 10 09 15 AM

Screen Shot 2023-09-19 at 10 10 10 AM

The aiengine-test-deployment cloud build trigger changes the image value for ai-copy in our lp-flux repo. Once the steps are complete, Flux will reconcile the existing pods in the test cluster (api-cluster-1 in leadpage-test) with the new image, rolling out new pods and terminating the old ones. You can view the progress on that by running the following commands in a terminal:

Authenticate with test cluster:

gcloud container clusters get-credentials api-cluster-1 --zone us-central1-b --project leadpage-test

View pods getting cycled as flux reconciles:

kubectl get pods | grep aiengine

To confirm its running the right image, run the following command against one of the pods you got in the previous kubectl get pods command:

kubectl get pod **POD_NAME** -o yaml | grep "image:"

Production

For Production, the steps are similar to the manual test deployment.

  1. You will need to copy the value in the 'tags' column for the image you want deployed to production in the ai-copy Artifact Registry.
  2. Open lead-pages cloud build trigger console and run the trigger entitled aiengine-production-deployment, pasting in the tag you copied in the previous step into the SHORT_SHA corresponding empty input box (where it says Value 1). then hit 'run trigger'.

Similar to what happened in test, the cloud build trigger changes the image value in the lp-flux repo, and flux will reconcile the existing pods in the production cluster (api-cluster-1 in lead-pages) with the new image.

Viewing the progress is also the same as the test deployment:

Authenticate with prod cluster:

gcloud container clusters get-credentials api-cluster-1 --zone us-central1-b --project lead-pages

View pods getting cycled as flux reconciles:

kubectl get pods | grep aiengine

To confirm its running the right image, run the following command against one of the pods you got in the previous kubectl get pods command:

kubectl get pod **POD_NAME** -o yaml | grep "image:"

Logs

To view running application logs, there are 2 options. First way is to run the following query in Gcloud logs exlorer. To find prod logs, switch the project to 'lead-pages'

the other way is to view the running pod logs directly, by running following commands:

set Gcloud kube config to correct context for cluster/ project/ etc: for prod: gcloud container clusters get-credentials api-cluster-1 --zone us-central1-b --project leadpage-test

for test: gcloud container clusters get-credentials api-cluster-1 --zone us-central1-b --project lead-pages

get pod name. will look something like "ai-copy-87cbdd94f-jzt2d" kubectl get pods

retrieve logs and print to terminal. k logs ai-copy-87cbdd94f-jzt2d --follow

Secret Management

Ai-copy uses a token to authenticate with the OpenAI API. https://beta.openai.com/docs/introduction. The secret is stored in https://console.cloud.google.com/security/secret-manager?project=center-infra in the center-infra project. Under the name 'aicopy'

Contributing

Clone the repo, create a branch for your work, test and develop your changes, and submit a pull request.

License

© 2023 LeadPages. See the LICENSE.md file for details.

Brought to you by Disaster Artists (@iddqdkfa, @jcravenlp, @noerbot, @TZawalich) and the #ai-copy team (@nmabusthLP, @evanmcneely). 💥🎨💥

Endpoints

/aiengine/v1

Ping

/aiengine/v1/ping

A no-op endpoint that can be used to check what the currently-deployed version of the API is.

Request

GET /aiengine/v1/ping

Get the currently-deployed version of the API.

Body
Empty

Response

Body
_meta._version String
The current internal version number of the API.
_meta Dictionary
Responses
200 OK
Errors
Empty


Copy

/aiengine/v1/copy

Request

POST /aiengine/v1/copy

Generate text with OpenAI.

Body
description String
tone Enum('casual', 'convincing', 'enthusiastic', 'formal', 'funny', 'inspirational', 'joyful', 'passionate', 'thoughtful')
variants Number?
useCase Enum('headline', 'cta', 'expand', 'summary', 'rewrite')
randomizeTone Boolean?
planInfo PlanInfo

Response

Body
data List[String]
tone Enum('casual', 'convincing', 'enthusiastic', 'formal', 'funny', 'inspirational', 'joyful', 'passionate', 'thoughtful')
user String
usage UsageInfo
creditBalance CreditDetails
Responses
200 OK
Errors
Empty


Image

/aiengine/v1/image

Request

GET /aiengine/v1/image

Get the keywords and styles that can be used with image prompts.

Body
Empty

Response

Body
styles List[Style]
keywords List[KeywordsByCategory]
Responses
200 OK
Errors
Empty


Request

POST /aiengine/v1/image

Generate images with OpenAI.

Body
prompt String
keywords List?[String]
Keywords selected by the user to build the prompt
style Enum('none', 'photo', 'painting', 'sketch', 'watercolor', '3D')
Style selected by the user to build the prompt
variants Number?
width Number?
height Number?
response_format Enum('b64_json', 'url')
planInfo PlanInfo

Response

Body
images List[Image]
prompt String
keywords List[String]
style String
variants Number
user String
usage UsageInfo
creditBalance CreditDetails
Responses
200 OK
Errors
Empty


Credit

/aiengine/v1/credit

Request

GET /aiengine/v1/credit

Query string
level Enum('standard', 'start', 'pro', 'advanced', 'enterprise')
period Enum('month', 'year', '2years', 'lifetime')
trial Enum('1', '0')
user String?
The user's UUID

Response

Body
creditsAllotted Or[Number, String]
The number of credits allotted to the user based on their plan type and period
creditsUsed Number
The number of credits used so far this month
creditsRemaining Or[Number, String]
The number of credits remaining this month
Responses
200 OK
Errors
Empty


Request

PATCH /aiengine/v1/credit

Update the number of credits used by a user this month.

Body
creditsUsed Number(0 < n)
The number of credits used so far this month
user String
The user's UUID

Response

Body
creditsUsed Number(0 < n)
The number of credits used so far this month
user String
The user's UUID
Responses
200 OK
Errors
Empty


Definitions

PlanInfo

PlanInfo
planLevel Enum('standard', 'start', 'pro', 'advanced', 'enterprise')
planPeriod Enum('month', 'year', '2years', 'lifetime')
isTrial Boolean

UsageInfo

UsageInfo
total Number

CreditDetails

CreditDetails
creditsAllotted Or[Number, String]
The number of credits allotted to the user based on their plan type and period
creditsUsed Number
The number of credits used so far this month
creditsRemaining Or[Number, String]
The number of credits remaining this month

Style

Style
id Enum('none', 'photo', 'painting', 'sketch', 'watercolor', '3D')
label String
image String

KeywordsByCategory

KeywordsByCategory
label Enum('Composition', 'Color', 'Mood')
keywords List[Keyword]

Keyword

Keyword
id Enum('35mm', '4K', '50mm', 'abstract', 'aerial_view', 'airbrush', 'amigurumi', 'amorphous', 'anime', 'art_deco', 'autochrome', 'background', 'blocks', 'blurred', 'bokeh', 'botanical', 'charcoal', 'childs_drawing', 'childs_painting', 'clean', 'close_up', 'collage', 'color_blocks', 'colored_pencil', 'comic_book', 'contemporary', 'cubism', 'decorative', 'double_exposure', 'earthy', 'elegant', 'etching', 'expressionism', 'fss', 'fauvism', 'fish_eye', 'flatlay', 'futuristic', 'geometric', 'graffiti', 'HD', 'ink', 'kodachrome', 'lomography', 'long_shot', 'low_angle', 'minimalist', 'mosaic', 'motion_blur', 'organic', 'overhead', 'paper_embossing', 'papercraft', 'pencil', 'photoreal', 'polaroid', 'post_impressionism', 'rhythmic', 'romanticism', 'screenprint', 'silhouette', 'simple', 'sss', 'still_life', 'street_art', 'surreal', 'surreal_photo', 'telephoto', 'ultra_realistic', 'vector_art', 'vector_line', 'wallpaper', 'woodcut', 'analogue', 'auburn', ' black', 'black_white', 'blue', 'blue_hour', 'brick', 'cinematic', 'colorful', 'cream', 'cyan', 'dark', 'deep', 'duotone', 'glitter', 'gold', 'goldenhour', 'gray', 'green', 'ivory', 'maroon', 'midday', 'monochromatic', 'muted', 'neon', 'overcast', 'oversaturated', 'pastel', 'purple', 'red', 'rose_gold', 'sepia', 'silver', 'tan', 'warm', 'white', 'desolate', 'dramatic', 'dreamy', 'emotive', 'enchanting', 'energetic', 'fantasy', 'gloomy', 'gothic', 'melancholy', 'moody', 'negative', 'playful', 'positive', 'romantic', 'serene', 'tranquil', 'cold')
label String
category Enum('Composition', 'Color', 'Mood')

Image

Image
id String
uri String
width Number
height Number