Skip to content

Commit 23b0e06

Browse files
committed
Agent Lambda Code
1 parent 7bde489 commit 23b0e06

File tree

1 file changed

+86
-89
lines changed

1 file changed

+86
-89
lines changed

β€Žscript/agent-cloudformation/bedrock-agent.yamlβ€Ž

Lines changed: 86 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ Parameters:
99

1010
ModelId:
1111
Type: String
12-
Default: 'us.anthropic.claude-3-7-sonnet-20250219-v1:0'
12+
Default: 'us.amazon.nova-pro-v1:0'
1313
Description: 'Foundation model ID for the agent'
1414

1515
DynamoDBTableName:
@@ -55,6 +55,7 @@ Resources:
5555
Action: sts:AssumeRole
5656
ManagedPolicyArns:
5757
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
58+
- arn:aws:iam::aws:policy/AmazonBedrockFullAccess
5859
Policies:
5960
- PolicyName: !Sub '${AgentName}-DynamoDBReadPolicy'
6061
PolicyDocument:
@@ -84,53 +85,45 @@ Resources:
8485
import json
8586
import os
8687
import boto3
87-
from boto3.dynamodb.conditions import Attr, Contains
88-
from decimal import Decimal
88+
from boto3.dynamodb.conditions import Attr
8989
9090
def lambda_handler(event, context):
9191
"""
92-
Bedrock Agent Action Group을 μœ„ν•œ Lambda ν•¨μˆ˜
93-
μ‚¬μš©μž μ§ˆλ¬Έμ— κΈ°λ°˜ν•˜μ—¬ κ΄€λ ¨ μ˜μƒμ„ κ²€μƒ‰ν•©λ‹ˆλ‹€.
92+
Bedrock을 ν™œμš©ν•œ 의미 기반 μ˜μƒ 검색
9493
"""
9594
9695
print(f"πŸ“₯ 받은 이벀트: {json.dumps(event, default=str)}")
9796
9897
try:
99-
# Bedrock Agent 이벀트 ꡬ쑰 νŒŒμ‹±
10098
api_path = event.get('apiPath', '')
10199
http_method = event.get('httpMethod', '')
102100
request_body = event.get('requestBody', {})
103101
104102
print(f"πŸ” API Path: {api_path}, Method: {http_method}")
105-
print(f"πŸ“¦ Request Body: {request_body}")
106103
107-
# DynamoDB ν΄λΌμ΄μ–ΈνŠΈ μ΄ˆκΈ°ν™”
108104
dynamodb = boto3.resource('dynamodb', region_name='us-west-2')
105+
bedrock = boto3.client('bedrock-runtime', region_name='us-west-2')
109106
110107
if api_path == '/search_classes' and http_method == 'POST':
111-
# requestBodyμ—μ„œ content μΆ”μΆœ
112108
content = request_body.get('content', {})
113109
app_json = content.get('application/json', {})
114110
properties = app_json.get('properties', [])
115111
116-
# propertiesμ—μ„œ query νŒŒλΌλ―Έν„° μΆ”μΆœ
117112
query = ''
118113
for prop in properties:
119114
if prop.get('name') == 'query':
120115
query = prop.get('value', '')
121116
break
122117
123-
print(f"πŸ”Ž μΆ”μΆœλœ query: {query}")
124-
result = search_classes(dynamodb, query)
125-
function_name = 'search_classes'
118+
print(f"πŸ”Ž μ‚¬μš©μž 질문: {query}")
119+
result = search_with_bedrock(dynamodb, bedrock, query)
120+
126121
else:
127122
result = {
128123
'statusCode': 400,
129124
'body': json.dumps({'error': 'Unknown endpoint'})
130125
}
131-
function_name = 'unknown'
132126
133-
# Bedrock Agent 응닡 ν˜•μ‹ (AWS 곡식 μŠ€νŽ™)
134127
response = {
135128
'messageVersion': '1.0',
136129
'response': {
@@ -154,8 +147,7 @@ Resources:
154147
import traceback
155148
print(f"πŸ“‹ μŠ€νƒ 트레이슀: {traceback.format_exc()}")
156149
157-
error_body = {'error': str(e)}
158-
error_response = {
150+
return {
159151
'messageVersion': '1.0',
160152
'response': {
161153
'actionGroup': event.get('actionGroup', ''),
@@ -164,123 +156,128 @@ Resources:
164156
'httpStatusCode': 500,
165157
'responseBody': {
166158
'application/json': {
167-
'body': error_body
159+
'body': {'error': str(e)}
168160
}
169161
}
170162
}
171163
}
172-
173-
print(f"πŸ“€ μ—λŸ¬ 응닡: {json.dumps(error_response, ensure_ascii=False, default=str)}")
174-
return error_response
175164
176-
def search_classes(dynamodb, query):
177-
"""DynamoDBμ—μ„œ μ˜μƒ 검색 - description 기반 간단 검색"""
178-
179-
print(f"πŸ” 검색 쿼리: {query}")
180-
181-
# 검색어λ₯Ό κ°œλ³„ ν‚€μ›Œλ“œλ‘œ 뢄리
182-
search_terms = [term.strip().lower() for term in query.split() if term.strip()] if query else []
183-
184-
print(f"πŸ”Ž 검색어: {search_terms}")
165+
def search_with_bedrock(dynamodb, bedrock, query):
166+
"""Bedrock Nova Pro둜 질문 이해 ν›„ DynamoDB 검색"""
185167
186168
table_name = os.environ['DYNAMODB_TABLE_NAME']
187169
188170
try:
171+
# 1. DynamoDBμ—μ„œ λͺ¨λ“  ν™œμ„± κ°•μ˜ κ°€μ Έμ˜€κΈ°
189172
table = dynamodb.Table(table_name)
173+
response = table.scan(
174+
FilterExpression=Attr('class_flag').ne(10) & (Attr('class_flag').eq(0) | Attr('class_flag').not_exists())
175+
)
176+
177+
all_courses = response.get('Items', [])
178+
print(f"πŸ“Š 전체 κ°•μ˜ 수: {len(all_courses)}개")
190179
191-
# 검색어가 μ—†μœΌλ©΄ 빈 κ²°κ³Ό λ°˜ν™˜
192-
if not search_terms:
193-
print("⚠️ 검색어 μ—†μŒ - 빈 κ²°κ³Ό λ°˜ν™˜")
180+
if not all_courses:
194181
return {
195182
'statusCode': 200,
196183
'body': json.dumps({
197184
'courses_found': 0,
198185
'courses': [],
199-
'message': '검색어λ₯Ό μž…λ ₯ν•΄μ£Όμ„Έμš”.'
186+
'message': 'λ“±λ‘λœ κ°•μ˜κ°€ μ—†μŠ΅λ‹ˆλ‹€.'
200187
}, ensure_ascii=False)
201188
}
202189
203-
# ν™œμ„± μ˜μƒλ§Œ 쑰회
204-
base_filter = Attr('class_flag').ne(10) & (Attr('class_flag').eq(0) | Attr('class_flag').not_exists())
205-
206-
# nameκ³Ό description ν•„λ“œμ—μ„œ λŒ€μ†Œλ¬Έμž ꡬ뢄 없이 검색
207-
search_conditions = []
208-
for term in search_terms:
209-
term_lower = term.lower()
210-
term_upper = term.upper()
211-
term_title = term.title()
212-
213-
search_conditions.append(
214-
Contains(Attr('name'), term) |
215-
Contains(Attr('name'), term_lower) |
216-
Contains(Attr('name'), term_upper) |
217-
Contains(Attr('name'), term_title) |
218-
Contains(Attr('description'), term) |
219-
Contains(Attr('description'), term_lower) |
220-
Contains(Attr('description'), term_upper) |
221-
Contains(Attr('description'), term_title)
222-
)
223-
224-
# OR 쑰건으둜 κ²°ν•©
225-
search_filter = search_conditions[0]
226-
for condition in search_conditions[1:]:
227-
search_filter = search_filter | condition
190+
# 2. κ°•μ˜ λͺ©λ‘μ„ ν…μŠ€νŠΈλ‘œ λ³€ν™˜ (μ΅œλŒ€ 50개)
191+
courses_text = "\n\n".join([
192+
f"κ°•μ˜ {i+1}:\n제λͺ©: {c.get('name', '')}\nμ„€λͺ…: {c.get('description', '')}\nλ‚œμ΄λ„: {c.get('difficulty', 'intermediate')}"
193+
for i, c in enumerate(all_courses[:50])
194+
])
228195
229-
final_filter = base_filter & search_filter
196+
# 3. Bedrock Nova Pro ν”„λ‘¬ν”„νŠΈ
197+
prompt = f"""μ‚¬μš©μž 질문: {query}
198+
199+
λ‹€μŒμ€ μ‚¬μš©ν•  수 μžˆλŠ” AWS κ°•μ˜ λͺ©λ‘μž…λ‹ˆλ‹€:
200+
201+
{courses_text}
202+
203+
μœ„ κ°•μ˜ μ€‘μ—μ„œ μ‚¬μš©μž μ§ˆλ¬Έμ— κ°€μž₯ μ ν•©ν•œ κ°•μ˜λ₯Ό μ΅œλŒ€ 3개 μ„ νƒν•˜κ³ , 각 κ°•μ˜ 번호만 JSON λ°°μ—΄λ‘œ λ°˜ν™˜ν•˜μ„Έμš”.
204+
μ˜ˆμ‹œ: {{"selected": [1, 3, 5]}}
205+
206+
λ§Œμ•½ μ ν•©ν•œ κ°•μ˜κ°€ μ—†λ‹€λ©΄ 빈 배열을 λ°˜ν™˜ν•˜μ„Έμš”: {{"selected": []}}
207+
208+
JSON만 λ°˜ν™˜ν•˜κ³  λ‹€λ₯Έ μ„€λͺ…은 ν•˜μ§€ λ§ˆμ„Έμš”."""
209+
210+
# 4. Bedrock Nova Pro 호좜
211+
body = json.dumps({
212+
"messages": [
213+
{
214+
"role": "user",
215+
"content": [{"text": prompt}]
216+
}
217+
],
218+
"inferenceConfig": {
219+
"max_new_tokens": 500,
220+
"temperature": 0.3
221+
}
222+
})
230223
231-
# μŠ€μΊ” μ‹€ν–‰
232-
response = table.scan(
233-
FilterExpression=final_filter,
234-
Limit=20
224+
bedrock_response = bedrock.invoke_model(
225+
modelId='us.amazon.nova-pro-v1:0',
226+
body=body
235227
)
236228
237-
print(f"πŸ“Š μŠ€μΊ” κ²°κ³Ό: {len(response.get('Items', []))}개 ν•­λͺ©")
229+
response_body = json.loads(bedrock_response['body'].read())
230+
bedrock_text = response_body['output']['message']['content'][0]['text']
231+
print(f"πŸ€– Bedrock 응닡: {bedrock_text}")
238232
239-
# κ²°κ³Ό ν¬λ§·νŒ… - thumbnailκ³Ό URL만 포함
240-
classes = []
241-
for item in response.get('Items', []):
242-
class_info = {
243-
'title': str(item.get('name', '')),
244-
'description': str(item.get('description', ''))[:150] + '...' if len(str(item.get('description', ''))) > 150 else str(item.get('description', '')),
245-
'url': str(item.get('url', '')),
246-
'thumbnail': str(item.get('image', '')),
247-
'author': str(item.get('author', '')),
248-
'difficulty': str(item.get('difficulty', 'intermediate'))
249-
}
250-
classes.append(class_info)
233+
# 5. JSON μΆ”μΆœ
234+
selected_indices = json.loads(bedrock_text)['selected']
251235
252-
# μƒμœ„ 5개만 λ°˜ν™˜
253-
top_classes = classes[:5]
236+
# 6. μ„ νƒλœ κ°•μ˜ 정보 ꡬ성
237+
selected_courses = []
238+
for idx in selected_indices:
239+
if 0 < idx <= len(all_courses):
240+
course = all_courses[idx - 1]
241+
selected_courses.append({
242+
'title': str(course.get('name', '')),
243+
'description': str(course.get('description', ''))[:150] + '...' if len(str(course.get('description', ''))) > 150 else str(course.get('description', '')),
244+
'url': str(course.get('url', '')),
245+
'thumbnail': str(course.get('image', '')),
246+
'author': str(course.get('author', '')),
247+
'difficulty': str(course.get('difficulty', 'intermediate'))
248+
})
254249
255-
message = f"'{' '.join(search_terms)}' κ΄€λ ¨ κ°•μ˜ {len(top_classes)}개λ₯Ό μ°Ύμ•˜μŠ΅λ‹ˆλ‹€." if top_classes else f"'{' '.join(search_terms)}' κ΄€λ ¨ κ°•μ˜λ₯Ό μ°Ύμ§€ λͺ»ν–ˆμŠ΅λ‹ˆλ‹€."
250+
message = f"'{query}' κ΄€λ ¨ κ°•μ˜ {len(selected_courses)}개λ₯Ό μ°Ύμ•˜μŠ΅λ‹ˆλ‹€." if selected_courses else f"'{query}' κ΄€λ ¨ κ°•μ˜λ₯Ό μ°Ύμ§€ λͺ»ν–ˆμŠ΅λ‹ˆλ‹€."
256251
257252
result_data = {
258-
'courses_found': len(top_classes),
259-
'courses': top_classes,
253+
'courses_found': len(selected_courses),
254+
'courses': selected_courses,
260255
'message': message,
261256
'traces': [
262-
{'type': 'preprocessing', 'content': f"πŸ” '{' '.join(search_terms)}' 검색 μ‹œμž‘", 'timestamp': ''},
263-
{'type': 'function_call', 'content': f'⚑ DynamoDBμ—μ„œ {len(classes)}개 ν•­λͺ© 발견', 'timestamp': ''},
264-
{'type': 'observation', 'content': f'βœ… μƒμœ„ {len(top_classes)}개 κ°•μ˜ 선택 μ™„λ£Œ', 'timestamp': ''}
257+
{'type': 'preprocessing', 'content': f"πŸ” '{query}' 검색 μ‹œμž‘", 'timestamp': ''},
258+
{'type': 'function_call', 'content': f'⚑ Bedrock Nova Pro둜 {len(all_courses)}개 κ°•μ˜ 뢄석', 'timestamp': ''},
259+
{'type': 'observation', 'content': f'βœ… {len(selected_courses)}개 κ°•μ˜ 선택 μ™„λ£Œ', 'timestamp': ''}
265260
]
266261
}
267262
268-
print(f"βœ… 검색 μ™„λ£Œ: {len(top_classes)}개 μ˜μƒ 발견")
269-
print(f"πŸ“Š κ²°κ³Ό 데이터: {json.dumps(result_data, ensure_ascii=False)}")
263+
print(f"βœ… 검색 μ™„λ£Œ: {len(selected_courses)}개 κ°•μ˜ 발견")
270264
271265
return {
272266
'statusCode': 200,
273267
'body': json.dumps(result_data, ensure_ascii=False)
274268
}
275269
276270
except Exception as e:
277-
print(f"❌ ν…Œμ΄λΈ” μ ‘κ·Ό 였λ₯˜: {str(e)}")
271+
print(f"❌ 검색 였λ₯˜: {str(e)}")
272+
import traceback
273+
print(traceback.format_exc())
278274
return {
279275
'statusCode': 500,
280-
'body': json.dumps({'error': f'Database error: {str(e)}'})
276+
'body': json.dumps({'error': f'Search error: {str(e)}'})
281277
}
282278
283279
280+
284281
# Lambda Permission for Bedrock Agent
285282
LambdaInvokePermission:
286283
Type: AWS::Lambda::Permission

0 commit comments

Comments
Β (0)