Skip to content

Commit

Permalink
Merge branch 'main' into feature/#231
Browse files Browse the repository at this point in the history
  • Loading branch information
Kota8102 committed Nov 15, 2024
2 parents fa810b6 + 573df8a commit 7803976
Show file tree
Hide file tree
Showing 11 changed files with 2,353 additions and 2,387 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,8 @@ src/frontend/*.njsproj
src/frontend/*.sln
src/frontend/*.sw?

# Pytest cache files
src/backend/lambda/**/__pycache__/**

.DS_Store
node_modules
873 changes: 871 additions & 2 deletions docs/arch.drawio.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,4 @@ known-third-party = ["fastapi", "pydantic", "starlette"]

[tool.pytest.ini_options]
pythonpath = "src/backend/lambda"
testpaths = ["src/backend/test/pytest"]
testpaths = ["src/backend/test/pytest"]
184 changes: 184 additions & 0 deletions src/backend/lambda/title_generate/title_generate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
import json
import logging
import os
import urllib.parse
import urllib.request

import boto3

logger = logging.getLogger(__name__)
# ロガーの設定
formatter = logging.Formatter(
"[%(asctime)s - %(levelname)s - %(filename)s(func:%(funcName)s, line:%(lineno)d)] %(message)s"
)
handler = logging.StreamHandler()
handler.setFormatter(formatter)
logger.addHandler(handler)
logger.setLevel(logging.INFO)


def lambda_handler(event, context):
"""
AWS Lambdaハンドラ関数。DynamoDBストリームのレコードを処理し、日記のタイトルを生成します。
この関数は以下を行います:
- DynamoDBでの 'INSERT' 操作によってトリガーされたイベントを処理。
- イベントから日記の内容を抽出。
- OpenAI API(ChatGPT)を使用して日記内容からタイトルを生成。
- 生成したタイトルを同じDynamoDBレコードに保存。
Args:
event (dict): DynamoDBストリームイベントの詳細情報、変更されたレコードを含む。
context (object): 実行時情報を提供するコンテキストオブジェクト。
Returns:
dict: ステータスコード、ヘッダー、および本文を含むHTTPレスポンス。
"""
logger.info("title generate lambda start")
try:
record = event["Records"][0]
if record["eventName"] == "INSERT":
logger.info(f"content: {record['dynamodb']['NewImage']['content']['S']}")
diary_content = record["dynamodb"]["NewImage"]["content"]["S"]
generated_title = generate_title_from_content(diary_content)
save_title_to_dynamodb(generated_title, record)

return {
"statusCode": 200,
"body": json.dumps("Processed DynamoDB Stream records."),
"headers": {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*",
},
}
except Exception as e:
return {
"statusCode": 400,
"body": json.dumps(f"An error occurred: {str(e)}"),
"headers": {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*",
},
}


def generate_title_from_content(diary_content):
"""ChatGPTを使用して日記の内容からタイトルを生成します。
Args:
diary_content (string): 日記の内容
Returns:
string: 生成されたタイトル
"""
api_endpoint = "https://api.openai.com/v1/chat/completions"

try:
api_key = get_parameter_from_parameter_store("OpenAI_API_KEY")
except Exception as e:
logger.error(
f"An error occurred during getting parameter from parameter store: {str(e)}"
)
raise

system_message = """Please write a title of 10 words or less based on the contents of your diary.""".strip()
request_data = {
"model": "gpt-4o-mini",
"messages": [
{"role": "system", "content": system_message},
{"role": "user", "content": diary_content},
],
"temperature": 0.7,
}
try:
response = send_request_to_openai_api(api_endpoint, api_key, request_data)
generated_title = json.loads(response)["choices"][0]["message"]["content"]
return generated_title
except urllib.error.HTTPError as e:
if e.code == 429:
logger.error(
"API request rate limit exceeded or usage has exceeded billing threshold. "
"Please wait and try again or check your account billing details."
)
else:
logger.error(f"An HTTP error occurred during OpenAI API call: {str(e)}")
raise
except Exception as e:
logger.error(f"An unexpected error occurred during OpenAI API call: {str(e)}")
raise


def save_title_to_dynamodb(generated_title, record):
"""生成されたタイトルをDynamoDBに保存します。
Args:
generated_title (string): 生成されたタイトル
record (dict): Lambda関数のイベントレコード
Returns:
none
"""
dynamodb = boto3.resource("dynamodb")
table_name = os.environ["TABLE_NAME"]
table = dynamodb.Table(table_name)
try:
dynamodb_response = table.update_item(
Key={
"user_id": record["dynamodb"]["NewImage"]["user_id"]["S"],
"date": record["dynamodb"]["NewImage"]["date"]["S"],
},
UpdateExpression="set title = :t",
ExpressionAttributeValues={":t": generated_title},
)
logger.info(f"DynamoDB update response: {dynamodb_response}")
except Exception as e:
logger.error(f"An error occurred during DynamoDB update: {str(e)}")
raise


def get_parameter_from_parameter_store(parameter_name):
"""OpenAI APIトークンを取得
Args:
parameter_name (string): OpenAIトークンのパラメータ名
Returns:
string: OpenAI APIトークン
"""

ssm = boto3.client(
"ssm", region_name=os.environ.get("AWS_REGION", "ap-northeast-1")
)
try:
response = ssm.get_parameter(Name=parameter_name, WithDecryption=True)
return response["Parameter"]["Value"]
except Exception as e:
logger.error(
f"An unexpected error occurred during get parameter fro parameter store: {str(e)}"
)
raise


def send_request_to_openai_api(api_endpoint, api_key, request_data):
"""OpenAI APIを呼び出します
Args:
api_endpoint (string): OpenAI APIエンドポイント
api_key (string): OpenAI APIキー
request_data (string): ChatGPTへのリクエストデータ
Returns:
dict: ChatGPTへのAPIリクエストのレスポンスデータ
"""
headers = {"Content-Type": "application/json", "Authorization": "Bearer " + api_key}

data = json.dumps(request_data).encode("utf-8")
req = urllib.request.Request(api_endpoint, data=data, headers=headers)

try:
response = urllib.request.urlopen(req).read().decode("utf-8")
except Exception as e:
error_message = f"An unexpected error occurred: {str(e)}"
logger.error(json.dumps({"error": error_message}))
raise
return response
9 changes: 8 additions & 1 deletion src/backend/lib/backend-stack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@ import * as acm from 'aws-cdk-lib/aws-certificatemanager'
import * as s3 from 'aws-cdk-lib/aws-s3'
import type { Construct } from 'constructs'
import { Api, Auth, Identity, Web } from './constructs'
import { Diary } from './constructs/diary'

interface BackendStackProps extends cdk.StackProps {}

export class BackendStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: BackendStackProps) {
super(scope, id, props)

const logBucket = new s3.Bucket(this, 'LogBucket', {
new s3.Bucket(this, 'LogBucket', {
removalPolicy: cdk.RemovalPolicy.DESTROY,
enforceSSL: true,
serverAccessLogsPrefix: 'log/',
Expand Down Expand Up @@ -44,6 +45,12 @@ export class BackendStack extends cdk.Stack {
const api = new Api(this, 'Api', {
userPool: auth.userPool,
})
// Diary機能コンストラクトのスタック化
const diary = new Diary(this, 'Diary', {
userPool: auth.userPool,
api: api.api,
cognitoAuthorizer: api.cognitoAuthorizer,
})

const web = new Web(this, 'Web', {
userPool: auth.userPool,
Expand Down
Loading

0 comments on commit 7803976

Please sign in to comment.