Skip to content
Open
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
77 changes: 17 additions & 60 deletions .github/workflows/awsapm.yml
Original file line number Diff line number Diff line change
@@ -1,81 +1,38 @@
# Example workflow for Claude Code with Amazon Bedrock
#
# This workflow demonstrates how to use the Application Observability for AWS action
# with Claude Code and a custom Bedrock model.
#
# Key Features:
# - Brings your own Bedrock model (pay-per-token usage on your AWS account)
# - Uses Anthropic's official claude-code-base-action for execution
# - Two-step process: preparation + execution
#
# Prerequisites:
# 1. AWS IAM role with OIDC trust for GitHub Actions
# 2. Bedrock InvokeModel permissions in your IAM role
# 3. Application Signals and CloudWatch permissions
#
# Setup:
# 1. Create repository secret AWSAPM_ROLE_ARN with your IAM role ARN
# 2. (Optional) Set repository variable AWS_REGION for your preferred region
# 3. Copy this file to .github/workflows/awsapm.yml in your repository

name: Application observability for AWS (Claude + Bedrock)
name: Application observability for AWS

on:
issue_comment:
types: [created, edited]
pull_request_review_comment:
types: [created]
issues:
types: [opened, assigned, edited]
pull_request_review:
types: [submitted]

jobs:
awsapm-investigation:
# Only run when @awsapm is mentioned
if: |
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@awsapm')) ||
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@awsapm')) ||
(github.event_name == 'pull_request_review' && contains(github.event.review.body, '@awsapm')) ||
(github.event_name == 'issues' && (contains(github.event.issue.body, '@awsapm') || contains(github.event.issue.title, '@awsapm')))
runs-on: ubuntu-latest

permissions:
contents: write # To create branches for PRs
pull-requests: write # To post comments on PRs
issues: write # To post comments on issues
id-token: write # Required for AWS OIDC authentication

contents: write
pull-requests: write
issues: write
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }}
aws-region: ${{ vars.AWS_REGION || 'us-east-1' }}

# Step 1: Prepare AWS MCP configuration and investigation prompt
- name: Prepare Investigation Context
id: prepare
uses: aws-actions/application-observability-for-aws@v1
with:
bot_name: "@awsapm"
cli_tool: "claude_code"

# Step 2: Execute investigation with Claude Code
- name: Run Claude Investigation
id: claude
uses: anthropics/claude-code-base-action@beta
with:
use_bedrock: "true"
model: "us.anthropic.claude-sonnet-4-5-20250929-v1:0"
prompt_file: ${{ steps.prepare.outputs.prompt_file }}
mcp_config: ${{ steps.prepare.outputs.mcp_config_file }}
allowed_tools: ${{ steps.prepare.outputs.allowed_tools }}
fetch-depth: 1

# Step 3: Post results back to GitHub issue/PR (reuse the same action)
- name: Post Investigation Results
if: always() # Run even if Claude step fails
uses: aws-actions/application-observability-for-aws@v1
- name: Run Application observability for AWS Investigation
id: awsapm
uses: mxiamxia/aws-apm-action@main
with:
aws_access_key_id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws_secret_access_key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws_region: ${{ vars.AWS_REGION || 'us-east-1' }}
bot_name: "@awsapm"
cli_tool: "claude_code"
comment_id: ${{ steps.prepare.outputs.awsapm_comment_id }}
output_file: ${{ steps.claude.outputs.execution_file }}
output_status: ${{ steps.claude.outputs.conclusion }}
61 changes: 0 additions & 61 deletions ai-validator/libs/tests/test-billing-summary.script.md

This file was deleted.

