[go: nahoru, domu]

Skip to content

Commit

Permalink
added sentence splitter and properly deployed
Browse files Browse the repository at this point in the history
  • Loading branch information
lhr0909 committed Jun 8, 2021
1 parent 3e04481 commit 7a99b62
Show file tree
Hide file tree
Showing 16 changed files with 1,256 additions and 49 deletions.
7 changes: 5 additions & 2 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# private key for JWT authentication
JWT_PRIVATE_KEY=
# use this if you are in China and need to lock in vendor to AWS
SERVERLESS_PLATFORM_VENDOR=aws

# private key for JWT authentication (see scripts/create_jwt.py to create a new private key)
JWT_SIGNING_KEY=

# API key for OpenAI
OPENAI_API_KEY=
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
.vscode
node_modules
__pycache__

.serverless
.requirements.zip

.env*
12 changes: 12 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,17 @@ export
create-jwt:
poetry run python ./scripts/create_jwt.py

sign-jwt:
poetry run python ./scripts/sign_jwt.py $(user)

authorizer:
poetry run python ./functions/authorizer.py

split-sentences:
poetry run python ./functions/split_sentences.py

local:
yarn serverless offline start $(args)

deploy:
yarn serverless deploy $(args)
54 changes: 54 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# OpenAI Serverless

A Stack for managing OpenAI API access using Serverless Framework against AWS.

## Prerequisites

- NodeJS LTS
- yarn
- pyenv
- Poetry
- make

## Install

```
yarn
poetry install
```

## Env Vars

Please check `.env.example`. Copy to `.env` and add the OpenAI API Key.

Run `make create-jwt` to create the JWK for signing. Put that in to `.env`.

Run `make sign-jwt user="<username>"` to get a signed JWT for requests.

## How to run locally

```
make local args="--stage dev"
```

## How to deploy

First make sure we have the right AWS profile set up, it is best to inject keys via `AWS_PROFILE` env var. (Put into `.env` is fine).

```
make deploy
```

## How to make requests

For example, with `/split_sentences`:

```
curl --request POST \
--url http://localhost:3000/dev/split_sentences \
--header 'Authorization: jwt <jwt_token>' \
--header 'Content-Type: application/json' \
--data '{
"sentence": "This is a sentence splitter that can split sentences into its own line. Please feel free to try it out!"
}'
```
Empty file added functions/__init__.py
Empty file.
58 changes: 58 additions & 0 deletions functions/authorizer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
try:
import unzip_requirements
except ImportError:
pass

import os
import json
from typing import Any, Dict
from dotenv import dotenv_values
from jwcrypto import jwk, jwt
from aws_lambda_powertools.utilities.typing import LambdaContext

config = {
**dotenv_values(os.path.join(os.path.dirname(__file__), '..', '.env')),
**os.environ,
}

signing_key = jwk.JWK.from_json(config.get('JWT_SIGNING_KEY'))

def generatePolicy(principalId, effect, methodArn):
authResponse = {}
authResponse['principalId'] = principalId

if effect and methodArn:
policyDocument = {
'Version': '2012-10-17',
'Statement': [
{
'Sid': 'FirstStatement',
'Action': 'execute-api:Invoke',
'Effect': effect,
'Resource': methodArn
}
]
}

authResponse['policyDocument'] = policyDocument

return authResponse

def handler(event: Dict[str, Any], context: LambdaContext) -> Dict[str, Any]:
try:
# test the JWT
token = str(event['authorizationToken']).replace('jwt ', '')
print('jwt token', token)
decrypted = jwt.JWT(key=signing_key, jwt=token)
print('jwt claims', decrypted.claims)

# Get principalId from idInformation
principalId = json.loads(decrypted.claims).get('uid')
except:
# Deny access if the token is invalid
return generatePolicy(None, 'Deny', event['methodArn'])

return generatePolicy(principalId, 'Allow', event['methodArn'])

