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
26 changes: 1 addition & 25 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,25 +1 @@
## Lambda에 올릴 zip 파일 생성하는 법
### 1. 관련된 의존성 설치
```
pip install \
--platform manylinux2014_x86_64 \
--target=./package \
--implementation cp \
--python-version 3.13 \
--only-binary=:all: \
--upgrade \
-r requirements.txt
```
### 2. 의존성 압축
```
cd package/

zip -r ../deployment-package.zip .
```

### 3. 람다 함수 zip 파일에 추가
```
cd ..

zip -g deployment-package.zip lambda_function.py
```
<img width="1174" height="549" alt="image" src="https://github.com/user-attachments/assets/ca5d57a4-7e21-40c8-88fc-5b4518964daf" />
177 changes: 90 additions & 87 deletions edukit-AI-agent-1/lambda_function.py
Original file line number Diff line number Diff line change
@@ -1,43 +1,64 @@
import json
import os
import boto3
import openai
from typing import Dict, Any
import redis
from openai import OpenAI, APIError # openai 라이브러리 import

# SQS 클라이언트 초기화
sqs = boto3.client('sqs')

# Redis 클라이언트 초기화
redis_client = redis.Redis(
host=os.environ.get('REDIS_HOST', 'localhost'),
port=int(os.environ.get('REDIS_PORT', 6379)),
password=os.environ.get('REDIS_PASSWORD'),
decode_responses=True
)

# OpenAI 클라이언트 초기화 (API 키 자동 로드)
# 핸들러 바깥에서 초기화하여 연결을 재사용합니다.
client = OpenAI(api_key=os.environ['OPENAI_API_KEY'])

def lambda_handler(event, context):
"""
AWS Lambda function to process SQS messages and refine draft content using OpenAI
SQS 메시지를 처리하고 OpenAI를 사용해 생기부 초안을 검토하는 Lambda 함수
"""

# Initialize OpenAI API key
openai.api_key = os.environ['OPENAI_API_KEY']

# 다음 큐 URL (환경 변수로 설정)
next_queue_url = os.environ.get('NEXT_QUEUE_URL')

try:
# Process each record from SQS
for record in event['Records']:
# Parse the SQS message body
message_body = json.loads(record['body'])

# Extract draft generation event data
task_id = message_body['taskId']
request_prompt = message_body['requestPrompt']
byte_count = message_body['byteCount']
task_id = message_body['task_id']
request_prompt = message_body['request_prompt']
byte_count = message_body['byte_count']
target_bytes = byte_count
min_bytes = byte_count - 100

version = message_body['version']
draft_content = message_body['draftContent']

# Refine the draft content using OpenAI
draft_content = message_body['draft_content']

processed_message = {
'task_id': task_id,
'version': version,
"status": "PHASE2_STARTED"
}
try:
stream_id = redis_client.xadd('ai-response', {'data': json.dumps(processed_message, ensure_ascii=False)})
print(f"Successfully added to Redis stream 'ai-response'. Stream ID: {stream_id}")
except Exception as redis_error:
print(f"Error adding to Redis stream: {str(redis_error)}")

# 규정 기반 프롬프트로 초안 개선 수행
refined_content = refine_draft_content(
draft_content,
request_prompt
draft_content=draft_content,
original_prompt=request_prompt
)

# 개선된 내용을 다음 큐로 전송

next_queue_url = os.environ.get('NEXT_QUEUE_URL')
print(f"Successfully reviewed draft. Byte count: {len(refined_content.encode('utf-8'))}/{target_bytes}")

# 개선된 내용을 다음 SQS 큐로 전송
if next_queue_url:
send_to_next_queue(
task_id=task_id,
Expand All @@ -48,121 +69,103 @@ def lambda_handler(event, context):
queue_url=next_queue_url
)

print(f"Processed and forwarded task_id: {task_id}")

return {
'statusCode': 200,
'body': json.dumps('Successfully processed all messages')
}
return {'statusCode': 200, 'body': json.dumps('Successfully processed all messages')}

except Exception as e:
print(f"Error processing messages: {str(e)}")
return {
'statusCode': 500,
'body': json.dumps(f'Error: {str(e)}')
}

return {'statusCode': 500, 'body': json.dumps(f'Error: {str(e)}')}

