Amazon Bedrock 에이전트 생성·평가
Amazon Bedrock 에이전트 생성·평가 (지식 베이스 & 액션 그룹 통합)
이 노트북에서는 Amazon Bedrock 에이전트를 평가하는 방법을 배워요. 평가할 에이전트는 고객에게 성인·어린이 메뉴 정보를 제공하고 테이블 예약 시스템을 관리하는 레스토랑 에이전트예요. 이 에이전트는 Amazon Bedrock Agents의 features 예제 노트북에서 약간의 변경을 가해 만든 거예요. 에이전트 생성 과정에 대해 더 알고 싶다면 여기를 참고하세요.
출처: 문서
본문
아키텍처는 아래와 같아요.
이 노트북에서 다루는 단계는 다음과 같아요.
- 필요한 라이브러리 import
- 에이전트 생성
- Ragas 메트릭 정의
- 에이전트 평가
- 생성한 리소스 정리
invokeAgent 함수는 사용자 쿼리를 Bedrock 에이전트에 보내고 에이전트의 응답과 trace 데이터를 모두 반환해요. 이벤트 스트림을 처리해 평가 목적의 trace 정보를 캡처해요.
def invokeAgent(query, session_id, session_state=dict()):
end_session: bool = False
# invoke the agent API
agentResponse = bedrock_agent_runtime_client.invoke_agent(
inputText=query,
agentId=agent_id,
agentAliasId=alias_id,
sessionId=session_id,
enableTrace=True,
endSession=end_session,
sessionState=session_state,
)
event_stream = agentResponse["completion"]
try:
traces = []
for event in event_stream:
if "chunk" in event:
data = event["chunk"]["bytes"]
agent_answer = data.decode("utf8")
end_event_received = True
return agent_answer, traces
# End event indicates that the request finished successfully
elif "trace" in event:
traces.append(event["trace"])
else:
raise Exception("unexpected event.", event)
return agent_answer, traces
except Exception as e:
raise Exception("unexpected event.", e)
Ragas 메트릭 정의
에이전트를 평가하는 것은 출력이 기대 결과와 일치하는지만 확인하면 되는 전통적인 소프트웨어 테스트와는 달라요. 이 에이전트들은 종종 여러 유효한 접근 방식이 있는 복잡한 작업을 수행해요. 본래의 자율성 때문에 에이전트가 제대로 작동하는지 확인하려면 평가가 필수적이에요.
에이전트에서 무엇을 평가할지 고르기
평가 메트릭을 선택하는 것은 전적으로 사용 사례에 달려 있어요. 좋은 경험칙은 사용자 요구에 직접 연결되거나 명확하게 비즈니스 가치를 주도하는 메트릭을 선택하는 거예요. 위의 레스토랑 에이전트 예시에서 우리는 에이전트가 불필요한 반복 없이 사용자 요청을 이행하고, 필요할 때 고객 경험을 높이기 위해 유용한 추천을 제공하며, 브랜드 톤과 일관성을 유지하기를 원해요.
이 우선순위를 평가할 메트릭을 정의해 보겠습니다. Ragas는 평가를 위한 여러 사용자 정의 메트릭을 제공해요.
평가 기준을 정의할 때는 모호한 점수보다 이진(binary) 결정이나 이산적(discrete) 분류 점수에 집중하세요. 이진 또는 명확한 분류는 성공 기준을 명시적으로 정의하도록 강제해요. 명확한 해석이 없는 0~100 사이의 점수를 내는 메트릭은 피하세요. 특히 평가가 독립적으로 이뤄질 때 87과 91 같은 비슷한 점수를 구분하기 어렵기 때문이에요.
Ragas에는 이러한 평가에 적합한 메트릭이 포함돼 있고, 그중 몇 가지를 실제로 살펴볼 거예요.
- Aspect Critic 메트릭 : LLM 판단을 활용해 이진 결과를 내는, 사용자 정의 기준에 제출이 따르는지 평가해요.
- Rubric Score 메트릭 : 상세한 사용자 정의 루브릭에 대해 응답을 평가해 품질을 반영하는 점수를 일관되게 부여해요.
from langchain_aws import ChatBedrock
from ragas.llms import LangchainLLMWrapper
model_id = "us.amazon.nova-pro-v1:0" # Choose your desired model
region_name = "us-east-1" # Choose your desired AWS region
bedrock_llm = ChatBedrock(model_id=model_id, region_name=region_name)
evaluator_llm = LangchainLLMWrapper(bedrock_llm)
from ragas.metrics import AspectCritic, RubricsScore
from ragas.dataset_schema import SingleTurnSample, MultiTurnSample, EvaluationDataset
from ragas import evaluate
rubrics = {
"score-1_description": (
"The item requested by the customer is not present in the menu and no recommendations were made."
),
"score0_description": (
"Either the item requested by the customer is present in the menu, or the conversation does not include any food or menu inquiry (e.g., booking, cancellation). This score applies regardless of whether any recommendation was provided."
),
"score1_description": (
"The item requested by the customer is not present in the menu and a recommendation was provided."
),
}
recommendations = RubricsScore(rubrics=rubrics, llm=evaluator_llm, name="Recommendations")
# Metric to evaluate if the AI fulfills all human requests completely.
request_completeness = AspectCritic(
name="Request Completeness",
llm=evaluator_llm,
definition=(
"Return 1 The agent completely fulfills all the user requests with no omissions. "
"otherwise, return 0."
),
)
# Metric to assess if the AI's communication aligns with the desired brand voice.
brand_tone = AspectCritic(
name="Brand Voice Metric",
llm=evaluator_llm,
definition=(
"Return 1 if the AI's communication is friendly, approachable, helpful, clear, and concise; "
"otherwise, return 0."
),
)
Ragas로 에이전트 평가
Ragas를 사용해 평가를 수행하려면 trace를 Ragas가 인식하는 형식으로 변환해야 해요. Amazon Bedrock 에이전트 trace를 Ragas 평가에 적합한 형식으로 변환하려면 Ragas는 [convert_to_ragas_messages][ragas.integrations.amazon_bedrock.convert_to_ragas_messages] 함수를 제공해요. 이 함수로 Amazon Bedrock 메시지를 Ragas가 기대하는 형식으로 변환할 수 있어요. 자세한 내용은 여기에서 읽을 수 있어요.
%%time
import uuid
session_id:str = str(uuid.uuid1())
query = "If you have children food then book a table for 2 people at 7pm on the 5th of May 2025."
agent_answer, traces_1 = invokeAgent(query, session_id)
print(agent_answer)
Your booking for 2 people at 7pm on the 5th of May 2025 has been successfully created. Your booking ID is ca2fab70.
query = "Can you check my previous booking? Can you please delete the booking?"
agent_answer, traces_2 = invokeAgent(query, session_id)
print(agent_answer)
Your reservation was found and has been successfully canceled.
from ragas.integrations.amazon_bedrock import convert_to_ragas_messages
# Convert Amazon Bedrock traces to messages accepted by Ragas.
# The convert_to_ragas_messages function transforms Bedrock-specific trace data
# into a format that Ragas can process as conversation messages.
ragas_messages_trace_1 = convert_to_ragas_messages(traces_1)
ragas_messages_trace_2 = convert_to_ragas_messages(traces_2)
# Initialize MultiTurnSample objects.
# MultiTurnSample is a data type defined in Ragas that encapsulates conversation
# data for multi-turn evaluation. This conversion is necessary to perform evaluations.
sample_1 = MultiTurnSample(user_input=ragas_messages_trace_1)
sample_2 = MultiTurnSample(user_input=ragas_messages_trace_2)
result = evaluate(
# Create an evaluation dataset from the multi-turn samples
dataset=EvaluationDataset(samples=[sample_1, sample_2]),
metrics=[request_completeness, brand_tone],
)
result.to_pandas()
Evaluating: 100%|██████████| 4/4 [00:00<?, ?it/s]
두 대화 모두에서 에이전트가 모든 사용자 요청을 누락 없이 완전히 이행했고(완전성), 친근하고 접근하기 쉬우며 도움이 되고 명확하고 간결하게(브랜드 음성) 소통했기 때문에 점수 1이 부여됐어요.
%%time
import uuid
session_id:str = str(uuid.uuid1())
query = "Do you serve Chicken Wings?"
agent_answer, traces_3 = invokeAgent(query, session_id)
print(agent_answer)
Yes, we serve Chicken Wings. Here are the details:
- **Buffalo Chicken Wings**: Classic buffalo wings served with celery sticks and blue cheese dressing. Allergens: Dairy (in blue cheese dressing), Gluten (in the coating), possible Soy (in the sauce).
%%time
session_id:str = str(uuid.uuid1())
query = "For desserts, do you have chocolate truffle cake?"
agent_answer, traces_4 = invokeAgent(query, session_id)
print(agent_answer)
I'm sorry, but we do not have chocolate truffle cake on our dessert menu. However, we have several delicious alternatives you might enjoy:
1. **Classic New York Cheesecake** - Creamy cheesecake with a graham cracker crust, topped with a choice of fruit compote or chocolate ganache.
2. **Apple Pie à la Mode** - Warm apple pie with a flaky crust, served with a scoop of vanilla ice cream and a drizzle of caramel sauce.
3. **Chocolate Lava Cake** - Rich and gooey chocolate cake with a molten center, dusted with powdered sugar and served with a scoop of raspberry sorbet.
4. **Pecan Pie Bars** - Buttery shortbread crust topped with a gooey pecan filling, cut into bars for easy serving.
5. **Banana Pudding Parfait** - Layers of vanilla pudding, sliced bananas, and vanilla wafers, topped with whipped cream and a sprinkle of crushed nuts.
May I recommend the **Chocolate Lava Cake** for a decadent treat?
%%time
from datetime import datetime
today = datetime.today().strftime('%b-%d-%Y')
session_id:str = str(uuid.uuid1())
query = "Do you have indian food?"
session_state = {
"promptSessionAttributes": {
"name": "John",
"today": today
}
}
agent_answer, traces_5 = invokeAgent(query, session_id, session_state=session_state)
print(agent_answer)
I could not find Indian food on our menu. However, we offer a variety of other cuisines including American, Italian, and vegetarian options. Would you like to know more about these options?
from ragas.integrations.amazon_bedrock import convert_to_ragas_messages
ragas_messages_trace_3 = convert_to_ragas_messages(traces_3)
ragas_messages_trace_4 = convert_to_ragas_messages(traces_4)
ragas_messages_trace_5 = convert_to_ragas_messages(traces_5)
sample_3 = MultiTurnSample(user_input=ragas_messages_trace_3)
sample_4 = MultiTurnSample(user_input=ragas_messages_trace_4)
sample_5 = MultiTurnSample(user_input=ragas_messages_trace_5)
result = evaluate(
dataset=EvaluationDataset(samples=[sample_3, sample_4, sample_5]),
metrics=[recommendations],
)
result.to_pandas()
Evaluating: 100%|██████████| 3/3 [00:00<?, ?it/s]
Recommendation 메트릭의 경우, 치킨 윙 문의는 항목이 있었으므로 0점을 받았어요. 초콜릿 트러플 케이크와 인도 음식 문의는 요청한 항목이 메뉴에 없고 대체 추천이 제공됐기 때문에 1점을 받았어요.
에이전트가 지식 베이스에서 검색한 정보를 얼마나 잘 활용하는지 평가하기 위해 Ragas가 제공하는 RAG 평가 메트릭을 사용해요. 이 메트릭에 대해 자세히 알아보려면 여기를 참고하세요.
이 튜토리얼에서는 다음 RAG 메트릭을 사용할 거예요.
ContextRelevance: 이중 LLM 판단을 통한 관련성 평가로 검색된 컨텍스트가 사용자 쿼리를 얼마나 잘 다루는지 측정해요.Faithfulness: 응답의 모든 주장(claim)이 제공된 검색 컨텍스트에 의해 뒷받침될 수 있는지 판단해 응답의 사실적 일관성을 평가해요.ResponseGroundedness: 응답의 각 주장이 제공된 컨텍스트에 직접적으로 뒷받침되거나 "근거가 있는(grounded)" 정도를 결정해요.
from ragas.metrics import ContextRelevance, Faithfulness, ResponseGroundedness
metrics = [
ContextRelevance(llm=evaluator_llm),
Faithfulness(llm=evaluator_llm),
ResponseGroundedness(llm=evaluator_llm),
]
from ragas.integrations.amazon_bedrock import extract_kb_trace
kb_trace_3 = extract_kb_trace(traces_3)
kb_trace_4 = extract_kb_trace(traces_4)
trace_3_single_turn_sample = SingleTurnSample(
user_input=kb_trace_3[0].get("user_input"),
retrieved_contexts=kb_trace_3[0].get("retrieved_contexts"),
response=kb_trace_3[0].get("response"),
reference="Yes, we do serve chicken wings prepared in Buffalo style, chicken wing that’s typically deep-fried and then tossed in a tangy, spicy Buffalo sauce.",
)
trace_4_single_turn_sample = SingleTurnSample(
user_input=kb_trace_4[0].get("user_input"),
retrieved_contexts=kb_trace_4[0].get("retrieved_contexts"),
response=kb_trace_4[0].get("response"),
reference="The desserts on the adult menu are:\n1. Classic New York Cheesecake\n2. Apple Pie à la Mode\n3. Chocolate Lava Cake\n4. Pecan Pie Bars\n5. Banana Pudding Parfait",
)
single_turn_samples = [trace_3_single_turn_sample, trace_4_single_turn_sample]
dataset = EvaluationDataset(samples=single_turn_samples)
kb_results = evaluate(dataset=dataset, metrics=metrics)
kb_results.to_pandas()
Evaluating: 100%|██████████| 6/6 [00:00<?, ?it/s]
에이전트가 목표를 달성할 수 있는지 평가하려면 다음 메트릭을 사용할 수 있어요.
AgentGoalAccuracyWithReference: 최종 결과를 어노테이션된 이상 결과와 비교해 AI가 사용자 목표를 달성했는지 판단하고, 이진 결과를 내요.AgentGoalAccuracyWithoutReference: 명시적 reference 없이 대화 상호작용만으로 AI가 사용자 목표를 충족했는지 유추해 이진 성공 지표를 제공해요.
from ragas.metrics import (
AgentGoalAccuracyWithoutReference,
AgentGoalAccuracyWithReference,
)
goal_accuracy_with_reference = AgentGoalAccuracyWithReference(llm=evaluator_llm)
goal_accuracy_without_reference = AgentGoalAccuracyWithoutReference(llm=evaluator_llm)
%%time
import uuid
session_id:str = str(uuid.uuid1())
query = "What entrees do you have for children?"
agent_answer, traces_6 = invokeAgent(query, session_id)
print(agent_answer)
Here are the entrees available for children:
1. CHICKEN NUGGETS - Crispy chicken nuggets served with a side of ketchup or ranch dressing. Allergens: Gluten (in the coating), possible Soy. Suitable for Vegetarians: No
2. MACARONI AND CHEESE - Classic macaroni pasta smothered in creamy cheese sauce. Allergens: Dairy, Gluten. Suitable for Vegetarians: Yes
3. MINI CHEESE QUESADILLAS - Small flour tortillas filled with melted cheese, served with a mild salsa. Allergens: Dairy, Gluten. Suitable for Vegetarians: Yes
4. PEANUT BUTTER AND BANANA SANDWICH - Peanut butter and banana slices on whole wheat bread. Allergens: Nuts (peanut), Gluten. Suitable for Vegetarians: Yes (if using vegetarian peanut butter)
5. VEGGIE PITA POCKETS - Mini whole wheat pita pockets filled with hummus, cucumber, and cherry tomatoes. Allergens: Gluten, possible Soy. Suitable for Vegetarians: Yes
from ragas.integrations.amazon_bedrock import convert_to_ragas_messages
ragas_messages_trace_6 = convert_to_ragas_messages(traces_6)
sample_6 = MultiTurnSample(
user_input=ragas_messages_trace_6,
reference="Response contains entrees food items for the children.",
)
result = evaluate(
dataset=EvaluationDataset(samples=[sample_6]),
metrics=[goal_accuracy_with_reference],
)
result.to_pandas()
Evaluating: 100%|██████████| 1/1 [00:00<?, ?it/s]
sample_6 = MultiTurnSample(user_input=ragas_messages_trace_6)
result = evaluate(
dataset=EvaluationDataset(samples=[sample_6]),
metrics=[goal_accuracy_without_reference],
)
result.to_pandas()
두 시나리오 모두에서 에이전트는 사용 가능한 모든 옵션—구체적으로 모든 어린이 엔트리를 나열함으로써—을 포괄적으로 제공해 점수 1을 얻었어요.
정리
불필요한 비용을 피하기 위해 생성된 모든 관련 리소스를 삭제해 보겠습니다.
clean_up_resources(
table_name,
lambda_function,
lambda_function_name,
agent_action_group_response,
agent_functions,
agent_id,
kb_id,
alias_id,
)
# Delete the agent roles and policies
delete_agent_roles_and_policies(agent_name)
# delete KB
knowledge_base.delete_kb(delete_s3_bucket=True, delete_iam_roles_and_policies=True)