diff --git a/bedrock-semantic-cache-s3vectors-sam/README.md b/bedrock-semantic-cache-s3vectors-sam/README.md new file mode 100644 index 000000000..3db30eb07 --- /dev/null +++ b/bedrock-semantic-cache-s3vectors-sam/README.md @@ -0,0 +1,151 @@ +# Serverless semantic cache for Amazon Bedrock (with Amazon S3 Vectors) + +Return cached answers for **semantically similar** prompts - different wording still hits - so you skip the LLM call on repeats and near-repeats. Cuts Amazon Bedrock cost and latency, scales to zero, and drops in front of any model. + +Learn more at Serverless Land Patterns: https://serverlessland.com/patterns/bedrock-semantic-cache-s3vectors-sam + +> Important: this application uses AWS services (AWS Lambda, Amazon Bedrock, Amazon S3 Vectors, AWS Systems Manager) and there are costs associated with these services after the Free Tier usage. You are responsible for any AWS costs incurred. No warranty is implied in this example. + +--- + +## TL;DR - read this first + +- **What it is:** a Lambda in front of Amazon Bedrock that caches answers by *meaning* (not exact text). Same question asked three different ways -> one Bedrock call, two instant cache hits. +- **Best for:** FAQ / support bots, docs Q&A, high-traffic assistants - anywhere many users ask the same things in different words. +- **Not for:** answers that must be exact, fresh, or per-user (unless you add namespacing / invalidation / a verify step). +- **Cost:** a cache hit skips the expensive LLM call and pays only a tiny embedding + vector query. Break-even is a few percent hit rate, so any repetitive workload is a net win. +- **Distinct from Bedrock native features:** native Prompt Caching is *exact-prefix* only (one character breaks it); Intelligent Prompt Routing picks a cheaper model. This caches by *semantic similarity* and skips the model entirely. They complement each other. + +## Where it shines + +- **Repetitive, paraphrase-heavy traffic.** Research shows ~31% of LLM queries are semantically similar to a prior one - those become instant, free hits. +- **Latency-sensitive UX.** Measured ~7x faster on a hit (~230 ms vs ~1,700 ms). +- **Cost-sensitive, high-volume assistants.** Every hit is one fewer Bedrock invocation and does **not** count against your Bedrock TPM/RPM limits (throttle relief under load). +- **Any model / provider.** The cache is model-agnostic; the on-miss call is a drop-in for Bedrock or an external model. + +## Where it will not shine + +- **Unique, one-off prompts.** No repetition -> ~0 hits -> you pay a tiny per-request overhead for nothing. Skip it here. +- **Answers that must be exact or fresh.** A similar-but-not-identical prompt can return a subtly different prior answer. Mitigate with a higher threshold + TTL, or bypass the cache for such routes. +- **Per-user / personalized answers.** Namespace the cache per user, or don't cache these. +- **Semantic antonyms.** The built-in negation guard catches "not / n't", but not opposites like "cheapest" vs "most expensive". For high-stakes (financial, legal, medical), add an optional LLM equivalence-verify on borderline hits. + +## How it saves money regardless (the math) + +Every request pays a tiny "cache tax": one embedding call (~$0.00002) + one vector query (fractions of a cent). You **save** on every HIT because you skip the LLM call (cents to dollars, especially with large prompts / RAG context / bigger models). + +``` +net savings = (hits x LLM cost skipped) - (all requests x tiny cache tax) +``` + +Because the tax is orders of magnitude smaller than an LLM call, **break-even is roughly a 1-5% hit rate.** Real repetitive workloads sit far above that, and savings **compound** as the cache warms. The only losing case is genuinely zero repetition. Plus: everything is pay-per-use and **scales to zero** (Lambda + S3 Vectors), so there is no idle cost. + +--- + +## How it works + +``` +prompt --> [Lambda] --embed--> Amazon Bedrock (Titan v2 -> 1024-dim vector) + | + |--search--> Amazon S3 Vectors (cosine top-K; answer is stored in vector metadata) + | |-- HIT (sim >= threshold, fresh, current epoch, negation-parity) --> return cached answer (~230 ms, $0 LLM) + | |-- MISS --> + |--generate--> Amazon Bedrock LLM (writes the answer) + |--store-----> Amazon S3 Vectors (embedding + answer + model + created_at + epoch) + |--return + (force-invalidate epoch is stored in AWS Systems Manager Parameter Store) +``` + +- **Amazon Bedrock** is used two ways: **embeddings** (turn text into a meaning vector so matching is semantic) and the **LLM** (answer on a miss). +- **Amazon S3 Vectors** is the cache store *and* the similarity search - pay-per-use, no always-on cost. This is the primitive that makes a serverless semantic cache economical. +- **AWS Lambda** is stateless glue. The cache lives entirely in S3 Vectors, so it survives cold starts, redeploys, and env recycling. +- **SSM Parameter Store** holds the epoch counter for force-invalidation. + +### Correctness features +- **Tunable similarity threshold** (default cosine 0.85) - per-deploy and per-request. +- **Freshness TTL** - entries older than `TTL_SECONDS` are treated as a miss. +- **Force-invalidate** - bump one epoch number -> every prior entry instantly misses (no deletes/scans). For big changes that can't wait for TTL. +- **Negation-parity guard** - "is X" vs "is NOT X" embed ~identically but mean the opposite; the guard blocks that false hit. +- **top-K + iterate** - a stale duplicate near-neighbour never blocks a valid hit. + +## Requirements + +- An AWS account with permissions for AWS Lambda, Amazon Bedrock, Amazon S3 Vectors, and AWS Systems Manager. +- [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html) v2, recent enough to include the `s3vectors` commands. +- [AWS SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html). +- Amazon Bedrock **model access enabled** for the embeddings model (`amazon.titan-embed-text-v2:0`) and the text model (`amazon.nova-lite-v1:0`) in your Region. +- A Region where Amazon S3 Vectors and Amazon Bedrock are available (e.g. `us-east-1`). + +## Deployment + +S3 Vectors is not yet a CloudFormation resource, so create the vector store first (two commands), then deploy the rest with SAM. + +```bash +# 1. Create the S3 Vectors bucket and a cosine index (1024 dims = Titan v2) +export VECTOR_BUCKET="semantic-cache-$(aws sts get-caller-identity --query Account --output text)" +aws s3vectors create-vector-bucket --vector-bucket-name "$VECTOR_BUCKET" +aws s3vectors create-index \ + --vector-bucket-name "$VECTOR_BUCKET" \ + --index-name prompt-cache --data-type float32 --dimension 1024 --distance-metric cosine \ + --metadata-configuration 'nonFilterableMetadataKeys=prompt,response,model,created_at,epoch' + +# 2. Build and deploy the Lambda + IAM + SSM epoch parameter +sam build +sam deploy --guided +# - VectorBucket: value of $VECTOR_BUCKET above +# - VectorIndex : prompt-cache +# - ApiKey : (optional) a secret for the x-api-key header, or leave blank for IAM-only +``` + +Note the `FunctionUrl` and `FunctionName` outputs. + +## Testing + +The function URL uses AWS_IAM auth (SigV4). The simplest test is a direct invoke: + +```bash +KEY="" +payload() { python3 -c "import json,sys;print(json.dumps({'headers':{'x-api-key':'$KEY'},'body':json.dumps({'prompt':sys.argv[1]})}))" "$1" > ev.json; } + +# MISS (calls Bedrock) +payload "What is the capital of France?"; aws lambda invoke --function-name semantic-cache --cli-binary-format raw-in-base64-out --payload file://ev.json out.json; cat out.json +sleep 6 +# HIT - exact repeat (cached=true, similarity ~1.0, ~230ms) +payload "What is the capital of France?"; aws lambda invoke --function-name semantic-cache --cli-binary-format raw-in-base64-out --payload file://ev.json out.json; cat out.json +# HIT - semantic (different words) +payload "Which city is the capital of France?"; aws lambda invoke --function-name semantic-cache --cli-binary-format raw-in-base64-out --payload file://ev.json out.json; cat out.json +``` + +Force-invalidate (e.g., after a data/policy change): + +```bash +python3 -c "import json;print(json.dumps({'headers':{'x-api-key':'$KEY'},'body':json.dumps({'action':'invalidate'})}))" > ev.json +aws lambda invoke --function-name semantic-cache --cli-binary-format raw-in-base64-out --payload file://ev.json out.json; cat out.json +# -> {"invalidated": true, "epoch": N}. Every prior answer now misses (propagates within ~30s). +``` + +Expected: exact/semantic repeats HIT (`cached=true` with a similarity score); unrelated prompts MISS; after `invalidate`, the same prompt MISSes once, then HITs again once re-cached. + +## Tuning + +| Setting | Env var / request field | Effect | +|---|---|---| +| Similarity threshold | `SIM_THRESHOLD` (deploy) or `threshold` (per request) | Higher = stricter matching, fewer but safer hits | +| Freshness | `TTL_SECONDS` | Max age of a served answer | +| Force-invalidate | `POST {"action":"invalidate"}` | Invalidate the whole cache instantly | +| Models | `EMBED_MODEL`, `LLM_MODEL` | Swap embeddings / answer model | + +## Cleanup + +```bash +sam delete +aws s3vectors delete-index --vector-bucket-name "$VECTOR_BUCKET" --index-name prompt-cache +aws s3vectors delete-vector-bucket --vector-bucket-name "$VECTOR_BUCKET" +aws ssm delete-parameter --name /semantic-cache/epoch +``` + +--- + +Author: Manish S + +Copyright 2026 Amazon.com, Inc. or its affiliates. All Rights Reserved. SPDX-License-Identifier: MIT-0 diff --git a/bedrock-semantic-cache-s3vectors-sam/example-pattern.json b/bedrock-semantic-cache-s3vectors-sam/example-pattern.json new file mode 100644 index 000000000..7e95c6dab --- /dev/null +++ b/bedrock-semantic-cache-s3vectors-sam/example-pattern.json @@ -0,0 +1,69 @@ +{ + "title": "Serverless semantic cache for Amazon Bedrock with Amazon S3 Vectors", + "description": "Cut Amazon Bedrock cost and latency by returning cached answers for semantically-similar prompts, using AWS Lambda and Amazon S3 Vectors.", + "language": "Python", + "level": "300", + "framework": "AWS SAM", + "patternArch": { + "icon1": { "x": 20, "y": 50, "service": "lambda", "label": "AWS Lambda (semantic cache)" }, + "icon2": { "x": 55, "y": 30, "service": "bedrock", "label": "Amazon Bedrock (embeddings + LLM)" }, + "icon3": { "x": 55, "y": 70, "service": "s3", "label": "Amazon S3 Vectors (cache store)" }, + "line1": { "from": "icon1", "to": "icon2" }, + "line2": { "from": "icon1", "to": "icon3" } + }, + "introBox": { + "headline": "How it works", + "text": [ + "Large language model (LLM) calls are slow and expensive, yet a large share of production prompts are paraphrases of ones already answered. This pattern places an AWS Lambda function in front of Amazon Bedrock that returns a cached answer whenever an incoming prompt is semantically similar to a previous one - so you skip the LLM call entirely on repeats and near-repeats.", + "When a request arrives, the Lambda function embeds the prompt with an Amazon Bedrock embeddings model (Amazon Titan Text Embeddings v2, 1024 dimensions). It then queries an Amazon S3 Vectors index for the nearest stored prompt using cosine similarity. Amazon S3 Vectors returns the closest match together with its distance and metadata in a single call, and the cached answer is stored directly in that metadata - so no separate database is required.", + "On a cache HIT (cosine similarity at or above a configurable threshold, the entry still within its freshness TTL, the current cache epoch, and passing a negation-parity guard) the function returns the stored answer in milliseconds at zero LLM cost. On a MISS, the function calls the Bedrock text model to generate the answer, stores the prompt embedding plus the answer, model, timestamp and epoch back into Amazon S3 Vectors, and returns the fresh result.", + "Correctness is designed, not assumed. A tunable similarity threshold trades precision for hit rate. A freshness TTL bounds staleness. A one-call force-invalidation bumps a global epoch stored in AWS Systems Manager Parameter Store, so every previously cached answer instantly becomes a miss without deleting or scanning anything - ideal for a policy or data change that cannot wait for the TTL. A negation-parity guard prevents the classic semantic-cache trap where a prompt and its negation ('is X' versus 'is NOT X') embed almost identically but mean the opposite. The query uses top-K retrieval and iterates candidates, so a stale duplicate near-neighbour never blocks a valid hit.", + "Amazon S3 Vectors is what makes this economical and fully serverless: pay-per-use vector storage and search that scales to zero, instead of an always-on vector database. The AWS Lambda function is stateless - the cache persists entirely in Amazon S3 Vectors, so it survives cold starts, redeploys, and execution-environment recycling. Access is over an IAM-signed Lambda function URL, with an optional application-level API key. IAM permissions follow least privilege: bedrock:InvokeModel is scoped to foundation models; s3vectors actions (QueryVectors, GetVectors, ListVectors, PutVectors, GetIndex) are scoped to the specific vector bucket and index; and ssm:GetParameter/PutParameter is scoped to the single epoch parameter.", + "The embeddings model and the answer model are both configurable, and the on-miss call is a drop-in for any model provider - the caching layer itself is model-agnostic. Best fit: FAQ and support assistants, documentation Q&A, and high-traffic assistants where users ask the same things in different words. Not intended for answers that must be exact, fresh, or per-user unless combined with namespacing, invalidation, or an equivalence-verification step." + ] + }, + "gitHub": { + "template": { + "repoURL": "https://github.com/aws-samples/serverless-patterns/tree/main/bedrock-semantic-cache-s3vectors-sam", + "templateURL": "serverless-patterns/bedrock-semantic-cache-s3vectors-sam", + "projectFolder": "bedrock-semantic-cache-s3vectors-sam", + "templateFile": "template.yaml" + } + }, + "resources": { + "headline": "Additional resources", + "bullets": [ + { "text": "Amazon S3 Vectors - vector storage in Amazon S3", "link": "https://docs.aws.amazon.com/AmazonS3/latest/userguide/s3-vectors.html" }, + { "text": "Amazon Bedrock - Titan Text Embeddings", "link": "https://docs.aws.amazon.com/bedrock/latest/userguide/titan-embedding-models.html" }, + { "text": "Amazon Bedrock prompt caching (native, prefix-based) - complements this pattern", "link": "https://docs.aws.amazon.com/bedrock/latest/userguide/prompt-caching.html" }, + { "text": "AWS Lambda function URLs", "link": "https://docs.aws.amazon.com/lambda/latest/dg/lambda-urls.html" } + ] + }, + "deploy": { + "text": [ + "See the README for the two S3 Vectors setup commands, then: sam build && sam deploy --guided" + ] + }, + "testing": { + "headline": "Testing", + "text": [ + "See the GitHub repo for detailed testing instructions (miss, exact hit, semantic hit, force-invalidate)." + ] + }, + "cleanup": { + "headline": "Cleanup", + "text": [ + "1. Delete the stack: sam delete.", + "2. Delete the S3 Vectors index and bucket: aws s3vectors delete-index ... then aws s3vectors delete-vector-bucket ...." + ] + }, + "authors": [ + { + "name": "Manish S", + "image": "", + "bio": "", + "linkedin": "", + "twitter": "" + } + ] +} diff --git a/bedrock-semantic-cache-s3vectors-sam/src/cache.py b/bedrock-semantic-cache-s3vectors-sam/src/cache.py new file mode 100644 index 000000000..aafc14c5d --- /dev/null +++ b/bedrock-semantic-cache-s3vectors-sam/src/cache.py @@ -0,0 +1,118 @@ +"""Serverless Semantic Cache for Amazon Bedrock. +Embed prompt -> query S3 Vectors for a semantically-similar prior prompt -> +HIT (similarity >= threshold, fresh by TTL, and current epoch) return cached response; +MISS -> call Bedrock, store {embedding + metadata}, return. +Force-invalidate: POST {"action":"invalidate"} bumps a global epoch (SSM) -> every +prior entry instantly becomes a miss. No deletes, no scanning, O(1).""" +import json, os, re, time, uuid +import boto3 + +_NEG = {"not","no","never","without","cannot","nor","neither","none","cant","dont","doesnt","isnt","arent","wont","shouldnt","wasnt","werent","hasnt","havent","didnt","aint"} +def _has_negation(text): + for t in re.findall(r"[a-z']+", (text or "").lower()): + if t.replace("'","") in _NEG or t.endswith("n't"): + return True + return False + +REGION = os.environ.get("AWS_REGION", "us-east-1") +BUCKET = os.environ["VECTOR_BUCKET"] +INDEX = os.environ["VECTOR_INDEX"] +EMBED_MODEL = os.environ.get("EMBED_MODEL", "amazon.titan-embed-text-v2:0") +DEFAULT_MODEL = os.environ.get("LLM_MODEL", "amazon.nova-lite-v1:0") +SIM_THRESHOLD = float(os.environ.get("SIM_THRESHOLD", "0.85")) +TTL_SECONDS = int(os.environ.get("TTL_SECONDS", "86400")) +API_KEY = os.environ.get("API_KEY", "") +EPOCH_PARAM = os.environ.get("EPOCH_PARAM", "/semantic-cache/epoch") + +br = boto3.client("bedrock-runtime", region_name=REGION) +s3v = boto3.client("s3vectors", region_name=REGION) +ssm = boto3.client("ssm", region_name=REGION) + +_epoch = {"val": None, "ts": 0.0} + + +def current_epoch(): + now = time.time() + if _epoch["val"] is None or now - _epoch["ts"] > 30: # refresh at most every 30s + try: + _epoch["val"] = ssm.get_parameter(Name=EPOCH_PARAM)["Parameter"]["Value"] + except Exception: + _epoch["val"] = "1" + _epoch["ts"] = now + return _epoch["val"] + + +def bump_epoch(): + try: + new = str(int(current_epoch()) + 1) + except Exception: + new = str(int(time.time())) + ssm.put_parameter(Name=EPOCH_PARAM, Value=new, Type="String", Overwrite=True) + _epoch["val"], _epoch["ts"] = new, time.time() + return new + + +def _resp(code, obj): + return {"statusCode": code, "headers": {"Content-Type": "application/json"}, "body": json.dumps(obj)} + + +def embed(text): + r = br.invoke_model(modelId=EMBED_MODEL, body=json.dumps({"inputText": text})) + return json.loads(r["body"].read())["embedding"] + + +def llm(prompt, model): + r = br.converse(modelId=model, messages=[{"role": "user", "content": [{"text": prompt}]}]) + return r["output"]["message"]["content"][0]["text"] + + +def handler(event, context): + t0 = time.time() + headers = {k.lower(): v for k, v in (event.get("headers") or {}).items()} + if API_KEY and headers.get("x-api-key") != API_KEY: + return _resp(401, {"error": "unauthorized"}) + body = {} + if event.get("body"): + try: + body = json.loads(event["body"]) + except Exception: + body = {} + + # --- force-invalidate: bump epoch, everything before is now a miss --- + if body.get("action") == "invalidate": + new = bump_epoch() + return _resp(200, {"invalidated": True, "epoch": new, + "note": "all entries cached before this epoch now miss"}) + + prompt = (body.get("prompt") or "").strip() + model = body.get("model", DEFAULT_MODEL) + threshold = float(body.get("threshold", SIM_THRESHOLD)) + if not prompt: + return _resp(400, {"error": "missing 'prompt'"}) + + ep = current_epoch() + vec = embed(prompt) + + q = s3v.query_vectors(vectorBucketName=BUCKET, indexName=INDEX, topK=5, + queryVector={"float32": vec}, returnDistance=True, returnMetadata=True) + for m in q.get("vectors", []): + sim = 1.0 - float(m.get("distance", 2.0)) + md = m.get("metadata", {}) or {} + fresh = (time.time() - int(md.get("created_at", "0") or 0)) < TTL_SECONDS + current = md.get("epoch") == ep + # negation-parity guard: 'X' vs 'NOT X' embed ~identically but mean the opposite + neg_ok = _has_negation(prompt) == _has_negation(md.get("prompt", "")) + if sim >= threshold and fresh and current and neg_ok and md.get("response"): + return _resp(200, {"cached": True, "similarity": round(sim, 4), "epoch": ep, + "matched_prompt": md.get("prompt"), "response": md["response"], + "model": md.get("model"), "latency_ms": int((time.time() - t0) * 1000)}) + + answer = llm(prompt, model) + s3v.put_vectors(vectorBucketName=BUCKET, indexName=INDEX, vectors=[{ + "key": uuid.uuid4().hex, + "data": {"float32": vec}, + "metadata": {"prompt": prompt, "response": answer, "model": model, + "created_at": str(int(time.time())), "epoch": ep}, + }]) + return _resp(200, {"cached": False, "similarity": None, "epoch": ep, "response": answer, + "model": model, "latency_ms": int((time.time() - t0) * 1000)}) diff --git a/bedrock-semantic-cache-s3vectors-sam/src/requirements.txt b/bedrock-semantic-cache-s3vectors-sam/src/requirements.txt new file mode 100644 index 000000000..7ef81e07c --- /dev/null +++ b/bedrock-semantic-cache-s3vectors-sam/src/requirements.txt @@ -0,0 +1 @@ +boto3>=1.43.35 diff --git a/bedrock-semantic-cache-s3vectors-sam/template.yaml b/bedrock-semantic-cache-s3vectors-sam/template.yaml new file mode 100644 index 000000000..8d592838b --- /dev/null +++ b/bedrock-semantic-cache-s3vectors-sam/template.yaml @@ -0,0 +1,101 @@ +AWSTemplateFormatVersion: "2010-09-09" +Transform: AWS::Serverless-2016-10-31 +Description: > + Serverless semantic cache for Amazon Bedrock. A Lambda function embeds each prompt, + finds semantically-similar prior prompts in Amazon S3 Vectors, and returns the cached + answer on a hit (skipping the LLM) or calls Amazon Bedrock and caches the result on a miss. + (bedrock-semantic-cache-s3vectors-sam) + +Parameters: + VectorBucket: + Type: String + Description: Name of the S3 Vectors vector bucket that stores the cache (create before deploy - see README). + VectorIndex: + Type: String + Default: prompt-cache + Description: Name of the S3 Vectors index (dimension 1024, distance metric cosine). + SimThreshold: + Type: String + Default: "0.85" + Description: Cosine-similarity threshold for a cache hit (0-1). Higher = stricter matching. + TtlSeconds: + Type: String + Default: "86400" + Description: Freshness window in seconds. Entries older than this are treated as a miss. + ApiKey: + Type: String + Default: "" + NoEcho: true + Description: Optional app-level API key checked in the x-api-key header. Leave blank to rely only on IAM auth. + EmbedModel: + Type: String + Default: amazon.titan-embed-text-v2:0 + Description: Bedrock embeddings model used for semantic matching (must be consistent; matches the index dimension). + LlmModel: + Type: String + Default: amazon.nova-lite-v1:0 + Description: Bedrock model used to generate the answer on a cache miss. + +Resources: + # Global cache epoch for one-call force-invalidation (bump this to invalidate everything). + CacheEpoch: + Type: AWS::SSM::Parameter + Properties: + Name: /semantic-cache/epoch + Type: String + Value: "1" + + SemanticCacheFunction: + Type: AWS::Serverless::Function + Properties: + FunctionName: semantic-cache + Runtime: python3.13 + Handler: cache.handler + CodeUri: src/ + Timeout: 60 + MemorySize: 512 + Architectures: + - arm64 + Environment: + Variables: + VECTOR_BUCKET: !Ref VectorBucket + VECTOR_INDEX: !Ref VectorIndex + EMBED_MODEL: !Ref EmbedModel + LLM_MODEL: !Ref LlmModel + SIM_THRESHOLD: !Ref SimThreshold + TTL_SECONDS: !Ref TtlSeconds + API_KEY: !Ref ApiKey + EPOCH_PARAM: /semantic-cache/epoch + FunctionUrlConfig: + AuthType: AWS_IAM + Policies: + - Statement: + - Sid: BedrockInvoke + Effect: Allow + Action: bedrock:InvokeModel + Resource: "arn:aws:bedrock:*::foundation-model/*" + - Sid: S3Vectors + Effect: Allow + Action: + - s3vectors:QueryVectors + - s3vectors:GetVectors + - s3vectors:ListVectors + - s3vectors:PutVectors + - s3vectors:GetIndex + Resource: + - !Sub "arn:aws:s3vectors:${AWS::Region}:${AWS::AccountId}:bucket/${VectorBucket}" + - !Sub "arn:aws:s3vectors:${AWS::Region}:${AWS::AccountId}:bucket/${VectorBucket}/index/*" + - Sid: EpochParam + Effect: Allow + Action: + - ssm:GetParameter + - ssm:PutParameter + Resource: !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/semantic-cache/epoch" + +Outputs: + FunctionUrl: + Description: IAM-signed HTTPS endpoint of the semantic cache. + Value: !GetAtt SemanticCacheFunctionUrl.FunctionUrl + FunctionName: + Description: Lambda function name (for aws lambda invoke testing). + Value: !Ref SemanticCacheFunction