if __name__ == '__main__':
print(signing_key.export_public())
83 changes: 83 additions & 0 deletions functions/split_sentences.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,93 @@
try:
import unzip_requirements
except ImportError:
pass

import os
import json
from typing import Any, Dict
from dotenv import dotenv_values
from aws_lambda_powertools.utilities.typing import LambdaContext
import openai

config = {
**dotenv_values(os.path.join(os.path.dirname(__file__), '..', '.env')),
**os.environ,
}

openai.api_key = config.get('OPENAI_API_KEY')

def handler(event: Dict[str, Any], context: LambdaContext) -> Dict[str, Any]:
try:
body = event.get('body')

if body is None:
return {
'statusCode': 400,
'body': json.dumps({ 'message': 'please provide POST body' }),
'headers': {
'Content-Type': 'application/json',
},
}

sentence = json.loads(body).get('sentence')

if sentence is None:
return {
'statusCode': 400,
'body': json.dumps({ 'message': 'please provide sentence in body JSON' }),
'headers': {
'Content-Type': 'application/json',
},
}

prompt = f'''Split the message into individual sentences. Put each sentence into its own line. Keep the original punctuations as much as possible.
####
Message:
Hi, my name is Simon. I am working on a sentence splitter powered by OpenAI
Sentences:
Hi, my name is Simon.
I am working on a sentence splitter powered by OpenAI
####
Message:
{sentence}
Sentences:'''
response = openai.Completion.create(
engine='curie-instruct-beta',
prompt=prompt,
max_tokens=256,
temperature=0.3,
stop=['####'],
)

sentences = response.get('choices', {})[0].get('text')

if sentences is None:
return {
'statusCode': 400,
'body': json.dumps({ 'message': 'no sentences produced' }),
'headers': {
'Content-Type': 'application/json',
},
}

sentences = list(filter(lambda s: s.strip(), str(sentences).split('\n')))

return {
'statusCode': 200,
'body': json.dumps({ 'sentences': sentences }),
'headers': {
'Content-Type': 'application/json',
},
}
except Exception as e:
return {
'statusCode': 400,
'body': json.dumps({ 'message': 'error occurred, please check logs' }),
'headers': {
'Content-Type': 'application/json',
},
}

if __name__ == '__main__':
print(config)
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"devDependencies": {
"dotenv-cli": "^4.0.0",
"serverless": "^2.44.0",
"serverless-dotenv-plugin": "^3.9.0",
"serverless-offline": "^7.0.0",
"serverless-python-requirements": "^5.1.1"
}
}
66 changes: 65 additions & 1 deletion poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ openai = "^0.6.4"
python-dotenv = "^0.17.1"
boto3 = "^1.17.89"
jwcrypto = "^0.9"
aws-lambda-powertools = "^1.16.1"

[tool.poetry.dev-dependencies]

Expand Down
5 changes: 5 additions & 0 deletions resources/authorizer_function.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
authorizer_function:
runtime: python3.8
handler: functions/authorizer.handler
environment:
JWT_SIGNING_KEY: ${env:JWT_SIGNING_KEY}
11 changes: 8 additions & 3 deletions resources/openai_functions.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
importer:
split_sentences:
runtime: python3.8
handler: split_sentences.handler
module: functions
handler: functions/split_sentences.handler
events:
- http:
path: split_sentences
method: post
cors: true
authorizer:
name: authorizer_function
resultTtlInSeconds: 0
identitySource: method.request.header.Authorization
identityValidationExpression: jwt .+
type: token
environment:
OPENAI_API_KEY: ${env:OPENAI_API_KEY}
6 changes: 3 additions & 3 deletions scripts/create_jwt.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from jwcrypto import jwk

key = jwk.JWK.generate(kty='RSA', size=2048)
key = jwk.JWK.generate(kty='oct', size=256)

print(key.export(private_key=True))
print(key.export(private_key=False))
print('Signing Key:')
print(key.export())
Loading

0 comments on commit 7a99b62

Please sign in to comment.