def refine_draft_content(draft_content: str, original_prompt: str) -> str:
"""
생활기록부 제약 조건을 확인하고 초안을 개선하는 함수
생활기록부 제약 조건을 확인하고 초안을 개선하는 함수 (규정 기반 프롬프트 복구)
"""
try:
# 새로운 프롬프팅 전용 시스템 프롬프트
system_prompt = """
다음은 학생 생활기록부 초안이야. 아래 '작성 금지 규정'에 따라, 위반되는 내용을 모두 찾아내서 수정하거나 일반적인 용어로 대체해 줘. 원본의 의미는 최대한 유지해 줘.

## 작성 금지 규정

회사/브랜드명: '유튜브'는 '온라인 동영상 플랫폼', 'ChatGPT'는 '인공지능 챗봇'으로 변경

어학/자격증: 'TOEIC', 'HSK', '한자능력검정' 등 명칭 언급 금지

수상/대회: 'OO 경진대회에서 수상' 또는 'XX대회에 참여'와 같은 내용 모두 삭제

기관명: 허용된 교육기관 외의 특정 대학, 기관명 언급 금지
system_prompt = (
"다음은 학생 생활기록부 초안이야. 아래 '작성 금지 규정'에 따라, "
"위반되는 내용을 모두 찾아내서 수정하거나 일반적인 용어로 대체해 줘. "
"원본의 의미는 최대한 유지해 줘.\n\n"
"## 작성 금지 규정\n\n"
"회사/브랜드명: '유튜브'는 '온라인 동영상 플랫폼', 'ChatGPT'는 '인공지능 챗봇'으로 변경\n\n"
"어학/자격증: 'TOEIC', 'HSK', '한자능력검정' 등 명칭 언급 금지\n\n"
"수상/대회: 'OO 경진대회에서 수상' 또는 'XX대회에 참여'와 같은 내용 모두 삭제\n\n"
"기관명: 허용된 교육기관 외의 특정 대학, 기관명 언급 금지\n\n"
"외국어: '프로젝트'는 '과제', '리더'는 '대표' 등 모든 영어 단어를 한글로 변경"
)

외국어: '프로젝트'는 '과제', '리더'는 '대표' 등 모든 영어 단어를 한글로 변경
"""

# 원본 바이트 수 계산
original_byte_count = len(draft_content.encode('utf-8'))

user_prompt = f"""## 검토할 초안 원문
{original_prompt}"""

# OpenAI v0.28.1 API 사용
combined_prompt = f"{system_prompt}\n\n{user_prompt}"

response = openai.Completion.create(
model="gpt-3.5-turbo-instruct",
prompt=combined_prompt,

user_prompt = f"## 검토할 초안 원문\n{original_prompt}"

response = client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_prompt}
],
temperature=0.5,
max_tokens=2000
)
refined_content = response.choices[0].text.strip()

refined_content = response.choices[0].message.content.strip()

# 프롬프트 설명 부분 제거 (이중 안전장치)
lines = refined_content.split('\n')
cleaned_lines = []

for line in lines:
# 설명성 텍스트 패턴 제거
line_stripped = line.strip()
if not (line_stripped.startswith('개선된') or
line_stripped.startswith('초안') or
line_stripped.startswith('다음은') or
line_stripped.startswith('위의') or
line_stripped.startswith('수정된') or
line_stripped.startswith('다듬어진') or
('바이트' in line_stripped and ':' in line_stripped) or
line_stripped.endswith(':') and len(line_stripped) < 30):
if not (
line_stripped.startswith('개선된') or
line_stripped.startswith('초안') or
line_stripped.startswith('다음은') or
line_stripped.startswith('위의') or
line_stripped.startswith('수정된') or
line_stripped.startswith('다듬어진') or
('바이트' in line_stripped and ':' in line_stripped) or
(line_stripped.endswith(':') and len(line_stripped) < 30)
):
cleaned_lines.append(line)

refined_content = '\n'.join(cleaned_lines).strip()

# 혹시 전체가 비어있으면 원본 반환
if not refined_content:
refined_content = draft_content

# 바이트 수 비교
refined_byte_count = len(refined_content.encode('utf-8'))
byte_ratio = refined_byte_count / original_byte_count if original_byte_count > 0 else 0

print(f"Byte count change: {original_byte_count} -> {refined_byte_count} (ratio: {byte_ratio:.2f})")

