# Agentic Audit POC: Technical Architecture This README explains **what we built**, **how the pieces fit together**, and **how to run and extend it** inside DevX/AWS. It covers services, Lambda functions, IAM, networking, config, testing, and troubleshooting. --- ## 1) High‑level goal Build a **low‑cost, explainable POC** that: * Runs **audit checks** over AP data stored in **Amazon S3** using **Amazon Athena** * Exposes two capabilities: 1. **Duplicate invoice check (AP‑R5/AP‑C5)** 2. **General read‑only SQL** generated by an **LLM (Bedrock)** for simple, ad‑hoc questions (e.g., “list vendor IDs”) * Returns **auditable results** (columns + rows + presigned CSV link) and **makes each step traceable** in logs --- ## 2) Architecture (services & data flow) ``` [User / Auditor] │ JSON request ("text": "check duplicate invoices" or "largest invoices by amount") ▼ [Lambda: gp_Agent (router + SQL generator)] - Calls Athena info_schema to build schema hint - Calls Bedrock (inference profile) to classify/route - If duplicate_check → invokes duplicateCheck_AthenaQuery - If ad_hoc_sql → validates/runs Athena SQL directly │ ├──▶ [Amazon Bedrock] │ └─ Intent JSON (duplicate_check / ad_hoc_sql) │ ├──▶ [Amazon Athena] │ └─ Query S3 data (Lake in S3) │ └──▶ [Amazon S3] └─ Stores Athena result CSV [Lambda: duplicateCheck_AthenaQuery] - Purpose‑built Athena SQL for AP‑R5 duplicate invoices - Returns rows + presigned CSV URL (Optional) [EC2/Flask UI] - Simple button to trigger gp_Agent via API Gateway/Lambda URL Observability: [CloudWatch Logs] for all Lambdas Security/Access: [IAM Roles], [SSM] ``` **Core AWS services used** * **Amazon S3** – Data lake (CSV files) & Athena results output bucket * **Amazon Athena** – Serverless SQL over S3 (no DB servers) * **AWS Lambda** – Three functions * `gp_Agent` (router + LLM intent + ad‑hoc SQL executor) * `duplicateCheck_AthenaQuery` (fixed duplicate logic) * `duplicateCheck_TaskAgent` (alternative narrow router that only supports duplicate intent) * **Amazon Bedrock** – LLM inference for intent classification & simple SQL drafting * **Amazon CloudWatch Logs** – Execution logs & timing * **(Optional) AWS Glue** – For automatic schema cataloging (not required for POC) * **(Optional) API Gateway or Lambda Function URL + EC2/Flask** – Frontend trigger --- ## 3) Lambda functions (purpose, inputs/outputs) ### 3.1 `duplicateCheck_AthenaQuery` **Purpose:** Run a **known‑good Athena query** that finds duplicate invoices (AP‑R5) by (`vendor_id`,`invoice_number`,`invoice_amount`,`invoice_date`). **Event →** ```json { "table": "invoices_test" } ``` **Response ←** ```json { "statusCode": 200, "queryId": "...", "columns": ["vendor_id","invoice_number","invoice_amount","invoice_date","duplicate_count"], "rows": [["ACME","INV-1001","1200.0","", "2"], ...], "s3_result": {"bucket":"<bucket>","key":"<path>.csv"}, "download_url": "https://...presigned..." } ``` **Notes** * Uses env vars: `ATHENA_DB`, `ATHENA_WORKGROUP`, `ATHENA_OUTPUT` (s3://…/athena-results/) * Waits for Athena completion and returns both **rows** and a **presigned URL** to the CSV * Timeouts: set Lambda timeout ≥ 30s (Athena latency) --- ### 3.2 `gp_Agent` **Purpose:** Orchestrator + general agent. * Builds a **schema hint** by querying `information_schema.columns` for allow‑listed tables * Calls **Bedrock** with a very strict system prompt to return **JSON only** with one of three actions: * `duplicate_check` → invokes `duplicateCheck_AthenaQuery` * `ad_hoc_sql` → validates **read‑only SELECT** then runs it in Athena * `unsupported` → explains limitation **Modes & Events** 1. **Chat/Router mode** ```json { "text": "please check duplicate invoices" } ``` 2. **Adapter (direct dup pass‑through)** ```json { "table": "invoices_test" } ``` **Response (examples)** * Duplicate path: ```json { "statusCode": 200, "path": "duplicate_check", "bedrock_invoked": true, "result": { ...rows + download_url... } } ``` * Ad‑hoc SQL path: ```json { "statusCode": 200, "path": "ad_hoc_sql", "bedrock_invoked": true, "submitted_sql": "SELECT vendor_id FROM ap_demo.invoices_test LIMIT 100;", "result": { ...rows + download_url... } } ``` * Unsupported: ```json { "statusCode": 200, "path": "unsupported", "bedrock_invoked": true, "message": "Supported now: duplicate check, read-only SELECT queries (ad-hoc)." } ``` **Env vars** * `ATHENA_DB=ap_demo` * `TABLE_ALLOWLIST=invoices_test` (CSV list) * `ATHENA_WORKGROUP=primary` * `ATHENA_OUTPUT=s3://<bucket>/athena-results/` * `DUP_CHECK_ARN=arn:aws:lambda:...:function:duplicateCheck_AthenaQuery` * `BEDROCK_REGION=us-east-1` *(or a Region where your model/profile is available)* * `BEDROCK_MODEL_ID=us.anthropic.claude-3-5-haiku-20241022-v1:0` *(**inference profile ID**, not base model)*** **Important behaviors** * **SQL validation**: allows only `SELECT` and requires the query to reference an allow‑listed table **with schema prefix** (e.g., `ap_demo.invoices_test`). Adds `LIMIT 100` if missing * **Schema hint**: Minimizes hallucinations by telling the LLM which columns exist * **Error surfacing**: Returns Athena `StateChangeReason` when a query fails (e.g., column doesn’t exist) --- ### 3.3 `duplicateCheck_TaskAgent` (narrow demo) **Purpose:** A minimal, LLM‑based intent classifier that only recognizes **duplicate invoice** instructions and delegates to `duplicateCheck_AthenaQuery`. **Event →** ```json { "text": "please check identical invoices" } ``` **Response ←** ```json { "statusCode": 200, "action": "duplicate_invoice_check", "bedrock_invoked": true, "result": { ...rows + download_url... } } ``` **When to use** * Use `gp_Agent` for the general POC. * Keep `duplicateCheck_TaskAgent` as a tiny, focused example or fallback. --- ## 4) Bedrock configuration * **Model access**: Grant model access in Bedrock console * **Inference region**: We use `us-east-1` where the **inference profile** is available * **Model identifier**: Use a **system‑defined inference profile ID** for on‑demand (example): * `BEDROCK_MODEL_ID=us.anthropic.claude-3-5-haiku-20241022-v1:0` * **IAM permission (router Lambda)**: `bedrock:InvokeModel` on `*` (or scoped to the chosen profile ARN) * **Logging**: Router prints `BEDROCK_RAW_TEXT_LEN`, and on success: intent JSON; on failure: parsing hints > If you call a base model ID that doesn’t support on‑demand, you’ll get a `ValidationException` directing you to use an inference profile. Keep `BEDROCK_REGION` aligned with the profile’s Regions. --- ## 5) Data & Athena setup * **S3 buckets** * `/data/` – your CSV(s) for `ap_demo.invoices_test` * `/athena-results/` – query output location configured in env var `ATHENA_OUTPUT` * **Athena database/workgroup** * `ATHENA_DB=ap_demo`, `ATHENA_WORKGROUP=primary` * **Table** * External table pointing to S3 CSV; define via DDL or use Glue crawler (optional) * **(Optional) AWS Glue** * If used, Glue Crawler can infer schema & create the table automatically; otherwise, create the table manually in Athena --- ## 6) IAM & permissions (minimum) **For `gp_Agent` Lambda role** * `bedrock:InvokeModel` on chosen profile * `athena:StartQueryExecution`, `athena:GetQueryExecution`, `athena:GetQueryResults` * `s3:GetObject`, `s3:PutObject`, `s3:ListBucket` on result bucket/prefix * `lambda:InvokeFunction` on `duplicateCheck_AthenaQuery` **For `duplicateCheck_AthenaQuery` Lambda role** * `athena:StartQueryExecution`, `athena:GetQueryExecution`, `athena:GetQueryResults` * `s3:GetObject`, `s3:PutObject`, `s3:ListBucket` on data & results bucket **(Optional) For `duplicateCheck_TaskAgent`** * `bedrock:InvokeModel` * `lambda:InvokeFunction` on `duplicateCheck_AthenaQuery` **Trust policies** * Each Lambda role trusted by `lambda.amazonaws.com` **(If using VPC‑attached Lambdas)** * Add appropriate **VPC endpoints** (Athena/Glue/S3 interface or gateway) or NAT for public endpoints --- ## 7) Environment variables (summary) | Function | Key | Example | | -------- | ------------------ | ---------------------------------------------------------------- | | All | `ATHENA_DB` | `ap_demo` | | All | `ATHENA_WORKGROUP` | `primary` | | All | `ATHENA_OUTPUT` | `s3://devx-apc5-demo-agentic/athena-results/` | | gp_Agent | `TABLE_ALLOWLIST` | `invoices_test` | | gp_Agent | `DUP_CHECK_ARN` | `arn:aws:lambda:us-east-2:…:function:duplicateCheck_AthenaQuery` | | gp_Agent | `BEDROCK_REGION` | `us-east-1` | | gp_Agent | `BEDROCK_MODEL_ID` | `us.anthropic.claude-3-5-haiku-20241022-v1:0` | --- ## 8) Deployment & testing 1. **Upload CSVs** to S3 and set/create the `ap_demo.invoices_test` table (Athena or Glue) 2. **Create buckets** and set `ATHENA_OUTPUT` to your results prefix 3. **Deploy Lambdas** (zip/upload or from console), set env vars, attach IAM roles 4. **Test `duplicateCheck_AthenaQuery` directly** with: ```json { "table": "invoices_test" } ``` 5. **Set `DUP_CHECK_ARN` in `gp_Agent`** to the ARN from step 4 6. **Test `gp_Agent`** with events: ```json { "text": "please check duplicate invoices" } { "text": "list vendor_id from ap_demo.invoices_test" } ``` 7. **Observe logs** in CloudWatch: intent JSON, timing, Athena reasons on failure *(Optional)* Expose via **API Gateway or Lambda Function URL** and call from EC2/Flask UI. --- ## 9) Security & guardrails * **SELECT‑only** enforcement for ad‑hoc SQL; rejects DDL/DML/CTAS/UNLOAD * **Table allow‑list** requires full `db.table` reference * **Limit injection** adds `LIMIT 100` if absent * **Schema hint** constrains LLM to existing columns → fewer hallucinations * **No PII** in prompts; only structure (column names/types) * **Presigned URLs** expire (default 15 min) for results * **IAM least‑privilege**: scope S3/Athena access to specific buckets and workgroup --- ## 10) Cost profile (POC) * Athena: ~$5/TB scanned; keep CSVs compact/partitioned, add LIMITs * Lambda: ms‑level charges; tiny for this POC * Bedrock: per‑token; schema hints are small (cheap) and only one call per request * S3: storage + GET for result files --- ## 11) Troubleshooting **Symptom:** `"Athena FAILED"` with no details * We now surface `StateChangeReason`; check `error` string in response **Symptom:** LLM returns non‑JSON → `JSONDecodeError` * Router prints `BEDROCK_PAYLOAD_NONJSON` or `BEDROCK_JSON_PARSE_ERR`; our parser strips code fences & extracts first `{...}` block * Tighten prompt or reduce schema text **Symptom:** `ValidationException … on‑demand not supported` * You used a **base model ID**. Switch to **inference profile ID** (e.g., `us.anthropic.claude-3-5-haiku-20241022-v1:0`) and ensure `BEDROCK_REGION` supports it **Symptom:** Duplicate path returns `Missing 'text' in request.` * `DUP_CHECK_ARN` points to the wrong Lambda (a chat function). Set it to `duplicateCheck_AthenaQuery` ARN **Symptom:** LLM wrote SQL with unknown columns (e.g., `created_by`) * Add that column to the dataset/schema, or adjust the ask. The schema hint + prompt now says “Use **only** allowed columns.” --- ## 12) Extending the POC * **More controls**: add new Task Lambdas (e.g., 3‑way match, bank‑account verification hits) * **Tax compliance (AP‑R10/AP‑C10)**: use Textract for W‑9/W‑8 and rule‑based Bedrock agent to flag exceptions * **Step Functions**: orchestrate multi‑step flows (classify → pick task → validate → report writer) * **Knowledge Base/Guardrails**: Bedrock KB for policy retrieval; Guardrails for prompt safety & deny‑lists * **Report Agent**: generate standardized audit report (Markdown/PDF) with traceable evidence links --- ## 13) Appendix – Code placement * `duplicateCheck_AthenaQuery` – **fixed SQL** for AP‑R5 duplicates * `gp_Agent` – **router + Bedrock** + **ad‑hoc SELECT** (with validators) * `duplicateCheck_TaskAgent` – **narrow intent** demo (duplicate only), optional > Keep `duplicateCheck_AthenaQuery` separate so `gp_Agent` can invoke it via `DUP_CHECK_ARN` without recursion. --- ## 14) Quick reference (copy/paste test events) **Duplicate check** ```json { "text": "please check duplicate invoices" } ``` **Ad‑hoc list** ```json { "text": "list vendor_id from ap_demo.invoices_test" } ``` **Direct duplicate (adapter mode)** ```json { "table": "invoices_test" } ``` --- **End of README**