-
Notifications
You must be signed in to change notification settings - Fork 1k
New serverless pattern - sam-dotnet-responsestreaming-bedrock-agent #3236
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| ## .NET | ||
| bin/ | ||
| obj/ | ||
| *.user | ||
| *.suo | ||
| *.userosscache | ||
| *.sln.docstates | ||
|
|
||
| ## SAM | ||
| .aws-sam/ | ||
| samconfig.toml | ||
|
|
||
| ## IDE | ||
| .idea/ | ||
| .vs/ | ||
| *.swp |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,214 @@ | ||||||
| # AWS Lambda Response Streaming with Bedrock Agent — AI Trip Planner (.NET) | ||||||
|
|
||||||
| This sample demonstrates an **AI agent** built with Amazon Bedrock (Claude Sonnet) **tool use** combined with **Lambda Response Streaming through API Gateway**. The agent uses a tool-calling loop to build a day-by-day trip itinerary, streaming each day's plan to the client as it's generated. | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
|
||||||
| ## Architecture | ||||||
|
|
||||||
| ``` | ||||||
| ┌──────────────┐ ┌──────────────────┐ ┌────────────────────┐ ┌─────────────┐ | ||||||
| │ Client │ │ API Gateway │ │ Lambda Function │ │ Amazon │ | ||||||
| │ (curl) │──GET─▶│ REST API │──────▶│ (Agent Loop) │◀─────▶│ Bedrock │ | ||||||
| │ │◀─stream│ ResponseTransfer │◀stream│ │ │ (Claude) │ | ||||||
| │ │ │ Mode: STREAM │ │ Day 1... Day 2... │ │ │ | ||||||
| └──────────────┘ └──────────────────┘ └────────────────────┘ └─────────────┘ | ||||||
| │ | ||||||
| Tool Use Loop: | ||||||
| 1. Model calls add_day_plan tool | ||||||
| 2. Lambda formats & streams day to client | ||||||
| 3. Lambda returns tool result to model | ||||||
| 4. Repeat until all days planned | ||||||
| ``` | ||||||
|
|
||||||
| ## What It Demonstrates | ||||||
|
|
||||||
| - **Agentic tool use** — The model calls an `add_day_plan` tool for each day, driving a structured multi-turn loop. The Lambda orchestrates the conversation and decides when the agent is done. | ||||||
| - **Streaming structured output** — Each day's itinerary is streamed to the client as soon as the model generates it, giving progressive feedback during a multi-step agent workflow. | ||||||
| - **API Gateway response streaming** — Uses `ResponseTransferMode: STREAM` so the client sees each day appear incrementally over the connection. | ||||||
| - **No external dependencies** — The agent relies only on Claude Sonnet's knowledge. No databases, APIs, or third-party services needed. | ||||||
| - **Practical AI pattern** — Represents a real-world agentic pattern where a model drives a structured workflow through tool calling. | ||||||
|
|
||||||
| ## Project Structure | ||||||
|
|
||||||
| ``` | ||||||
| ├── template.yaml # SAM template (API Gateway + Lambda + Bedrock IAM) | ||||||
| ├── events/ | ||||||
| │ └── plan-trip.json # Sample API Gateway proxy event | ||||||
| └── src/ | ||||||
| ├── Program.cs # Agent loop: Bedrock tool use → stream formatted output | ||||||
| └── TripPlannerStreaming.csproj | ||||||
| ``` | ||||||
|
|
||||||
| --- | ||||||
|
|
||||||
| ## Prerequisites | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please add, that the sample will incur costs in the used AWS account |
||||||
|
|
||||||
| - [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0) | ||||||
| - [AWS SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/install-sam-cli.html) | ||||||
| - AWS account with credentials configured (`aws configure`) | ||||||
| - **Amazon Bedrock model access** — Enable access to Claude Sonnet in the [Bedrock console](https://console.aws.amazon.com/bedrock/home#/modelaccess) | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. not needed since ~Nov/2025 |
||||||
|
|
||||||
| --- | ||||||
|
|
||||||
| ## Build & Deploy | ||||||
|
|
||||||
| ### Build | ||||||
|
|
||||||
| ```bash | ||||||
| sam build | ||||||
| ``` | ||||||
|
|
||||||
| ### Deploy | ||||||
|
|
||||||
| ```bash | ||||||
| sam deploy --guided | ||||||
| ``` | ||||||
|
|
||||||
| Follow the prompts to configure stack name, region, and confirm IAM role creation. On subsequent deploys: | ||||||
|
|
||||||
| ```bash | ||||||
| sam deploy | ||||||
| ``` | ||||||
|
|
||||||
| --- | ||||||
|
|
||||||
| ## Testing | ||||||
|
|
||||||
| ### 1. Get the API endpoint | ||||||
|
|
||||||
| ```bash | ||||||
| API_URL=$(aws cloudformation describe-stacks \ | ||||||
| --stack-name sam-dotnet-responsestreaming-bedrock-agent \ | ||||||
| --query "Stacks[0].Outputs[?OutputKey=='ApiEndpoint'].OutputValue" \ | ||||||
| --output text) | ||||||
| ``` | ||||||
|
|
||||||
| ### 2. Plan a trip with defaults (Tokyo, 3 days) | ||||||
|
|
||||||
| ```bash | ||||||
| curl --no-buffer "$API_URL" | ||||||
| ``` | ||||||
|
|
||||||
| You'll see each day's plan stream in progressively: | ||||||
|
|
||||||
| ``` | ||||||
| ✈️ Trip Planner — Tokyo, Japan (3 days) | ||||||
| Interests: culture, food, and nature | ||||||
| ════════════════════════════════════════════════════════════════ | ||||||
|
|
||||||
| 📅 Day 1: Traditional Tokyo — Temples & Tsukiji | ||||||
| 🌅 Morning: Start at Senso-ji temple in Asakusa... | ||||||
| ☀️ Afternoon: Explore the Tsukiji Outer Market... | ||||||
| 🌙 Evening: Head to Shinjuku for yakitori... | ||||||
| 💡 Tips: Get a Suica card at the airport... | ||||||
|
|
||||||
| 📅 Day 2: Modern Tokyo — Shibuya & Harajuku | ||||||
| ... | ||||||
|
|
||||||
| 📅 Day 3: Nature & Serenity — Day Trip to Nikko | ||||||
| ... | ||||||
|
|
||||||
| ════════════════════════════════════════════════════════════════ | ||||||
|
|
||||||
| Overall trip tips: ... | ||||||
|
|
||||||
| ✅ Happy travels! | ||||||
| ``` | ||||||
|
|
||||||
| ### 3. Custom trips | ||||||
|
|
||||||
| ```bash | ||||||
| # Barcelona for 5 days, food & architecture | ||||||
| curl --no-buffer "$API_URL?destination=Barcelona,+Spain&days=5&interests=architecture,+tapas,+and+beach" | ||||||
|
|
||||||
| # Iceland adventure | ||||||
| curl --no-buffer "$API_URL?destination=Iceland&days=7&interests=northern+lights,+glaciers,+and+hot+springs" | ||||||
|
|
||||||
| # Quick weekend in Paris | ||||||
| curl --no-buffer "$API_URL?destination=Paris,+France&days=2&interests=art,+pastries,+and+wine" | ||||||
| ``` | ||||||
|
|
||||||
| ### Parameters | ||||||
|
|
||||||
| | Parameter | Default | Range | Description | | ||||||
| |---------------|----------------------|--------|----------------------------------------| | ||||||
| | `destination` | Tokyo, Japan | — | Where to travel | | ||||||
| | `days` | 3 | 1–14 | Number of days for the trip | | ||||||
| | `interests` | culture, food, and nature | — | Traveler's interests (comma-separated) | | ||||||
|
|
||||||
| --- | ||||||
|
|
||||||
| ## How the Agent Loop Works | ||||||
|
|
||||||
| ``` | ||||||
| ┌─────────────────────────────────────────────────────────────┐ | ||||||
| │ Agent Loop (Lambda) │ | ||||||
| │ │ | ||||||
| │ 1. Send user request + tool definition to Bedrock │ | ||||||
| │ 2. Model responds with add_day_plan tool call │ | ||||||
| │ 3. Lambda formats the day plan and streams it to client │ | ||||||
| │ 4. Lambda returns tool result ("Day N added") to model │ | ||||||
| │ 5. Model calls tool again for next day (or ends turn) │ | ||||||
| │ 6. Repeat until model provides final summary (no tool call) │ | ||||||
| │ │ | ||||||
| └─────────────────────────────────────────────────────────────┘ | ||||||
| ``` | ||||||
|
|
||||||
| ```csharp | ||||||
| while (continueLoop) | ||||||
| { | ||||||
| var response = await bedrockClient.ConverseAsync(converseRequest); | ||||||
|
|
||||||
| // If model used the add_day_plan tool, format and stream it | ||||||
| foreach (var toolUse in toolUseBlocks) | ||||||
| { | ||||||
| // Extract structured day plan from tool input | ||||||
| var title = input.GetProperty("title").GetString(); | ||||||
| // ... format and write to response stream ... | ||||||
| await writer.FlushAsync(); // Stream to client immediately | ||||||
|
|
||||||
| // Return result so model continues to next day | ||||||
| toolResults.Add(new ContentBlock { ToolResult = ... }); | ||||||
| } | ||||||
|
|
||||||
| // If no tool use, model is done — stream the summary | ||||||
| if (response.StopReason == StopReason.EndTurn) | ||||||
| continueLoop = false; | ||||||
| } | ||||||
| ``` | ||||||
|
|
||||||
| Key points: | ||||||
| - **Tool use drives structure** — Instead of free-form text, the model outputs structured day plans via the `add_day_plan` tool, ensuring consistent formatting. | ||||||
| - **Progressive streaming** — Each day appears as soon as the model generates it. The client doesn't wait for the entire itinerary. | ||||||
| - **Multi-turn conversation** — The agent loop sends tool results back to the model, maintaining conversation context across turns. | ||||||
| - **Graceful termination** — When the model stops calling tools and provides text directly, the loop ends and streams the summary. | ||||||
|
|
||||||
| --- | ||||||
|
|
||||||
| ## Cleanup | ||||||
|
|
||||||
| ```bash | ||||||
| sam delete | ||||||
| ``` | ||||||
|
|
||||||
| --- | ||||||
|
|
||||||
| ## Useful Commands | ||||||
|
|
||||||
| | Command | Description | | ||||||
| |---------|-------------| | ||||||
| | `sam build` | Build the Lambda function | | ||||||
| | `sam deploy --guided` | Deploy with interactive prompts | | ||||||
| | `sam deploy` | Deploy with saved config | | ||||||
| | `sam logs --tail` | Tail CloudWatch logs | | ||||||
| | `sam delete` | Tear down the stack | | ||||||
| | `dotnet build src/` | Build locally without SAM | | ||||||
|
|
||||||
| --- | ||||||
|
|
||||||
| ## References | ||||||
|
|
||||||
| - [Building responsive APIs with API Gateway response streaming](https://aws.amazon.com/blogs/compute/building-responsive-apis-with-amazon-api-gateway-response-streaming/) | ||||||
| - [Bedrock Converse API with tool use](https://docs.aws.amazon.com/bedrock/latest/userguide/tool-use.html) | ||||||
| - [Lambda Response Streaming (.NET SDK PR)](https://github.com/aws/aws-lambda-dotnet/pull/2288) | ||||||
| - [Claude Sonnet on Bedrock](https://docs.aws.amazon.com/bedrock/latest/userguide/model-card-anthropic-claude-sonnet-5.html) | ||||||
| - [AWS SAM Developer Guide](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/) | ||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| { | ||
| "resource": "/plan", | ||
| "path": "/plan", | ||
| "httpMethod": "GET", | ||
| "headers": { | ||
| "Accept": "*/*", | ||
| "Host": "abc123.execute-api.us-east-1.amazonaws.com" | ||
| }, | ||
| "queryStringParameters": { | ||
| "destination": "Barcelona, Spain", | ||
| "days": "4", | ||
| "interests": "architecture, tapas, and beach" | ||
| }, | ||
| "pathParameters": null, | ||
| "stageVariables": null, | ||
| "requestContext": { | ||
| "resourceId": "abc123", | ||
| "resourcePath": "/plan", | ||
| "httpMethod": "GET", | ||
| "requestId": "test-request-id", | ||
| "accountId": "123456789012", | ||
| "stage": "prod", | ||
| "identity": { | ||
| "sourceIp": "127.0.0.1", | ||
| "userAgent": "curl/8.0" | ||
| }, | ||
| "apiId": "abc123" | ||
| }, | ||
| "body": null, | ||
| "isBase64Encoded": false | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,64 @@ | ||
| { | ||
| "title": "Lambda Response Streaming with Bedrock Agent Tool Use (.NET)", | ||
| "description": "An AI trip planner agent using Claude Sonnet tool calling that streams a day-by-day itinerary progressively through API Gateway response streaming.", | ||
| "language": ".NET", | ||
| "level": "300", | ||
| "framework": "SAM", | ||
| "introBox": { | ||
| "headline": "How it works", | ||
| "text": [ | ||
| "This pattern deploys a Lambda function that runs an agentic tool-use loop with Amazon Bedrock (Claude Sonnet). The model calls an add_day_plan tool for each day of a trip itinerary, and the Lambda streams each day's plan to the client as it's generated.", | ||
| "The agent loop continues until all days are planned, giving the user progressive feedback as structured output appears. API Gateway response streaming ensures the client sees each day incrementally over the connection.", | ||
| "This demonstrates a practical AI agent pattern where a model drives a structured workflow through tool calling, with no external databases or APIs required." | ||
| ] | ||
| }, | ||
| "gitHub": { | ||
| "template": { | ||
| "repoURL": "https://github.com/aws-samples/serverless-patterns/tree/main/sam-dotnet-responsestreaming-bedrock-agent", | ||
| "templateURL": "serverless-patterns/sam-dotnet-responsestreaming-bedrock-agent", | ||
| "projectFolder": "sam-dotnet-responsestreaming-bedrock-agent", | ||
| "templateFile": "template.yaml" | ||
| } | ||
| }, | ||
| "resources": { | ||
| "bullets": [ | ||
| { | ||
| "text": "Lambda Response Streaming Documentation", | ||
| "link": "https://docs.aws.amazon.com/lambda/latest/dg/response-streaming.html" | ||
| }, | ||
| { | ||
| "text": "Amazon Bedrock Tool Use (Function Calling)", | ||
| "link": "https://docs.aws.amazon.com/bedrock/latest/userguide/tool-use.html" | ||
| }, | ||
| { | ||
| "text": "Amazon Bedrock Converse API", | ||
| "link": "https://docs.aws.amazon.com/bedrock/latest/userguide/conversation-inference-call.html" | ||
| } | ||
| ] | ||
| }, | ||
| "deploy": { | ||
| "text": [ | ||
| "sam build", | ||
| "sam deploy --guided" | ||
| ] | ||
| }, | ||
| "testing": { | ||
| "text": [ | ||
| "See the GitHub repo for detailed testing instructions." | ||
| ] | ||
| }, | ||
| "cleanup": { | ||
| "text": [ | ||
| "Delete the stack: <code>sam delete</code>." | ||
| ] | ||
| }, | ||
| "authors": [ | ||
| { | ||
| "name": "Doug Perkes", | ||
| "image": "", | ||
| "bio": "Senior Solutions Architect at AWS, focused on .NET and serverless.", | ||
| "linkedin": "dougperkes", | ||
| "twitter": "" | ||
| } | ||
| ] | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| namespace TripPlannerStreaming; | ||
|
|
||
| /// <summary> | ||
| /// Parsed trip planning request parameters. | ||
| /// </summary> | ||
| public record TripRequest(string Destination, int Days, string Interests) | ||
| { | ||
| public const string DefaultDestination = "Tokyo, Japan"; | ||
| public const int DefaultDays = 3; | ||
| public const string DefaultInterests = "culture, food, and nature"; | ||
| public const int MaxDays = 14; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// A single day's plan extracted from the agent's tool call. | ||
| /// </summary> | ||
| public record DayPlan(int DayNumber, string Title, string Morning, string Afternoon, string Evening, string Tips); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.