# 분량이 너무 줄어들었으면 경고
if byte_ratio < 0.7:
print(f"Warning: Content significantly reduced (ratio: {byte_ratio:.2f})")

return refined_content

except APIError as e:
print(f"OpenAI API error: {e}")
return draft_content
except Exception as e:
print(f"Error calling OpenAI API: {str(e)}")
raise e
print(f"An unexpected error occurred: {str(e)}")
return draft_content

def send_to_next_queue(task_id: str, request_prompt: str,
byte_count: int, version: int, refined_content: str, queue_url: str) -> None:
"""
개선된 내용을 다음 SQS 큐로 전송하는 함수
개선된 내용을 다음 SQS 큐로 전송하는 함수 (원상 복구)
"""
try:
message_body = {
'task_id': task_id,
'request_prompt': request_prompt,
'byte_count': byte_count,
'version': version,
'draft_content': refined_content, # 개선된 내용으로 업데이트
'draft_content': refined_content,
}

response = sqs.send_message(
QueueUrl=queue_url,
MessageBody=json.dumps(message_body, ensure_ascii=False)
)

print(f"Message sent to queue: {queue_url}, MessageId: {response['MessageId']}")

except Exception as e:
print(f"Error sending message to SQS: {str(e)}")
raise e
5 changes: 3 additions & 2 deletions edukit-AI-agent-1/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
openai==0.28.1
boto3==1.34.34
redis
openai
pydantic
36 changes: 25 additions & 11 deletions edukit-AI-agent-2/lambda_function.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,18 @@ def lambda_handler(event, context):

version = message_body['version']
draft_content = message_body['draft_content']


processed_message = {
'task_id': task_id,
'version': version,
"status": "PHASE3_STARTED"
}
try:
stream_id = redis_client.xadd('ai-response', {'data': json.dumps(processed_message, ensure_ascii=False)})
print(f"Successfully added to Redis stream 'ai-response'. Stream ID: {stream_id}")
except Exception as redis_error:
print(f"Error adding to Redis stream: {str(redis_error)}")

# Review the draft content using OpenAI
final_content = review_student_record(
draft_content=draft_content,
Expand All @@ -52,6 +63,7 @@ def lambda_handler(event, context):
'task_id': task_id,
'version': version,
'final_content': final_content,
"status": "COMPLETED"
}

try:
Expand Down Expand Up @@ -104,21 +116,23 @@ def review_student_record(draft_content: str, target_bytes: int, min_bytes: int)

def create_review_prompt(draft_content: str, target_bytes: int, min_bytes: int) -> str:
"""학생 생활기록부 검토 프롬프트를 생성합니다."""
prompt = f"""다음은 학생 생활기록부 원고야. 아래 '스타일 가이드'에 맞춰 최종본을 완성해 줘.
prompt = f"""
다음은 학생 생활기록부 원고야. 아래 '스타일 가이드'에 맞춰 최종본을 완성해 줘.

## 스타일 가이드
## 스타일 가이드

목표 분량: 각 버전은 UTF-8 인코딩 기준 {target_bytes}Byte (최소 {min_bytes}byte 이상)로 맞춰 줘.
문체: 모든 문장은 '~함.' 또는 '~임.'으로 끝나는 현재형 음슴체로 변경하고, 문장 끝에 온점을 붙여 줘.
구두점: 쉼표(,)는 사용하지 말고, 의미가 명확하도록 문장을 다듬어 줘.
표현: '학생은', '학생이' 같은 표현은 사용하지 마.
목표 분량: 각 버전은 UTF-8 인코딩 기준 {target_bytes}Byte (최소 {min_bytes}byte 이상)로 맞춰 줘.
문체: 모든 문장은 '~함.' 또는 '~임.'으로 끝나는 현재형 음슴체로 변경하고, 문장 끝에 온점을 붙여 줘.
구두점: 쉼표(,)는 사용하지 말고, 의미가 명확하도록 문장을 다듬어 줘.
표현: '학생은', '학생이' 같은 표현은 사용하지 마.

## 원고
## 원고

{draft_content}
{draft_content}

## 최종본
## 최종본

위의 스타일 가이드를 정확히 따라 최종본을 작성해 줘. 설명이나 부연설명 없이 완성된 본문만 출력해."""
위의 스타일 가이드를 정확히 따라 최종본을 작성해 줘. 설명이나 부연설명 없이 완성된 본문만 출력해.
"""

return prompt