21 changes: 0 additions & 21 deletions data_test/test_cases/metrics_test_cases.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,27 +60,6 @@
}]
},
"notes": "Corresponds to PDF scenario 2"
},
{
"test_case_id": "billing_summary_service_metric_availability_check",
"description": "Verify Service Latency metric availability for billing summary API (Scenario 16)",
"test_scenario": "Scenario 16",
"metric_namespace": "ApplicationSignals",
"metric_name": "Latency",
"statistic": "p99",
"dimensions": [
{"Name": "Operation", "Value": "GET ^summary/$"},
{"Name": "Environment", "Value": "ENVIRONMENT_NAME_PLACEHOLDER"},
{"Name": "Service", "Value": "billing-service-python"}
],
"evaluation_period_minutes": 180,
"threshold": {
"comparison_operator": [{
"operator": "GreaterThanOrEqualToThreshold",
"threshold_value": 0
}]
},
"notes": "Corresponds to PDF scenario 16"
},
{
"test_case_id": "database_insight_dependency_metric_availability_check",
Expand Down
6 changes: 1 addition & 5 deletions pet_clinic_ai_agents/nutrition_agent/nutrition_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,6 @@ def create_nutrition_agent():
system_prompt = (
"You are a specialized pet nutrition expert at our veterinary clinic, providing accurate, evidence-based dietary guidance for pets. "
"Never mention using any API, tools, or external services - present all advice as your own expert knowledge.\n\n"
"CRITICAL VALIDATION RULES:\n"
"1. ALWAYS check if the 'products' field is empty or contains an error message before making product recommendations\n"
"2. If the products field is empty or contains an error (starts with 'Error:'), NEVER recommend products from your training data\n"
"3. Instead, respond: 'We currently don't have nutrition products available for [pet type]. Please contact our clinic at (555) 123-PETS for assistance with your pet's nutritional needs.'\n\n"
"When providing nutrition guidance:\n"
"- Use the specific nutrition information available to you as the foundation for your recommendations\n"
"- Always recommend the SPECIFIC PRODUCT NAMES provided to you that pet owners should buy FROM OUR PET CLINIC\n"
Expand Down Expand Up @@ -110,4 +106,4 @@ async def invoke(payload, context):
return ''.join(response_data)

if __name__ == "__main__":
uvicorn.run(agent_app, host='0.0.0.0', port=8080)
uvicorn.run(agent_app, host='0.0.0.0', port=8080)
42 changes: 1 addition & 41 deletions pet_clinic_billing_service/billing_service/views.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
from rest_framework import viewsets, status
from rest_framework.response import Response
from django.db.models import Subquery, Count, Sum
from django.utils import timezone
from django.core.cache import cache
from django.db.models import Subquery
from .models import Billing,CheckList
from .serializers import BillingSerializer
from opentelemetry import trace
Expand Down Expand Up @@ -156,44 +154,6 @@ def log(self, data):
# Don't raise the exception to avoid disrupting the main flow


class SummaryViewSet(viewsets.ViewSet):
def list(self, request, pk=None):
span = trace.get_current_span()

# Always set request counter to 1
span.set_attribute("billing_summary_request", 1)

# Set num_summaries based on current minute
current_minute = timezone.now().minute
num_summaries = 50 if current_minute % 5 == 0 else 2

# Use random cache key to simulate high cache miss rate
cache_key = f'billing_summary_last_7_days_{random.randint(1, num_summaries)}'
summary = cache.get(cache_key)

if summary is None:
# Cache miss
span.set_attribute("billing_summary_cache_hit", 0)

# Sleep to simulate high latency when there's a cache miss
time.sleep(2)

billings = Billing.objects.all()

summary = {
'total_count': billings.count(),
'total_amount': billings.aggregate(Sum('payment'))['payment__sum'] or 0,
'period': 'all_time'
}

cache.set(cache_key, summary, 300) # Cache for 5 minutes
else:
# Cache hit
span.set_attribute("billing_summary_cache_hit", 1)

return Response(summary)


class HealthViewSet(viewsets.ViewSet):
def list(self, request):
logger.info("HealthViewSet.list() called - Health check requested")
Expand Down
92 changes: 0 additions & 92 deletions pet_clinic_billing_service/create-metric-filters.sh

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,10 @@
from django.contrib import admin
from django.urls import include, path
from rest_framework.routers import DefaultRouter
from billing_service.views import HealthViewSet, BillingViewSet, SummaryViewSet
from billing_service.views import HealthViewSet, BillingViewSet

router = DefaultRouter()
router.register('billings', BillingViewSet, basename='billings')
router.register('summary', SummaryViewSet, basename='summary')
router.register('health', HealthViewSet, basename='health')
urlpatterns = [
path("admin/", admin.site.urls),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,4 @@ public Flux<BillingDetail> getBillings() {
.bodyToFlux(BillingDetail.class);
}

public Mono<Object> getBillingSummary() {
return webClientBuilder.build().get()
.uri("http://billing-service/summary/")
.retrieve()
.bodyToMono(Object.class);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -122,11 +122,6 @@ public Flux<BillingDetail> getBillings() {
return billingServiceClient.getBillings();
}

@GetMapping(value = "billing/summary")
public Mono<Object> getBillingSummary() {
return billingServiceClient.getBillingSummary();
}

@PostMapping(value = "insurance/pet-insurances")
public Mono<Void> addPetInsurance(final @RequestBody PetInsurance petInsurance) {
System.out.println(petInsurance.toString());
Expand Down
Loading