# Introduction

{% embed url="<https://www.youtube.com/watch?v=3EgCNYPuEvU>" %}


# Introduction to Python

{% embed url="<https://www.youtube.com/watch?v=9Ze7g4xNACA>" %}

Getting Started


# Setup Your IDE

Setting up your Integrated Development Environment

**Installing VSCode:**

1. Install an IDE
   1. [VSCODE](https://code.visualstudio.com/download)
      1. [Python Plugin](https://marketplace.visualstudio.com/items?itemName=ms-python.python)
      2. [Getting Started with Python in VS Code](https://code.visualstudio.com/docs/python/python-tutorial)<br>

**If you're using Windows, install Windows Subsystem for Linux (WSL):**

1. [Install WSL2](https://learn.microsoft.com/en-us/windows/wsl/install) (not the original WSL)
2. [Install a Linux Distribution from Windows Market Place](https://apps.microsoft.com/detail/9pn20msr04dw?hl=en-US\&gl=US)
3. [Install the Remote Development plugin for VSCode](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.vscode-remote-extensionpack)
4. [Ensure you can connect to your WSL environment from VSCode](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-wsl)


# Python Install & Virtual Environment

## Installing Python 3.12

1. Download Python 3.12 from [python.org/downloads](https://www.python.org/downloads/). Choose the installer for your operating system (Windows, macOS, or Linux).
2. Run the installer. On Windows, check **Add python.exe to PATH** before clicking Install.
3. Verify the installation by opening a terminal and running:

   ```bash
   python3 --version
   ```

   You should see `Python 3.12.x`. On Windows, use `python --version` instead.

   If Windows opens the Microsoft Store when you type `python`, the PATH was not set during installation. Re-run the installer, select **Modify**, and ensure the PATH checkbox is enabled.

## Writing Your First Program

1. Create a new directory for your work:

   ```bash
   mkdir helloworld && cd helloworld
   ```
2. Create a file named `app.py` with the following content:

   ```python
   print("Hello, World!")
   ```
3. Run it:

   ```bash
   python3 app.py
   ```

   On Windows use `python app.py`. You should see `Hello, World!` printed to the terminal.

## Virtual Environments

A virtual environment isolates the Python packages for one project from those of another. Without it, installing a package for one project can break a different project that depends on a different version of the same package.

You will create a virtual environment in every project directory throughout this course.

### Create and Activate

Use the built-in `venv` module to create an environment named `venv` at the root of your project:

```bash
python3 -m venv venv
```

Activate it before installing any packages:

* **macOS / Linux:**

  ```bash
  source venv/bin/activate
  ```
* **Windows:**

  ```bash
  venv\Scripts\activate
  ```

When activated, your terminal prompt shows the environment name (e.g., `(venv)`). All `pip install` commands now install into this isolated environment.

### Install Packages

With the environment active, install packages normally:

```bash
pip install requests
```

Packages are stored inside the `venv/` directory and do not affect your system Python or other projects.

### Deactivate

When you are finished working:

```bash
deactivate
```

This returns you to the system Python. The installed packages remain inside `venv/` for next time.

### Best Practices

* Name the environment `venv/` and add it to `.gitignore` so it is never committed to version control.
* Re-create the environment on a new machine by running `python3 -m venv venv` and then `pip install -r requirements.txt` (once you start tracking dependencies in a requirements file).
* Always activate the environment before running project code or installing packages.


# Python Basics

###

### Defining a Function

To define a function, you use the `def` keyword, followed by the function name and parentheses `()`. Inside the parentheses, you can specify parameters (inputs). The function body is indented and contains the code that runs when the function is called.

```python
def greet(name):
    print(f"Hello, {name}!")
```

In this example, `greet` is a function that takes one parameter, `name`, and prints a greeting.

### Calling a Function

To call a function, you use its name followed by parentheses. If the function requires parameters, you pass them inside the parentheses.

```python
greet("Alice")  # Output: Hello, Alice!
```

### Return Values

Functions can also return values using the `return` statement. This allows you to get a result from the function and use it elsewhere in your code.

```python
def add(a, b):
    return a + b

result = add(3, 5)
print(result)  # Output: 8
```

### Parameters and Arguments

* **Parameters** are the variables listed inside the parentheses in the function definition.
* **Arguments** are the values you pass to the function when you call it.

```python
def multiply(x, y):
    return x * y

print(multiply(4, 5))  # Output: 20
```

### Default Parameters

You can provide default values for parameters. If an argument is not provided, the default value is used.

```python
def greet(name="World"):
    print(f"Hello, {name}!")

greet()        # Output: Hello, World!
greet("Alice") # Output: Hello, Alice!
```

### Keyword Arguments

You can also call functions using keyword arguments, where you specify the parameter name along with its value.

```python
def describe_pet(pet_name, animal_type="dog"):
    print(f"I have a {animal_type} named {pet_name}.")

describe_pet(pet_name="Buddy")               # Output: I have a dog named Buddy.
describe_pet(pet_name="Whiskers", animal_type="cat")  # Output: I have a cat named Whiskers.
```

### Arbitrary Arguments

If you don't know how many arguments will be passed to your function, you can use `*args` for positional arguments and `**kwargs` for keyword arguments.

```python
def make_pizza(*toppings):
    print("Making a pizza with the following toppings:")
    for topping in toppings:
        print(f"- {topping}")

make_pizza("pepperoni", "mushrooms", "green peppers")
```

```python
def build_profile(first, last, **user_info):
    profile = {}
    profile['first_name'] = first
    profile['last_name'] = last
    for key, value in user_info.items():
        profile[key] = value
    return profile

user_profile = build_profile('albert', 'einstein', location='princeton', field='physics')
print(user_profile)
```

### Object Oriented Design

Object-Oriented Programming (OOP) in Python is a programming paradigm that uses "objects" to model real-world entities. These objects are instances of "classes," which can be thought of as blueprints for creating objects. OOP helps in organizing code in a way that is modular, reusable, and easier to maintain.

#### Key Concepts of OOP

1. **Classes and Objects**

   * **Class**: A blueprint for creating objects. It defines a set of attributes and methods that the objects created from the class will have.
   * **Object**: An instance of a class. It represents a specific entity with attributes and behaviors defined by the class.

   ```python
   class Dog:
       def __init__(self, name, age):
           self.name = name
           self.age = age

       def bark(self):
           return "Woof!"

   my_dog = Dog("Buddy", 3)
   print(my_dog.name)  # Output: Buddy
   print(my_dog.bark())  # Output: Woof!
   ```
2. **Encapsulation**

   * Encapsulation is the bundling of data (attributes) and methods (functions) that operate on the data into a single unit, or class. It restricts direct access to some of the object's components, which can prevent the accidental modification of data.

   ```python
   class Car:
       def __init__(self, make, model):
           self.__make = make  # Private attribute
           self.__model = model  # Private attribute

       def get_make(self):
           return self.__make

       def set_make(self, make):
           self.__make = make

   my_car = Car("Toyota", "Corolla")
   print(my_car.get_make())  # Output: Toyota
   ```
3. **Inheritance**

   * Inheritance allows a class to inherit attributes and methods from another class. This helps in reusing code and creating a hierarchical relationship between classes.

   ```python
   class Animal:
       def speak(self):
           pass

   class Dog(Animal):
       def speak(self):
           return "Woof!"

   class Cat(Animal):
       def speak(self):
           return "Meow!"

   my_dog = Dog()
   my_cat = Cat()
   print(my_dog.speak())  # Output: Woof!
   print(my_cat.speak())  # Output: Meow!
   ```
4. **Polymorphism**

   * Polymorphism allows methods to do different things based on the object it is acting upon. It means "many forms" and allows the same method to be used on different objects.

   ```python
   class Bird:
       def fly(self):
           return "Flying high!"

   class Penguin(Bird):
       def fly(self):
           return "I can't fly!"

   my_bird = Bird()
   my_penguin = Penguin()
   print(my_bird.fly())  # Output: Flying high!
   print(my_penguin.fly())  # Output: I can't fly!
   ```
5. **Abstraction**

   * Abstraction means hiding the complex implementation details and showing only the essential features of the object. It helps in reducing programming complexity and effort.

   ```python
   from abc import ABC, abstractmethod

   class Shape(ABC):
       @abstractmethod
       def area(self):
           pass

   class Rectangle(Shape):
       def __init__(self, width, height):
           self.width = width
           self.height = height

       def area(self):
           return self.width * self.height

   my_rectangle = Rectangle(3, 4)
   print(my_rectangle.area())  # Output: 12
   ```

#### Benefits of OOP

* **Modularity**: Code is organized into classes and objects, making it easier to manage and understand.
* **Reusability**: Classes can be reused across different programs.
* **Scalability**: OOP makes it easier to manage and scale large codebases.
* **Maintainability**: Encapsulation and abstraction make it easier to maintain and update code.

OOP is a powerful way to structure your programs, especially as they grow in complexity. It helps you think about your code in terms of real-world entities and their interactions, making it more intuitive and easier to manage.

### Working with File

#### Opening and Closing Files

To work with files, you first need to open them. Python provides the `open()` function for this purpose. Always remember to close the file after you’re done to free up system resources.

Python

```python
# Open a file for reading
file = open("example.txt", "r")

# Do something with the file
content = file.read()
print(content)

# Close the file
file.close()
```

#### Using the `with` Statement

A better way to handle files is by using the `with` statement. It ensures that the file is properly closed after its suite finishes, even if an exception is raised.

Python

```python
with open("example.txt", "r") as file:
    content = file.read()
    print(content)
# No need to explicitly close the file
```

#### Reading Files

You can read files in different ways:

* **Read the entire file**:

Python

```python
with open("example.txt", "r") as file:
    content = file.read()
    print(content)
```

* **Read line by line**:

Python

```python
with open("example.txt", "r") as file:
    for line in file:
        print(line.strip())
```

* **Read into a list**:

Python

```python
with open("example.txt", "r") as file:
    lines = file.readlines()
    print(lines)
```

#### Writing to Files

You can write to files using the `write()` method. If the file doesn’t exist, it will be created.

Python

```python
with open("example.txt", "w") as file:
    file.write("Hello, World!\n")
    file.write("This is a new line.")
```

#### Appending to Files

To append to a file (add new content without deleting the existing content), use the `a` mode.

Python

```python
with open("example.txt", "a") as file:
    file.write("\nThis line is appended.")
```

#### Working with Binary Files

For binary files (like images or executable files), use the `b` mode.

Python

```python
with open("example.jpg", "rb") as file:
    content = file.read()
    print(content)

with open("copy.jpg", "wb") as file:
    file.write(content)
```

#### Handling File Exceptions

It’s important to handle exceptions that may occur while working with files, such as `FileNotFoundError`.

Python

```python
try:
    with open("nonexistent.txt", "r") as file:
        content = file.read()
except FileNotFoundError:
    print("The file does not exist.")
```

#### Using the `os` and `pathlib` Modules

For more advanced file operations, you can use the `os` and `pathlib` modules.

* **Listing files in a directory**:

Python

```python
import os

print(os.listdir("."))
```

* **Creating directories**:

Python

```python
os.mkdir("new_directory")
```

* **Using `pathlib` for path manipulations**:

Python

```python
from pathlib import Path

path = Path("example.txt")
print(path.exists())
print(path.is_file())
print(path.parent)
```

Working with files is a fundamental skill in Python programming, and these examples should help you get started.


# Python Modules

A module in Python is simply a Python file that contains definitions and statements. It can define functions, classes, and variables that you can reference in other Python files, or scripts, using the `import` statement.

Here's an example of how you can create your own Python module:

1. **Create a Python file** - Let's create a Python file named `mymodule.py`.

```python
# mymodule.py

def greet(name):
    return f"Hello, {name}!"

def add_numbers(a, b):
    return a + b
```

In this file, we've defined two functions: `greet` and `add_numbers`.

2. **Use the module** - Now, you can use these functions in another Python file by importing the module.

```python
# main.py

import mymodule

print(mymodule.greet("Alice"))  # prints: Hello, Alice!
print(mymodule.add_numbers(1, 2))  # prints: 3
```

In `main.py`, we import `mymodule` and then call the functions defined in `mymodule.py`.

Remember, the name of the module is the same as the name of the Python file, but without the `.py` extension. Also, the Python file that you're importing the module into needs to be in the same directory as the module, or the module needs to be in a directory that's part of the Python path.

### Example Module: `datetime`

The `datetime` module supplies classes for manipulating dates and times.

```python
import datetime

# Get the current date and time
now = datetime.datetime.now()
print(f"Current date and time: {now}")

# Get just the current time
current_time = now.time()
print(f"Current time: {current_time}")
```

### Example Module: `os`

The `os` module provides a way of using operating system dependent functionality.

```python
import os

# Get the current working directory
cwd = os.getcwd()
print(f"Current working directory: {cwd}")

# List all files and directories in the current directory
print("Files and directories:", os.listdir(cwd))
```

### Example Module: `random`

The `random` module can generate random numbers.

```python
import random

# Generate a random number between 1 and 10
random_number = random.randint(1, 10)
print(f"Random number between 1 and 10: {random_number}")

# Randomly select an item from a list
my_list = ['apple', 'banana', 'cherry']
random_item = random.choice(my_list)
print(f"Random item from list: {random_item}")
```


# Python Package

In Python, a package is a way of organizing related modules into a directory hierarchy. Essentially, it's a directory that contains multiple module files and a special `__init__.py` file to let Python know that the directory is a package.

Here's a step-by-step guide on how to organize your modules into a package:

1. **Create a directory** - This directory will be your package. Let's call it `mypackage`.

```
mypackage/
```

2. **Add a `__init__.py` file** - This file is required for Python to recognize the directory as a package. It can be empty, but it can also execute initialization code for the package.

```
mypackage/
    __init__.py
```

3. **Add module files** - Now you can add your Python module files to the package.

```
mypackage/
    __init__.py
    module1.py
    module2.py
```

In this example, `module1.py` and `module2.py` are Python files with their own functions, classes, and variables.

4. **Use the package** - You can now import the modules in the package from another Python script using the package name and the module name.

```python
# main.py

from mypackage import module1, module2

module1.my_function()
module2.my_function()
```

In this example, `my_function` is a function defined in `module1.py` and `module2.py`.

5. **Subpackages** - Packages can also contain subpackages. A subpackage is just a package that is a subdirectory of another package.

```
mypackage/
    __init__.py
    module1.py
    module2.py
    subpackage/
        __init__.py
        module3.py
```

You can import modules from subpackages like this:

```python
from mypackage.subpackage import module3

module3.my_function()
```

Organizing your code into packages and modules keeps your codebase clean, manageable, and easier to reuse across different projects.


# Additional Resources

### Roadmap.sh for Python

[Roadmap.sh for Python](https://roadmap.sh/python) has a recommended path with resources for learning the language. The site can save your progress as you go if you register. We recommend a few other learning paths later in this course as well.


# Project: SEC CIK Lookup Module

### What is the SEC

The U.S. Securities and Exchange Commission (SEC) plays a crucial role in the United States government. Its primary mission is threefold:

1. **Protect Investors**: The SEC ensures that investors receive accurate and complete information about securities being offered for public sale. This helps investors make informed decisions.
2. **Maintain Fair, Orderly, and Efficient Markets**: The SEC oversees the securities markets to ensure they operate smoothly and fairly, preventing fraud and manipulation.
3. [**Facilitate Capital Formation**: By regulating the securities industry, the SEC helps businesses raise capital, which is essential for economic growth and job creation](https://www.investor.gov/introduction-investing/investing-basics/role-sec).

The SEC was established in response to the stock market crash of 1929 and the subsequent Great Depression. [It enforces federal securities laws and regulates securities exchanges, brokers, and dealers](https://www.investor.gov/introduction-investing/investing-basics/role-sec).

#### Quarterly and Annual Company Reports

#### **Form 10-K**

* **Annual Report**: The 10-K is a comprehensive annual report that provides a detailed overview of a company’s financial performance over the past fiscal year.
* [**Contents**: It includes information such as the company’s history, organizational structure, financial statements, earnings per share, subsidiaries, executive compensation, and any other relevant data](https://www.investopedia.com/terms/1/10-k.asp).
* [**Purpose**: This form is essential for investors as it offers a thorough understanding of the company’s operations, financial condition, and risks](https://www.investopedia.com/terms/1/10-k.asp).

#### **Form 10-Q**

* **Quarterly Report**: The 10-Q is a quarterly report that provides a detailed overview of a company’s financial performance over the past quarter.
* **Contents**: It includes financial statements, management’s discussion and analysis (MD\&A), disclosures about market risk, internal controls, and any other relevant data for the quarter.
* **Purpose**: This form is essential for investors as it offers timely updates on the company’s financial condition, operations, and any significant changes or events that occurred during the quarter.

### What is a CIK?

The **Central Index Key (CIK)** is a unique identifier assigned by the U.S. Securities and Exchange Commission (SEC) to entities and individuals who file disclosures with the SEC. [This includes companies, filing agents, and even foreign governments](https://en.wikipedia.org/wiki/Central_Index_Key). [The CIK is used within the SEC’s EDGAR (Electronic Data Gathering, Analysis, and Retrieval) system to track and manage filings](https://www.sec.gov/search-filings/cik-lookup).

[If you need to look up a specific CIK, you can use the SEC’s ](https://en.wikipedia.org/wiki/Central_Index_Key)[CIK Lookup tool](https://www.sec.gov/search-filings/cik-lookup).<br>


# Building a CIK Lookup Module

For our application we need a way to look up a company CIK without using the web form.

The SEC has a page called [accessing EDGAR Data](https://www.sec.gov/search-filings/edgar-search-assistance/accessing-edgar-data). On that page CIK data is in 3 formats.

* [company\_tickers.json](https://www.sec.gov/files/company_tickers.json): data file for ticker, CIK, EDGAR conformed company name associations.
* [company\_tickers\_exchange.json](https://www.sec.gov/files/company_tickers_exchange.json): data file for EDGAR conformed company name, CIK, ticker, exchange associations
* [company\_tickers\_mf.json](https://www.sec.gov/data/company_tickers_mf.json): fund CIK, series, class, ticker

## NOTE: Follow the SEC EDGAR Fair Access Policy

Fair access is described on the [accessing EDGAR Data](https://www.sec.gov/search-filings/edgar-search-assistance/accessing-edgar-data) page.

> ### Fair access
>
> * **Current max request rate: 10 requests/second.**
>
> To ensure everyone has equitable access to SEC EDGAR content, please use efficient scripting. Download only what you need and please moderate requests to minimize server load.
>
> SEC reserves the right to limit request rates to preserve fair access for all users. See our [Internet Security Policy](https://www.sec.gov/privacy.htm#security) for our current rate request limit.
>
> The SEC does not allow botnets or automated tools to crawl the site. Any request that has been identified as part of a botnet or an automated tool outside of the acceptable policy will be managed to ensure fair access for all users.
>
> Please declare your user agent in request headers:
>
> Sample Declared Bot Request Headers:

| User-Agent:      | Sample Company Name AdminContact@\<sample company domain>.com |
| ---------------- | ------------------------------------------------------------- |
| Accept-Encoding: | gzip, deflate                                                 |
| Host:            | [www.sec.gov](http://www.sec.gov)                             |

### Our Python Module

Our Python Module will be a class that initializes and stores the EDGAR company data into a hash map (dictionary). Once we store the data we can use it to go from company name or ticker to its CIK number

* Write a class that initializes two dictionaries.
  * One where the company name is the key
  * One where the stock ticker is the key.
* When the method initializes it should retrieve a fresh copy of the data from the URLs provided by SEC EDGAR
  * This will allow your function to always have the latest and correct data.
* The class should have two methods. (Method names should follow [PEP8 Style Guide](https://peps.python.org/pep-0008/#method-names-and-instance-variables))
  * Ex: `name_to_cik`
  * Ex: `ticker_to_cik`
  * The return values should be a tuple that ***at least*** includes CIK, Name, Ticker but could include more information.

{% embed url="<https://www.youtube.com/watch?v=JBexmhrqWd0>" %}


# Introduction to Git and GitHub

**Git** is a distributed version control system designed to track changes in source code during software development. It allows multiple developers to work on a project simultaneously without interfering with each other's work. Git's key features include:

* **Local Operations**: Most operations in Git are performed locally.
* **Branching and Merging**: Git supports powerful branching and merging capabilities, allowing developers to experiment with new features safely.

**GitHub** is a web-based platform that uses Git for version control and provides additional features to facilitate collaboration among developers. It offers:

* **Repository Hosting**: GitHub hosts Git repositories, making it easy to share code with others.
* **Collaboration Tools**: GitHub includes tools for issue tracking, code review, and project management.
* **Community and Networking**: Developers can connect, collaborate, and contribute to open-source projects on GitHub.

Together, Git and GitHub streamline the development process, making it easier for teams to manage and collaborate on code.


# Getting Started with Git

#### 1. Install Git

First, you need to install Git on your computer. You can download it from the [official Git website](https://git-scm.com/).

#### 2. Configure Git

After installation, configure your Git settings:

```bash
git config --global user.name "Your Name"
git config --global user.email "your.email@example.com"
```

#### 3. Initialize a Repository

Navigate to your project directory and initialize a Git repository:

```bash
cd your-project-directory
git init
```

#### 4. Add Files to the Repository

Add files to your repository to start tracking them:

```bash
git add .
```

#### 5. Commit Changes

Commit your changes with a message describing what you've done:

```bash
git commit -m "Initial commit"
```

#### 6. Connect to a Remote Repository

If you want to push your code to a remote repository (e.g., GitHub), you need to add the remote URL:

```bash
git remote add origin https://github.com/yourusername/your-repo.git
```

#### 7. Push Changes to the Remote Repository

Push your changes to the remote repository:

```bash
git push -u origin master
```

#### 8. Pull Changes from the Remote Repository

To update your local repository with changes from the remote repository:

```bash
git pull origin master
```

#### 9. Branching and Merging

Create a new branch to work on a feature:

```bash
git checkout -b new-feature
```

After making changes, commit them and switch back to the main branch:

```bash
git commit -m "Add new feature"
git checkout master
```

Merge the new feature branch into the main branch:

```bash
git merge new-feature
```

#### 10. Viewing the Commit History

To see the commit history of your project:

```bash
git log
```

This guide covers the basics to get you started with Git. If you need more detailed information, you can refer to the [Git documentation](https://git-scm.com/doc) or the [GitHub Docs](https://docs.github.com/en/get-started/getting-started-with-git).


# Project: GitHub Setup

This project establishes the repository you will use for every subsequent module in the course. By the end you will have a GitHub repo with a README, a `.gitignore`, branch protection, and a feature-branch workflow you will repeat in every project that follows.

## Create Your Repository

1. Sign in to [github.com](https://github.com) (create a free account if you do not have one).
2. Click **New repository** (the `+` menu in the top-right corner).
3. Name the repository something descriptive — `sec-llm-service` works well.
4. Set visibility to **Private** (you will share it with instructors via collaborator invite).
5. Check **Add a README file** so the repo initializes with a default branch.
6. Click **Create repository**.

## Add a `.gitignore`

Create a `.gitignore` at the repository root. At minimum it should exclude:

```
# Python virtual environment
venv/

# Byte-compiled files
__pycache__/

# Environment variables / secrets
.env

# Amplify build output (added later in the course)
amplify_outputs.json
```

Commit the file to your default branch before continuing.

## Enable Branch Protection

Branch protection prevents direct pushes to your main branch and requires pull requests for every change — the same workflow used in professional teams.

1. In your repository, go to **Settings > Branches**.
2. Under **Branch protection rules**, click **Add rule**.
3. Set **Branch name pattern** to `main`.
4. Enable **Require a pull request before merging**.
5. (Recommended) Enable **Require status checks to pass before merging** once you add CI later in the course.
6. Click **Create**.

With this rule active you cannot push directly to `main`; all changes flow through pull requests.

## Feature-Branch and Pull Request Workflow

Every project in this course follows a feature-branch workflow:

1. **Create a branch** from `main` for the work you are about to do:

   ```bash
   git checkout main
   git pull origin main
   git checkout -b feature/sec-cik-module
   ```
2. **Make commits** as you work. Keep commits small and descriptive:

   ```bash
   git add src/cik_lookup.py
   git commit -m "feat: add ticker_to_cik lookup function"
   ```
3. **Push the branch** to GitHub:

   ```bash
   git push -u origin feature/sec-cik-module
   ```
4. **Open a pull request** on GitHub comparing your feature branch into `main`. Write a short description of what changed and why.
5. **Review and merge.** Once you are satisfied the code is correct, merge the pull request on GitHub. Delete the feature branch after merging to keep the branch list clean.

Repeat this cycle for every project module. The habit of branching, committing with clear messages, and merging through a PR is the single most transferable workflow skill in this course.

## Commit Message Conventions

Use short, imperative-mood subject lines:

| Pattern            | Example                               |
| ------------------ | ------------------------------------- |
| `feat: <what>`     | `feat: add quarterly_filing helper`   |
| `fix: <what>`      | `fix: handle missing CIK gracefully`  |
| `docs: <what>`     | `docs: add API usage notes to README` |
| `refactor: <what>` | `refactor: extract SEC URL builder`   |

Keep the subject under 72 characters. If more context is needed, add a blank line followed by a body paragraph.


# Introduction to APIs

1. **What is an API?**

* An **API** stands for **Application Programming Interface**. Think of it as a bridge or a messenger that allows different software components to **communicate** and **exchange data**.
* Imagine you’re at a restaurant, and you want to order food. You don’t directly talk to the chef in the kitchen; instead, you interact with the waiter (the API) who takes your order and communicates it to the kitchen (another software component).
* Similarly, an API acts as a stable intermediary between two systems. It defines how different software parts can interact with each other.

2. **How Do APIs Work?**

* Computers follow a set of rules (protocols) to communicate. Just like humans need a common language to understand each other, computers need protocols.
* On the web, we commonly use the **HTTP protocol** (Hyper Text Transfer Protocol). APIs available on the web use HTTP for communication.
* Here’s how the **request-response cycle** works:
  * **Client**: The requesting computer/device (e.g., your mobile phone, laptop, or desktop) sends a request to the server.
  * **Server**: The bigger computer (server) processes the request and sends back a response.
  * The client provides specific information with the request, including:
    * **URL**: The web address where the request is made.
    * **Method**: Whether you want to retrieve existing data or save new data.

3. **Why Are APIs Important?**

* APIs allow developers to:
* Access data from external sources (e.g., weather data, social media posts, or images).
* Integrate services (e.g., payment gateways, maps, or messaging) into their applications.
* Build powerful, resilient, and secure applications by connecting different software components.


# Types of APIs

APIs (Application Programming Interfaces) are different interfaces and protocols used in programming.

1. **ABI (Application Binary Interface)**: An ABI is a low-level binary interface between two or more pieces of software on a particular architecture. It defines how an application interacts with itself, how an application interacts with the kernel, and how an application interacts with libraries. It's like an API but expressed in compiled code instead of source code.
2. **SOAP (Simple Object Access Protocol)**: SOAP is a message specification for exchanging information between systems and applications. It uses XML for data encoding and is developed in a more structured and formalized way. SOAP APIs provide a reliable and trusted way to send and receive messages between systems.
3. **REST (Representational State Transfer)**: REST is an architectural style for building web-based APIs. It's not a protocol or a standard, but a set of constraints and principles that promote simplicity, scalability, and statelessness in the design. REST APIs can transfer data in a variety of formats, including XML, plain text, HTML, and JSON.
4. **Binary Formats**: Binary data formats are used when you need to deal with raw binary data directly. They are often used for performance reasons, as they allow you to work with data more compactly and with less overhead. For example, you might use a binary data format when sending and receiving data to a REST API.
5. **GraphQL**: GraphQL is a query language for APIs and a runtime for executing those queries with your existing data. It provides a more efficient, powerful, and flexible alternative to REST.
6. **gRPC**: gRPC is a high-performance, open-source universal RPC framework. It uses Protocol Buffers as the interface definition language.
7. **JSON-RPC and XML-RPC**: Both are remote procedure call (RPC) protocols encoded in JSON and XML respectively. They are simple and lightweight subprotocols of the HTTP protocol.
8. **OData (Open Data Protocol)**: OData is a protocol that allows the creation and consumption of queryable and interoperable RESTful APIs. It's useful when you want to expose a data source via an API.
9. **WebSockets**: WebSockets provide a persistent connection between a client and server that both parties can use to start sending data at any time. It's useful for real-time applications.
10. **Webhooks**: Webhooks are a way for an application to provide other applications with real-time information. A webhook delivers data to other applications as it happens, meaning you get data immediately.


# REST APIs

This course focuses on REST APIs.

A REST API (Representational State Transfer Application Programming Interface) is a set of rules and conventions for building and interacting with web services.

Here are the key principles of REST APIs:

1. **Uniform Interface**: REST APIs have a uniform interface, which decouples the client and server and allows each to evolve independently.
2. **Stateless**: Each request from a client to a server must contain all the information needed to understand and process the request. The server does not store anything about the latest HTTP request the client made.
3. **Client-Server Architecture**: The client is responsible for the user interface and user experience, and the server is responsible for processing requests and managing resources.
4. **Cacheable**: Responses from the server can be cached by the client. This improves performance by reducing the load on the server and network.
5. **Layered System**: The architecture allows for layers of servers (e.g., load balancers, cache servers) that can be added to improve scalability and performance.

REST APIs use standard HTTP methods like `GET`, `POST`, `PUT`, and `DELETE` to perform operations on data. The data sent and received is typically in JSON or XML format.

For example, to retrieve a user's information from a database, you might send a `GET` request to `http://api.example.com/users/123`. The server responds with the data for the user with that ID.

REST APIs are widely adopted for their simplicity, scalability, and performance. They are used in web apps, mobile apps, and microservices architectures.


# What is cURL?

`cURL` (client URL) is a command-line tool for transferring data to and from a server. It supports HTTP, HTTPS, and many other protocols, and runs on almost every platform. This makes cURL ideal for testing API communication from any device with a command line and network connectivity.

The most basic command is `curl [URL]`. The `curl` command followed by a URL retrieves the resource at that address — for a web page, it returns the HTML source.

To use cURL with REST APIs, you specify different HTTP methods along with headers and data. Here are common examples:

1. **GET Request** — retrieve data from the server:

   ```
   curl http://api.example.com/resource
   ```
2. **POST Request** — send data to the server (JSON in this case):

   ```
   curl -X POST -H "Content-Type: application/json" -d '{"key":"value"}' http://api.example.com/resource
   ```
3. **PUT Request** — update existing data on the server:

   ```
   curl -X PUT -H "Content-Type: application/json" -d '{"key":"value"}' http://api.example.com/resource/id
   ```
4. **DELETE Request** — delete existing data from the server:

   ```
   curl -X DELETE http://api.example.com/resource/id
   ```

In these examples, `-X` specifies the HTTP method, `-H` sets request headers, and `-d` sends data in the request body.

The actual commands vary based on the API's specifications. Always refer to the API documentation for accurate request formats.

### Key cURL Resources

* The Tutorial from the official cURL website
  * <https://curl.se/docs/tutorial.html>
* The open source cURL book from the Everything cURL website
  * <https://everything.curl.dev/index.html>


# Data Formats

In order to make use of data on the internet we first need to parse our source. Our data source can be made up of structured and unstructured data.

\
**Structured Data**

1. **HTML (Hypertext Markup Language)**:
   * **Purpose**: HTML is primarily used for creating web pages. It defines the structure and layout of content on a webpage.
   * **Description**: HTML uses tags to mark up elements like headings, paragraphs, images, links, and forms. It’s readable by both humans and browsers.
   * **Example**:**HTML**

     ```
     <!DOCTYPE html>
     <html>
       <head>
         <title>My Web Page</title>
       </head>
       <body>
         <h1>Welcome to My Page</h1>
         <p>This is some text.</p>
         <img src="my-image.jpg" alt="My Image">
       </body>
     </html>
     ```
   * **Parsing**: Browsers automatically parse HTML to render web pages.
2. **XML (Extensible Markup Language)**:
   * **Purpose**: XML is used for structuring and organizing data. It’s extensible because you can define your own tags.
   * **Description**: XML uses tags to create hierarchically organized documents. It’s readable by humans and easy to manipulate programmatically.
   * **Example**:**XML**

     ```
     <?xml version="1.0" encoding="UTF-8"?>
     <friends>
       <friend>
         <name>John Ferreira</name>
         <age>26</age>
         <city>Porto</city>
         <profession>Full Stack Web Developer</profession>
         <hobby>Fitness</hobby>
       </friend>
       <!-- More friend records... -->
     </friends>
     ```
   * **Parsing**: You can use libraries (e.g., **`xml.etree.ElementTree`** in Python) to parse XML data.
3. **JSON (JavaScript Object Notation)**:
   * **Purpose**: JSON is widely used for data interchange between systems. It’s lightweight and easy to read.
   * **Description**: JSON represents data as key-value pairs. It’s commonly used in APIs, configuration files, and databases.
   * **Example**:**JSON**

     ```
     {
       "friends": [
         {
           "name": "John Ferreira",
           "age": 26,
           "city": "Porto",
           "profession": "Full Stack Web Developer",
           "hobby": "Fitness"
         },
         { "name": "..." }
       ]
     }
     ```
   * **Parsing**: Most programming languages have built-in support for parsing JSON (e.g., **`json`** module in Python).
4. **CSV (Comma-separated Values)**:
   * **Purpose**: CSV is used for representing tabular data (rows and columns). It’s commonly used in spreadsheets and databases.
   * **Description**: CSV is plain text with records separated by commas. The first line often serves as the header.
   * **Example**:

     ```
     name,age,city,profession,hobby
     John Ferreira,26,Porto,Full Stack Web Developer,Fitness
     Leonardo Marinho,18,London,Electric Engineer,Build lego
     Caroline Azevedo,34,Salvador,Entrepreneur,Sing
     ```
   * **Parsing**: You can read CSV files line by line and split values using commas.

<br>

**Unstructured Data**

**Unstructured data** refers to information that **doesn’t follow a specific format or structure**, making it challenging to process and analyze using conventional tools. [Unlike structured data, which neatly fits into tables (like those found in Microsoft Excel), unstructured data can’t be quickly analyzed and searched without further processing](https://www.coursera.org/articles/what-is-unstructured-data).

<br>


# Project: SEC EDGAR API Library

For this project we are going to expand our CIK Lookup Module to add additional methods to make it a client we can use to find and retrieve the 10K and 10Q documents.\
\
To get more context on this explore the SEC Website using the EDGAR search tool.

* [SEC EDGAR Full Text Search](https://www.sec.gov/edgar/search/)
  * [Full Text Search FAQ](https://www.sec.gov/edgar/search/efts-faq.html)
* [SEC EDGAR CIK Search](https://www.sec.gov/search-filings/cik-lookup)
* [How do I use EDGAR?](https://www.sec.gov/search-filings/edgar-search-assistance/how-do-i-use-edgar)
* [EDGAR Application Programming Interfaces (APIs)](https://www.sec.gov/search-filings/edgar-application-programming-interfaces)

1. Review the links above
   1. Read through the API and test the API by making calls with cURL and inspecting the results.
      1. If you need a tool to help parse the JSON while testing on the command line check out [JQ](https://jqlang.github.io/jq/)
2. Find a Company 10Q
   1. Search For a Company
   2. Find the latest 10Q they submitted
   3. Open the 10Q
   4. Skim through it to better understand the type of information included

We are going to fully automate the `Find a Company 10Q` steps.


# Find Company Submissions

Experiment with the API using curl until you are able to find 10K and 10Q submissions. Not all companies file their reports on the same days. In order to know which 10Q and 10K documents are available to us we need to get their submissions.\
\
Lets check out the submissions API

`curl -A "MLT GS gspivey@mlt.org" https://data.sec.gov/submissions/CIK320193.json`

We attempt to cURL using Apple's CIK however we do not get a desired response.

```xml
<?xml version="1.0" encoding="UTF-8"?>
<Error><Code>NoSuchKey</Code><Message>The specified key does not exist.</Message><Key>submissions/CIK320193.json</Key><RequestId>S037Y7EEH567HC5K</RequestId><HostId>tBhGAzTTCykT5nxZbXE7jR9imKH2CaMQ99DE56CsfiQpq67UyLZquL8itb1qp0+psOO7TLWTf3Y=</HostId></Error>%
```

It turns out the API expects CIK numbers to be 10 digits. Take this in consideration for your code later as you likely return a shortened CIK.

\
`curl -A "MLT GS gspivey@mlt.org" https://data.sec.gov/submissions/CIK0000320193.json`

Now we get a response. Lots of content is stripped out but the structure was left in place. Take note that recent is made up of multiple arrays. Based on the information returned you should be able to Identify the `Accession Number` and `Filing Date` for all `10-Q` and `10-K` submissions.

<pre class="language-json"><code class="lang-json">{
   "cik":"320193",
   "entityType":"operating",
   "sic":"3571",
   "sicDescription":"Electronic Computers",
   "insiderTransactionForOwnerExists":0,
   "insiderTransactionForIssuerExists":1,
   "name":"Apple Inc.",
   "tickers":[
      "AAPL"
   ],
   "exchanges":[
      "Nasdaq"
   ],
   "ein":"942404110",
   "description":"",
   "website":"",
   "investorWebsite":"",
   "category":"Large accelerated filer",
   "fiscalYearEnd":"0928",
   "stateOfIncorporation":"CA",
   "stateOfIncorporationDescription":"CA",
   "addresses":{
      "mailing":{
         "street1":"ONE APPLE PARK WAY",
         "street2":null,
         "city":"CUPERTINO",
         "stateOrCountry":"CA",
         "zipCode":"95014",
         "stateOrCountryDescription":"CA"
      },
      "business":{
         "street1":"ONE APPLE PARK WAY",
         "street2":null,
         "city":"CUPERTINO",
         "stateOrCountry":"CA",
         "zipCode":"95014",
         "stateOrCountryDescription":"CA"
      }
   },
   "phone":"(408) 996-1010",
   "flags":"",
   "formerNames":[
      {
         "name":"APPLE INC",
         "from":"2007-01-10T00:00:00.000Z",
         "to":"2019-08-05T00:00:00.000Z"
      },
      {
         "name":"APPLE COMPUTER INC",
         "from":"1994-01-26T00:00:00.000Z",
         "to":"2007-01-04T00:00:00.000Z"
      },
      {
<strong>         "name":"APPLE COMPUTER INC/ FA",
</strong>         "from":"1997-07-28T00:00:00.000Z",
         "to":"1997-07-28T00:00:00.000Z"
      }
   ],
   "filings":{
      "recent":{
         "accessionNumber":[],
         "filingDate":[],
         "acceptanceDateTime":[],
         "act":[],
         "form":[],
         "fileNumber":[],
         "filmNumber":[],
         "items":[],
         "size":[],
         "isXBRL":[],
         "isInlineXBRL":[],
         "primaryDocument":[],
         "primaryDocumentDescription":[]
      },
      "files":[
         {
            "name":"CIK0000320193-submissions-001.json",
            "filingCount":1069,
            "filingFrom":"1994-01-26",
            "filingTo":"2014-01-26"
         }
      ]
   }
}

</code></pre>

You can use JSON formatters and query tools to explore the response

* [JQ](https://jqlang.github.io/jq/)
* [JQ Playground](https://jqplay.org/)
* [JSON Formatter & Validator](https://jsonformatter.curiousconcept.com)


# Filter Submissions and Retrieve Doc

Under `filings -> recent`

There are 3 key arrays along the CIK number that is key data needed to find a document

* accessionNumber
* primaryDocument
  * This is the name of the document we are going to use
* primaryDocumentDescription
  * These will describe what the document is. In our case we care about the `10-K` and `10-Q` documents.
  * You could think about making your program configurable to handle any document type

Remember arrays are ordered. So you can use the index number to help you find the other values you care about.

The URL for the document will follow this format

`https://www.sec.gov/Archives/edgar/data/{CIK}/{accessionNumber}/{primaryDocument}`

Note: accession numbers in the SEC API response contain dashes (e.g., `0000320193-24-000069`), but the URL path uses the dash-stripped form (e.g., `000032019324000069`). Strip the dashes when constructing the URL.

#### Example

> **Capture-Dated Example** — results shown may differ from your own.

`curl -A "<your organization> <your name> <your email>" -v https://www.sec.gov/Archives/edgar/data/320193/000032019324000069/aapl-20240330.htm`

Replace the CIK, accession number, and document name with values from your own submission lookup. Use a recent filing from any company you choose.


# Expanding Your CIK Module

Now that you know how to find 10-K and 10-Q files for a company based on its CIK number lets write some methods that make this useful to us.

* `annual_filing(cik, year)`
* `quarterly_filing(cik, year, quarter`)

These are public methods but you likely want some helper private methods as well.\
\
Update your git repo with the latest Module.


# Introduction to AWS

Amazon Web Services (AWS) is a cloud platform from Amazon that provides services as building blocks. These building blocks can be used to create and deploy any type of application in the cloud. AWS offers a broad set of global products including compute, storage, databases, analytics, networking, developer tools, management tools, security, and enterprise applications. These services are on-demand, available in seconds, with pay-as-you-go pricing.

A key benefit of AWS is replacing upfront capital infrastructure expenses with low variable costs that scale with your business. Instead of procuring servers weeks or months in advance, you can spin up hundreds of servers in minutes and deliver results faster. AWS provides a highly reliable, scalable, low-cost infrastructure platform that powers hundreds of thousands of businesses in 190 countries. AWS services are designed to work together, enabling sophisticated and highly scalable applications.

AWS combines infrastructure as a service (IaaS), platform as a service (PaaS), and packaged software as a service (SaaS) offerings into a single platform.

### AWS Value Proposition

* **Security:** AWS prioritizes security and provides numerous services and features to help protect your data. AWS already has security and compliance certifications, which can be beneficial if you need to support standards like HIPAA or SOC II. This allows you to focus on the security aspects that matter for your application.
* **Flexibility:** AWS consists of 100+ services, supporting everything from image recognition to database migrations. This means there's a high likelihood that you can run whatever stack you're working in on AWS. You also have the flexibility to go at the pace of change that's right for you.
* **Elasticity:** With AWS, you no longer need to over-provision workloads to support occasional traffic spikes. AWS services can scale up or down based on demand, providing the ability to pay only for the compute power, storage, and other resources you use.
* **Cost Savings:** AWS can help reduce costs by eliminating the need for upfront capital infrastructure expenses. You only pay for the resources you use, which can lead to significant cost savings.
* **Productivity:** AWS can increase staff productivity by allowing developers to instantly provision resources and begin writing code for innovative new applications or services. This can lead to faster delivery of features and more efficient use of resources.
* **Operational Resilience:** AWS provides operational resilience that protects against hardware failures, natural disasters, and power outages. Using cloud services can increase operational resilience and avoid the high costs of IT disruption.
* **Business Agility:** AWS improves business agility by enabling quick resource provisioning. Developers can instantly provision resources and begin writing code, delivering features faster.
* **Sustainability:** AWS minimizes the environmental impact of business operations through sustainability initiatives.


# AWS Services

This section introduces the AWS services you will use throughout the course. For a full reference with open-source alternatives, see [AWS Services Reference](/reference/aws-services).

### Amazon S3

Amazon S3 (Simple Storage Service) stores and retrieves objects — files of any size, accessible via HTTP. You will use S3 to store SEC filing documents that your Lambda functions read during inference.

### AWS Lambda

AWS Lambda runs your code without provisioning servers. You upload a handler function, and Lambda executes it in response to events (HTTP requests, schedules, other AWS services). The SEC Lambda and 10Q Inference modules both deploy Lambda functions — first with SAM, then with CDK.

### Amazon API Gateway

API Gateway creates HTTP endpoints that route requests to your Lambda functions. The Partner Bot Web Page module uses API Gateway to expose the inference Lambda as a REST API the browser can call.

### Amazon Bedrock

Bedrock provides managed API access to large language models. You call Bedrock through the AWS SDK to run inference against the course's canonical model (see [Models](/reference/models) for details).

### Amazon EventBridge

EventBridge triggers actions on a schedule or in response to events. In this course, an EventBridge cron rule fires the daily SEC filing refresh Lambda.

### Amazon Cognito

Cognito manages user sign-up, sign-in, and access control. The Partner Bot Web Page module uses Cognito to authenticate users before they can call the inference API.

***

Last verified: 2026-06


# Key Reading

* [Step By Step Guide to Learning AWS](https://roadmap.sh/aws)
* [What is NoSQL?](https://aws.amazon.com/nosql/?trk=faq_card)
* [What is a Serverless Database?](https://aws.amazon.com/what-is/serverless-database/?trk=faq_card)<br>


# Project: SEC Lambda (SAM)

In this project you build two AWS Lambda functions deployed with SAM CLI. Together they form an event-driven backend that keeps SEC EDGAR data fresh and answers questions about company filings on demand.

## Architecture

The project contains two functions with different invocation patterns:

**Lambda 1 — EDGAR File Refresh (scheduled)** Downloads the SEC EDGAR company-tickers JSON file and uploads it to an S3 bucket. A SAM `Schedule` event triggers the function daily. The S3 bucket has versioning enabled so each upload creates a recoverable snapshot rather than overwriting the previous data.

**Lambda 2 — Document Retrieval (synchronous)** Accepts a JSON request conforming to the [Lambda Contract](/reference/contract), retrieves the specified 10-K or 10-Q filing text, and returns a response through a synchronous `Invoke` call.

Both functions reuse the SEC modules you built in earlier projects (CIK lookup, EDGAR API library). The key lesson is that Lambda is *not* your laptop: dependencies must be explicitly packaged, environment variables replace hardcoded paths, and the SEC Fair Access policy still applies in the cloud.

## What you will learn

* Initializing a SAM project with `sam init` and structuring `template.yaml`
* Packaging Python dependencies (notably `requests`) into a Lambda deployment
* Scheduling Lambda invocations with EventBridge via SAM event configuration
* Handling synchronous Lambda invocations and mapping request/response to a defined contract
* Reading CloudWatch logs to diagnose runtime failures

## Prerequisites

* Completed: Project SEC CIK Lookup Module
* Completed: Project SEC EDGAR API Library
* An AWS account with CLI access configured (see [Setup: AWS Account](/reference/setup-aws-account))
* Bedrock model access enabled (see [Setup: Bedrock Access](/reference/setup-bedrock-access))
* SAM CLI installed ([AWS SAM CLI install guide](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/install-sam-cli.html))
* Python 3.12

## Pages in this module

| Page                                                                                    | Purpose                                                                     |
| --------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- |
| [Lambda Project Setup](/cloud-deployment-sam/project-sec-lambda/lambda-project-setup)   | Initialize the SAM project, configure `template.yaml`, package dependencies |
| [Lambda Error Handling](/cloud-deployment-sam/project-sec-lambda/lambda-error-handling) | Error patterns, CloudWatch logs, local debugging with `sam local invoke`    |

***

Last verified: 2026-06


# Lambda Project Setup

## Goal

Initialize a SAM project containing two Lambda functions (EDGAR file refresh and document retrieval), configure `template.yaml` for Python 3.12, and deploy the stack so that both functions run in AWS with their dependencies correctly packaged.

## Contract

Your Lambda 2 (document retrieval) function accepts requests and returns responses conforming to the [Lambda Contract](/reference/contract).

The model used for inference is defined in [Models](/reference/models). Link there rather than hardcoding an identifier.

## Required Reading

* [AWS SAM CLI Developer Guide — sam init](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-cli-command-reference-sam-init.html)
* [AWS SAM template anatomy](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-specification-template-anatomy.html)
* [AWS::Serverless::Function](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-function.html)
* [Lambda runtimes — Python 3.12](https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html)
* [SEC EDGAR Fair Access policy](https://www.sec.gov/os/webmaster-faq#developers)
* [Lambda Contract](/reference/contract)
* [Models](/reference/models)

## Constraints

1. **SAM CLI only.** Use `sam init`, `sam build`, and `sam deploy` for all infrastructure. No console-click deployments; no raw CloudFormation uploads.
2. **Python 3.12 runtime.** Both functions must declare `Runtime: python3.12` in `template.yaml`.
3. **Package `requests` explicitly.** The `requests` library is not included in the Lambda Python runtime. List it in a `requirements.txt` at each function's root so that `sam build` bundles it into the deployment artifact.
4. **Custom User-Agent on all SEC EDGAR requests.** The SEC Fair Access policy requires a declared identity. Every HTTP call to `sec.gov` must include a `User-Agent` header with your name and contact email. Recall the pattern from the CIK Lookup project.
5. **No comments inside JSON blocks.** JSON does not support comments. Configuration examples in your code (event payloads, `template.yaml` outputs) must contain only valid JSON.
6. **Lambda I/O matches the contract.** Lambda 2 must accept exactly the fields defined in the [request schema](/reference/contract) and return the [response schema](/reference/contract). Do not invent additional fields or rename existing ones.
7. **S3 bucket versioning enabled.** Lambda 1 uploads to a versioned S3 bucket. Configure this in `template.yaml`, not after the fact in the console.
8. **EventBridge schedule for Lambda 1.** Use a SAM `Schedule` event to trigger the daily refresh. Note: EventBridge cron expressions evaluate in UTC.

## Acceptance Criteria

* `sam build` completes without errors.
* `sam deploy --guided` creates the CloudFormation stack with both functions, an S3 bucket (versioning enabled), and an EventBridge rule.
* Lambda 1 executes on schedule, downloads `https://www.sec.gov/files/company_tickers.json`, and uploads it to S3. CloudWatch logs show a successful invocation.
* Lambda 2, when invoked with a valid [contract request](/reference/contract), returns a well-formed response. When invoked with an invalid `period` value, it returns a validation error.
* All SEC EDGAR HTTP requests include a `User-Agent` header matching your declared identity.
* `sam local invoke` with a sample event file produces a response locally before deploying.

## Hints

<details>

<summary>Project initialization</summary>

`sam init` offers templates. Choose the "Hello World" Python template, then replace the generated function code with your own. The directory structure SAM creates is significant: each function lives in its own subdirectory with its own `requirements.txt`.

</details>

<details>

<summary>template.yaml structure</summary>

You need two `AWS::Serverless::Function` resources. Each specifies `Runtime`, `Handler`, `CodeUri` (pointing to the function subdirectory), and its event source. Lambda 1 gets a `Schedule` event; Lambda 2 gets no event source in the template (it is invoked programmatically).

The S3 bucket is a separate `AWS::S3::Bucket` resource with `VersioningConfiguration` set.

```yaml
AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Description: SEC Lambda project — EDGAR refresh and document retrieval

Globals:
  Function:
    Runtime: python3.12
    Timeout: 30
    MemorySize: 256

Resources:
  EdgarRefreshFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: app.lambda_handler
      CodeUri: edgar_refresh/
      Environment:
        Variables:
          BUCKET_NAME: !Ref EdgarBucket
      Policies:
        - S3CrudPolicy:
            BucketName: !Ref EdgarBucket
      Events:
        DailyRefresh:
          Type: Schedule
          Properties:
            Schedule: cron(0 6 * * ? *)

  DocumentRetrievalFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: app.lambda_handler
      CodeUri: document_retrieval/
      Environment:
        Variables:
          BUCKET_NAME: !Ref EdgarBucket
      Policies:
        - S3ReadPolicy:
            BucketName: !Ref EdgarBucket

  EdgarBucket:
    Type: AWS::S3::Bucket
    Properties:
      VersioningConfiguration:
        Status: Enabled
```

Note: both functions inherit `Runtime: python3.12` from the `Globals` section. Each function's `CodeUri` points to a subdirectory containing its own `requirements.txt` with `requests` listed.

</details>

<details>

<summary>Packaging requests</summary>

Create a `requirements.txt` in each function's `CodeUri` directory containing `requests`. When you run `sam build`, SAM installs those dependencies into the build artifact automatically. Verify by checking the `.aws-sam/build/` directory.

</details>

<details>

<summary>User-Agent header</summary>

Pass a `headers` dict to every `requests.get()` call targeting `sec.gov`:

```python
headers = {"User-Agent": "YourName your@email.edu"}
```

This is the same pattern you used in the CIK module. Extract it to a constant or environment variable so it is easy to update.

</details>

<details>

<summary>Local testing</summary>

Create a JSON file (e.g., `events/retrieve.json`) containing a valid contract request. Then:

```bash
sam local invoke DocumentRetrievalFunction --event events/retrieve.json
```

This runs the function in a Docker container matching the Lambda runtime. If you see `No module named 'requests'`, run `sam build` first.

</details>

<details>

<summary>EventBridge UTC gotcha</summary>

SAM `Schedule` expressions use UTC. If you set `cron(0 12 * * ? *)` expecting noon local time, it fires at noon UTC. Adjust for your timezone during testing, or set a frequent schedule (every 5 minutes) to verify quickly, then switch to daily.

</details>

***

Last verified: 2026-06


# Lambda Error Handling

## Goal

Build a robust error-handling layer for your SEC Lambda functions so that failures surface clearly in logs, callers receive structured error responses, and you can diagnose problems without redeploying.

## Contract

Your Lambda function must:

* Return a structured JSON error response (not a stack trace) when something goes wrong.
* Distinguish between client errors (bad input) and server errors (downstream failures).
* Produce CloudWatch log entries that let you trace a specific invocation from request to failure.
* Set the appropriate HTTP status code when invoked behind API Gateway (via the [Lambda contract](/reference/contract)).

When another Lambda invokes yours synchronously, the caller must detect failures via the `FunctionError` field in the `invoke` response — not by parsing the payload alone.

## Required Reading

* [AWS Lambda error handling (Python)](https://docs.aws.amazon.com/lambda/latest/dg/python-exceptions.html)
* [Invocation response — FunctionError field](https://docs.aws.amazon.com/lambda/latest/dg/API_Invoke.html#API_Invoke_ResponseSyntax)
* [Lambda retry behavior](https://docs.aws.amazon.com/lambda/latest/dg/invocation-retries.html)
* [CloudWatch Logs concepts](https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/CloudWatch-Logs-Monitoring-CloudWatch-Metrics.html)
* [SAM CLI logs command](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-cli-command-reference-sam-logs.html)
* [Lambda contract](/reference/contract)
* [Models reference](/reference/models)

## Constraints

1. Use `try`/`except` (Python's term — not "try/catch").
2. Every `except` block must log the exception with enough context to identify the failing invocation (request ID, input parameters).
3. Never return a raw traceback to callers. Return a JSON body with an `error` key describing the failure class.
4. When invoking another Lambda with `boto3`, always check the response for the `FunctionError` key before reading the payload.
5. Use `sam logs` and `sam local invoke` for local reproduction before deploying diagnostic changes.
6. All model references link to the [Models reference page](/reference/models).

## Acceptance Criteria

1. Your handler wraps all business logic in a `try`/`except` that catches specific exception types before a final catch-all `Exception`.
2. A Bedrock `invoke_model` call that fails (throttle, invalid input, expired model) is caught, logged with the request context, and returned as a structured error.
3. An SEC EDGAR HTTP request that fails (network timeout, 403 from missing User-Agent, non-200 status) is caught and surfaced with the URL that failed.
4. When Lambda A invokes Lambda B synchronously, Lambda A checks `response["FunctionError"]` and handles the downstream failure without crashing.
5. You can find your Lambda's log group in CloudWatch, locate a specific invocation by request ID, and read the error output.
6. You can reproduce a failure locally using `sam local invoke` with a test event, and stream deployed logs using `sam logs --tail`.

## Hints

<details>

<summary>Structuring the try/except</summary>

Catch the narrowest exceptions first. For Bedrock calls, `botocore.exceptions.ClientError` covers API-level failures. For HTTP calls, `requests.exceptions.RequestException` covers network-level failures. A final `except Exception` prevents unhandled crashes but should log at ERROR level — it means you missed a case.

</details>

<details>

<summary>Detecting FunctionError from a caller</summary>

When you call `lambda_client.invoke(FunctionName=..., Payload=...)`, the response dict always has a `StatusCode`. But a 200 status does NOT mean the invoked function succeeded — Lambda returns 200 for the transport even when the function itself raised an error. Check `response.get("FunctionError")`. If present, the value is `"Handled"` or `"Unhandled"` and the `Payload` contains the error output, not your expected result.

</details>

<details>

<summary>Finding your log group in CloudWatch</summary>

Lambda log groups follow the naming pattern `/aws/lambda/<function-name>`. Inside the log group, each invocation creates log entries prefixed with the request ID. Search for that ID to isolate a single execution. The `START`, `END`, and `REPORT` lines bracket each invocation and show duration, memory used, and billed duration.

</details>

<details>

<summary>Using sam logs and sam local invoke</summary>

`sam logs -n YourFunctionName --stack-name your-stack --tail` streams live logs from the deployed function — useful during integration testing. `sam local invoke YourFunctionName -e events/test-event.json` runs the function in a local Docker container using your `template.yaml` definition — useful for reproducing errors without deploying.

</details>

<details>

<summary>Consolidated error list</summary>

Your SEC Lambda functions will encounter these error categories:

| Category               | Example                                 | Suggested Response                        |
| ---------------------- | --------------------------------------- | ----------------------------------------- |
| Input validation       | Missing `ticker` field                  | 400 — describe the missing/invalid field  |
| SEC EDGAR network      | Timeout or connection refused           | 502 — upstream unreachable                |
| SEC EDGAR 403          | Missing or malformed User-Agent         | 502 — access denied by upstream           |
| SEC EDGAR 404          | Invalid CIK or accession number         | 404 — filing not found                    |
| Bedrock throttle       | `ThrottlingException`                   | 429 — retry after backoff                 |
| Bedrock model error    | Invalid model ID or payload             | 502 — model invocation failed             |
| Bedrock response parse | Unexpected response shape               | 500 — internal parse error                |
| S3 access              | Bucket/key not found, permission denied | 500 — storage access failed               |
| Unhandled              | Anything not caught above               | 500 — internal error (log full traceback) |

</details>

***

Last verified: 2026-06


# CDK Bridge: SAM to CDK

This module marks the transition point in the course. Everything before this module deploys with SAM CLI. Everything after deploys with the AWS Cloud Development Kit (CDK).

SAM served you well for the SEC Lambda project — a single function, a `template.yaml`, and `sam deploy`. That workflow stays manageable when you have one or two resources. The 10Q Inference project ahead requires a Lambda function, an S3 bucket, IAM roles with cross-service permissions, a Bedrock inference profile reference, and eventually an API Gateway. Defining all of that in raw CloudFormation YAML becomes brittle. CDK lets you write that infrastructure in Python 3.12 — the same language as your Lambda code — with constructs that handle sensible defaults, cross-resource wiring, and type checking.

## What this module covers

1. **Why CDK?** — When SAM stops scaling and what CDK gives you in return.
2. **Project: CDK Init** — Initialize a CDK Python project, understand its structure, and prepare it for the 10Q Inference deployment.

## Prerequisites

* Completed: Project SEC Lambda (SAM) — you need a working Lambda before bridging to CDK
* AWS CLI configured (see [Setup: AWS Account](/reference/setup-aws-account))
* Python 3.12
* Node.js 18+ (CDK CLI is a Node package)

## What comes after

Every module past this point — 10Q Inference, Partner Bot Web Page, MCP Server, RAG Pipeline — deploys with CDK. The patterns you learn here carry forward unchanged.

***

Last verified: 2026-06


# Why CDK?

## When SAM is enough

SAM CLI works best when your infrastructure is a small, self-contained unit: one or two Lambda functions, a single trigger, and a deployment bucket. The SEC Lambda project fits that shape. You write a `template.yaml`, run `sam build && sam deploy`, and the deployment finishes in under a minute.

SAM's strength is brevity. Its `AWS::Serverless::Function` resource hides the underlying CloudFormation machinery — the IAM execution role, the log group, the deployment package upload. For a single-function project, that hiding is a feature.

## When SAM stops scaling

Add a second function that reads from S3. Now you need an S3 bucket resource, a bucket policy, and an IAM policy statement granting the function read access. Add Bedrock invocation — another IAM permission block. Add an API Gateway in front — a RestApi resource, a deployment stage, a method, a Lambda permission, and CORS configuration.

Each new resource in `template.yaml` adds 10-30 lines of YAML. Worse, the relationships between resources are expressed as string references (`!Ref`, `!GetAtt`) with no validation until deploy time. A typo in a logical ID silently produces a broken stack.

SAM does not prevent you from building complex stacks. It just offers no help once you leave the single-function sweet spot.

## What CDK gives you

CDK is an infrastructure-as-code framework where you define AWS resources in a general-purpose programming language. This course uses Python 3.12.

### Type-checked resource definitions

CDK constructs are Python classes with typed parameters. Your editor catches invalid property names, wrong types, and missing required fields before you deploy. Compare:

**SAM (YAML, validated at deploy time):**

```yaml
Resources:
  InferenceFunction:
    Type: AWS::Serverless::Function
    Properties:
      Runtime: python3.12
      Handler: handler.lambda_handler
      Policies:
        - Statement:
            - Effect: Allow
              Action: bedrock:InvokeModel
              Resource: "*"
```

**CDK (Python, validated at synth time):**

```python
inference_fn = _lambda.Function(
    self, "InferenceFunction",
    runtime=_lambda.Runtime.PYTHON_3_12,
    handler="handler.lambda_handler",
    code=_lambda.Code.from_asset("lambda"),
)
inference_fn.add_to_role_policy(
    iam.PolicyStatement(
        actions=["bedrock:InvokeModel"],
        resources=["*"],
    )
)
```

The CDK version is slightly longer, but if you misspell `runtime` or pass an integer where a string is expected, the error appears immediately — not after a two-minute CloudFormation rollback.

### Automatic cross-resource wiring

When one resource needs to reference another, CDK handles the glue. Granting a Lambda function read access to an S3 bucket:

```python
bucket.grant_read(inference_fn)
```

That single line generates the IAM policy statement, attaches it to the function's execution role, and sets up the dependency ordering so the bucket exists before the function deploys. In SAM/CloudFormation you would write all three pieces by hand.

### Constructs compose

A CDK "construct" is a reusable building block. You can wrap a pattern (Lambda + API Gateway + CORS configuration) into a single class and instantiate it across stacks. The 10Q Inference project uses this to define its multi-function architecture without repeating boilerplate.

### Synthesize before deploy

`cdk synth` generates the CloudFormation template locally. You can inspect exactly what will be created, diff it against the deployed stack (`cdk diff`), and catch misconfigurations before any AWS API call is made.

## When to stay with SAM

SAM remains the right choice when:

* Your project is a single Lambda function with a simple trigger.
* You need `sam local invoke` for rapid local iteration (CDK does not have a built-in local execution equivalent — you use `pytest` or invoke the function directly).
* Your team already has SAM templates in production and migration cost outweighs benefit.

This course uses SAM for the SEC Lambda project because it is genuinely simpler there. The transition to CDK happens here, at the point where complexity justifies the tooling shift.

## Summary

| Dimension             | SAM                                    | CDK                                                  |
| --------------------- | -------------------------------------- | ---------------------------------------------------- |
| Language              | YAML (CloudFormation shorthand)        | Python 3.12 (or TypeScript, Java, etc.)              |
| Validation timing     | Deploy (CloudFormation)                | Synth (local, before any API call)                   |
| Cross-resource wiring | Manual `!Ref` / `!GetAtt`              | Automatic via `.grant_*()` and construct references  |
| Reuse pattern         | Copy-paste YAML sections               | Compose constructs as Python classes                 |
| Best for              | Single-function, simple-trigger stacks | Multi-resource stacks with cross-service permissions |
| Local testing         | `sam local invoke`                     | `pytest` + direct Lambda invocation                  |

The next page walks you through initializing your first CDK project.

***

Last verified: 2026-06


# Project: CDK Init

## Goal

Initialize a CDK Python project that will serve as the deployment foundation for the 10Q Inference module. By the end of this project you will have a working CDK app that synthesizes a valid CloudFormation template, even though it deploys no resources yet. The 10Q Inference project builds on top of this skeleton.

## Contract

This project produces infrastructure code, not a Lambda function. There is no request/response contract here. The Lambda functions you deploy in later modules conform to the [Lambda Contract](/reference/contract) — the CDK stack is the mechanism that deploys them.

The model your Lambda will invoke is defined in [Models](/reference/models). You will reference that page when adding Bedrock permissions in the next module.

## Required Reading

* [Why CDK?](/cloud-deployment-cdk/cdk-bridge/why-cdk) — the preceding page in this module
* [AWS CDK Getting Started (Python)](https://docs.aws.amazon.com/cdk/v2/guide/getting-started.html) — official install and bootstrapping guide
* [AWS CDK API Reference — aws-cdk-lib](https://docs.aws.amazon.com/cdk/api/v2/python/) — construct library documentation

## Constraints

* Use Python 3.12 as both the CDK app language and the target Lambda runtime in later stacks.
* Install the CDK CLI globally via npm (`npm install -g aws-cdk`). Do not use a Python-packaged CDK CLI wrapper.
* Use CDK v2 (`aws-cdk-lib`). Do not use CDK v1 individual packages.
* The project must synthesize cleanly (`cdk synth` exits zero and produces a valid template) before you consider this project complete.
* Do not deploy to AWS yet. `cdk synth` and `cdk diff` are sufficient for this project. Deployment happens in the 10Q Inference module.

## Acceptance Criteria

1. Running `cdk init app --language python` in a fresh directory produces a working project skeleton.
2. The project uses a Python 3.12 virtual environment with dependencies installed from `requirements.txt`.
3. `cdk synth` produces a CloudFormation template in `cdk.out/` without errors.
4. The generated stack class is in a Python module under the project directory (not in `app.py` directly).
5. You can explain what each generated file does — `app.py`, the stack module, `cdk.json`, `requirements.txt`, and the `tests/` directory.

## Hints

### Installing the CDK CLI

```bash
npm install -g aws-cdk
cdk --version
```

You need Node.js 18 or later. The CDK CLI is a Node package regardless of which language your CDK app uses.

### Bootstrapping your AWS account

Before any CDK app can deploy (in later modules), the target account/region needs a one-time bootstrap:

```bash
cdk bootstrap aws://<ACCOUNT_ID>/<REGION>
```

This creates an S3 bucket and IAM roles that CDK uses for deployments. Run it once per account/region pair. You do not need to bootstrap for `cdk synth` — only for `cdk deploy`.

### Initializing the project

```bash
mkdir cdk-10q-inference
cd cdk-10q-inference
cdk init app --language python
```

This generates:

| File/Directory                                 | Purpose                                               |
| ---------------------------------------------- | ----------------------------------------------------- |
| `app.py`                                       | Entry point — instantiates the CDK app and your stack |
| `cdk_10q_inference/`                           | Python package containing your stack class            |
| `cdk_10q_inference/cdk_10q_inference_stack.py` | The stack definition (where you add resources)        |
| `cdk.json`                                     | CDK configuration — tells the CLI how to run your app |
| `requirements.txt`                             | Python dependencies (`aws-cdk-lib`, `constructs`)     |
| `requirements-dev.txt`                         | Dev dependencies (e.g., `pytest`)                     |
| `tests/`                                       | Test directory with a placeholder unit test           |

### Activating the virtual environment

After `cdk init`, activate the generated virtual environment and install dependencies:

```bash
python3.12 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
```

Use Python 3.12 explicitly. If your system default is a different version, specify the full path or use `python3.12` directly.

### Verifying the skeleton

```bash
cdk synth
```

This runs `app.py`, which instantiates your stack, and outputs the synthesized CloudFormation template to stdout. It also writes the full output to `cdk.out/`. If this exits zero, your project structure is correct.

### Understanding `app.py`

The entry point is minimal — it creates a `cdk.App`, instantiates your stack class, and calls `app.synth()`. All resource definitions belong in the stack class, not in `app.py`.

### The stack class

Open the generated stack file (e.g., `cdk_10q_inference/cdk_10q_inference_stack.py`). It subclasses `Stack` and currently has an empty `__init__`. In the next module you will add Lambda functions, S3 buckets, and IAM policies here.

### What `cdk.json` controls

The `app` field in `cdk.json` tells the CDK CLI how to execute your app:

```json
{
  "app": "python3 app.py"
}

```

If you renamed `app.py` or need to pass environment variables, this is where you configure it.

### Next step

Once `cdk synth` succeeds with no errors, this project is complete. The 10Q Inference module picks up from this skeleton and adds the actual Lambda and supporting resources.

***

Last verified: 2026-06


# Introduction to Large Language Models

The roadmap.sh site does a great job at describing the Large Language Models and Prompt Engineering. We will attempt to summarize the concept however [following the Prompt Engineering](https://roadmap.sh/prompt-engineering) roadmap path is highly recommended.

### What are LLMs?

> LLMs, or Large Language Models, are advanced Artificial Intelligence models specifically designed for understanding and generating human language. These models are typically based on deep learning architectures, such as Transformers, and are trained on massive amounts of text data from various sources to acquire a deep understanding of the nuances and complexities of language.
>
> LLMs have the ability to achieve state-of-the-art performance in multiple Natural Language Processing (NLP) tasks, such as machine translation, sentiment analysis, summarization, and more. They can also generate coherent and contextually relevant text based on given input, making them highly useful for applications like chatbots, question-answering systems, and content generation.
>
> As an example, OpenAI's GPT-3 is a prominent LLM that has gained significant attention due to its capability to generate high-quality text and perform a variety of language tasks with minimal fine-tuning.

Quote from [Roadmap.sh What are LLMs?](https://github.com/kamranahmedse/developer-roadmap/blob/master/src/data/roadmaps/prompt-engineering/content/100-basic-llm/100-what-are-llms.md)

### Why Run LLMs in the cloud?

Large Language Models are computationally intensive typically requiring GPU offload for additional computation and a large amount of system memory to host them. [Efficient Memory Management for Large Language Model Service with PagedAttention](https://dl.acm.org/doi/pdf/10.1145/3600006.3613165) dives deep on the Key Value (KV) space and its effect on required VRAM and how to more effectively minimize the VRAM usage reducing memory requirements.\
\
The main takeaway is while there are some people experimenting running LLMs themselves the infrastructure required to make use of them at scale lends itself well to cloud computing.\
\
For this course we are using AWS's Bedrock Service which hosts a set of LLM models. On that platform we will use [Anthropic's Claude model](https://www.anthropic.com/claude). Using Claude through Bedrock makes it easier to integrate with other AWS services.

### LLM Prompt Context

#### What is a Prompt?

A prompt is the input given to an LLM to generate a response. It can be a question, a statement, or any text that guides the model on what kind of output is expected. For example, if you ask an LLM, "What is the capital of France?", the prompt is "What is the capital of France?".

#### What is Prompt Context?

Prompt context refers to the additional information or background provided to the LLM to help it generate a more accurate and relevant response. This context can include:

* **Previous conversation history:** If you're having a chat with an LLM, the context includes all the previous messages exchanged.
* **Specific instructions:** Guidelines on how the LLM should respond, such as the tone, style, or format.
* **Relevant details:** Any additional information that can help the LLM understand the prompt better.

#### Why is Prompt Context Important?

Prompt context is crucial because it helps the LLM generate responses that are more accurate, coherent, and relevant to the user's needs. Without context, the model might produce responses that are off-topic or lack depth.

***

Last verified: 2026-06


# Project: 10Q Inference (CDK)

LLMs answer from their training data. When a student asks about a company's most recent quarterly filing, the model's training cutoff means it likely has not seen that disclosure. The answer it produces sounds confident but is often wrong — invented figures, conflated dates, or outright hallucinations.

This project teaches you to close that gap. You build a CDK-deployed Lambda that retrieves a real SEC 10-Q filing, injects it as context into the prompt, and returns an answer grounded in the actual document. By the end of this module you will have seen the failure mode first-hand (Part 1), fixed it with retrieval-augmented context (Part 2), extracted clean text from a filing (Part 3), and wired it all together behind the course Lambda contract (Part 4).

## Module structure

| Part                                                                                                                  | Focus                                                        |
| --------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------ |
| [Part 1: Inference Test](/cloud-deployment-cdk/project-10q-inference/part-1-inference-test)                           | Invoke the model without context and observe its limitations |
| [Part 2: Inference with Context](/cloud-deployment-cdk/project-10q-inference/part-2-inference-with-context)           | Provide filing text as context and compare the results       |
| [Part 3: Text Extraction](/cloud-deployment-cdk/project-10q-inference/part-3-text-extraction)                         | Extract and prepare filing text for the prompt               |
| [Part 4: Question to Enhanced Prompt](/cloud-deployment-cdk/project-10q-inference/part-4-question-to-enhanced-prompt) | End-to-end Lambda conforming to the contract                 |

## Prerequisites

* Completed the [CDK Bridge](/cloud-deployment-cdk/cdk-bridge) module (your CDK project is initialized)
* Working EDGAR API library from [Project: SEC EDGAR API Library](/foundations/project-sec-edgar-api-library)
* Bedrock model access enabled per [Setup: Bedrock Access](/reference/setup-bedrock-access)

## Deployment tool

Every part of this module uses **AWS CDK (Python 3.12)** for infrastructure deployment. If you have not yet initialized your CDK project, complete [Project: CDK Init](/cloud-deployment-cdk/cdk-bridge/project-cdk-init) first.

## Key references

* [Models](/reference/models) — the canonical model identifier for all Bedrock calls
* [Lambda Contract](/reference/contract) — the request/response schema your Lambda must conform to

***

Last verified: 2026-06


# Part 1: Inference Test

## Goal

Invoke the [canonical course model](/reference/models) on Bedrock and observe how it responds to questions about information beyond its training cutoff. You will see first-hand that the model produces plausible but inaccurate answers when asked about very recent SEC filings — establishing the motivation for retrieval-augmented context in Part 2.

## Contract

This part has no Lambda contract yet — you are calling the model interactively from a Python script. Part 4 wraps this into the full [Lambda Contract](/reference/contract).

Your script invokes the Bedrock `InvokeModel` or `Converse` API and prints the response text to stdout.

## Required Reading

* [InvokeModel API Reference](https://docs.aws.amazon.com/bedrock/latest/APIReference/API_runtime_InvokeModel.html)
* [Converse API Reference](https://docs.aws.amazon.com/bedrock/latest/APIReference/API_runtime_Converse.html)
* [Invoke Model Guide](https://docs.aws.amazon.com/bedrock/latest/userguide/inference-invoke.html)
* [Models](/reference/models) — use the canonical model identifier listed there

## Constraints

* Use Python 3.12 and boto3.
* Use the model identifier from [Models](/reference/models). Do not hardcode a model ID in your script.
* Deploy your inference script's supporting infrastructure (IAM role, any test Lambda) via CDK.
* Do not provide any filing text as context in this part — the point is to observe the model's limitations without help.

## Acceptance Criteria

1. Your script successfully invokes the model and prints a response.
2. You ask the model at least one question about a specific figure from a company's most recent 10-Q filing (filed within the last three months). Use the EDGAR API skills from the previous project to identify a recent filing.
3. You document the model's response and compare it against the actual filing. The model should produce an inaccurate or incomplete answer — demonstrating the training cutoff gap.
4. You save both the question(s) and response(s) for comparison in Part 2.

## Hints

Start by confirming your Bedrock access works. Ask the model a general knowledge question — something it will answer correctly — to verify your boto3 call is wired up:

* "Can you explain what a 10-Q filing contains?"
* "What is a stock ticker symbol?"

Once the plumbing works, shift to the real exercise: find a company's most recent quarterly filing on SEC EDGAR. Look for filings from the last few months — these are beyond the model's training cutoff. Within the filing, find a specific dollar figure (revenue, investment amount, operating expense) in a section like "Management's Discussion and Analysis."

Ask the model about that figure without providing the filing. Note how confident the response sounds despite being wrong or incomplete.

The model's training data has a cutoff date. Facts disclosed in very recent filings are invisible to it. This is not a bug — it is the fundamental limitation that Part 2 solves with retrieval-augmented context.

***

> **Capture-Dated Example** — results shown may differ from your own.

The prompt below was sent to the model without any filing context. The model's response demonstrates that it lacks specific knowledge of facts published after its training cutoff.

**Prompt:**

`How much did Amazon invest in Anthropic in Q3 2023 and Q1 2024?`

**Response:**

```
In Q3 2023, Amazon announced it would invest up to $4 billion in Anthropic. In January 2024 (Q1 2024), Amazon completed an initial $1.25 billion investment in Anthropic as part of that agreement, with the potential to invest up to $2.75 billion more in the future. So the confirmed investment amounts are:

Q3 2023: Announced up to $4 billion (but no money invested yet)
Q1 2024: $1.25 billion actually invested

The remaining potential investment of up to $2.75 billion has not been made as of early 2024 and would depend on future decisions by Amazon.
```

The [10-Q filing](https://www.sec.gov/ix?doc=/Archives/edgar/data/1018724/000101872424000130/amzn-20240630.htm) shows the actual figures differ from the model's response. The model conflated announced intent with actual investment amounts.

**Key takeaway:** an LLM answering without context produces plausible but inaccurate financial details. This is why Part 2 provides the filing text as context.

When you run this exercise, pick a recent filing (within the last few months) and ask about a specific figure from it. The model's training cutoff means it will not have seen very recent disclosures — use that gap to demonstrate the value of retrieval-augmented context.

***

Last verified: 2026-06


# Part 2: Inference with Context

## Goal

Demonstrate that providing a 10-Q filing as prompt context transforms the model's output from confident guessing to accurate extraction. You will ask the same question from Part 1, but this time include the filing text in the prompt. The contrast between the two responses is the core lesson of this module: retrieval-augmented context gives an LLM access to information beyond its training cutoff.

## Contract

This part has no Lambda contract yet — you are calling the model interactively. Part 4 wraps the full flow into the [Lambda Contract](/reference/contract).

Your script sends a prompt containing both your question and the filing text, then prints the response.

## Required Reading

* [Converse API Reference](https://docs.aws.amazon.com/bedrock/latest/APIReference/API_runtime_Converse.html)
* [Models](/reference/models) — use the canonical model identifier
* [Lambda Contract](/reference/contract) — the schema your final Lambda will conform to in Part 4

Review the SEC EDGAR module pages for retrieving filing documents programmatically:

* [Filter Submissions and Retrieve Doc](/foundations/project-sec-edgar-api-library/filter-submissions-and-retrieve-doc)

## Constraints

* Use Python 3.12 and boto3.
* Use the model identifier from [Models](/reference/models).
* Deploy supporting infrastructure via CDK.
* Use a 10-Q filing from the last three months. Do not hardcode a specific filing date or company — your approach must work for any recent filing.
* Retrieve the filing text programmatically using your EDGAR API library or Lambda from previous modules.

## Acceptance Criteria

1. Your script retrieves the text of a recent 10-Q filing (filed within the last three months) for your chosen company.
2. Your script sends a prompt that includes both the question and the filing text as context.
3. The model's response accurately extracts the requested figure from the filing, in contrast to the inaccurate response from Part 1.
4. You compare the Part 1 (no context) and Part 2 (with context) responses side by side, confirming the improvement.

## Hints

Structure your prompt so the model knows to answer from the provided text rather than its training data. A pattern that works well:

```
Using the information below, answer the following question.

Question: <your question here>

Document:
<filing text here>
```

Retrieve the filing text using your EDGAR API Lambda or library. The 10-Q is HTML on SEC EDGAR — you need the text content, not the raw markup. For now, a basic approach (strip tags, extract readable text) is sufficient. Part 3 covers text extraction in depth.

Be aware of the model's input token limit. A full 10-Q can be tens of thousands of tokens. If your filing is very long, truncate to the relevant section (the one containing the answer to your question). The model performs better on focused context than on a massive document dump.

Pick the same company and question you used in Part 1. The side-by-side comparison is the deliverable — it demonstrates that context is the difference between a wrong answer and a right one.

***

> **Capture-Dated Example** — results shown may differ from your own.

This example uses the same question from Part 1, but provides the full 10-Q filing text as context.

**Prompt structure:**

```
Using the information below, answer the following question.

Question: How much did Amazon invest in Anthropic in Q3 2023 and Q1 2024?

Document:
<full 10-Q filing text inserted here>
```

The prompt included the complete text of Amazon's 10-Q for the quarter ended June 30, 2024 (filed August 2024).

**Response:**

```
Based on the information provided in the quarterly report:

- Amazon invested $1.25 billion in a convertible note from Anthropic in Q3 2023.

- Amazon invested $2.75 billion in a second convertible note from Anthropic in Q1 2024.

So the total investment in Anthropic across Q3 2023 and Q1 2024 was $4 billion.
```

**Key takeaway:** with the filing text as context, the model extracts the correct figures directly from the disclosure. Compare this to the incorrect response in Part 1 where no context was provided. The model did not learn new facts — it simply had access to the relevant document.

When you run this exercise, use the same company and question from Part 1. Download the filing text using your EDGAR API library and include it in the prompt. The contrast between your two responses is the core deliverable of this part.

***

Last verified: 2026-06


# Part 3: Text Extraction

## Goal

Build a reusable text-extraction utility that converts a raw SEC filing (HTML) into clean plain text suitable for inclusion in an LLM prompt. SEC EDGAR serves 10-Q filings as HTML documents. Passing raw HTML to the model wastes tokens on markup and confuses extraction. This part teaches you to strip the markup, estimate the resulting token count, and truncate intelligently so the text fits within the model's context window.

## Contract

This part produces a utility function, not a standalone Lambda. The function accepts raw HTML (as returned by your EDGAR API library) and returns plain text ready for prompt injection.

The utility is consumed by the Lambda you build in [Part 4](/cloud-deployment-cdk/project-10q-inference/part-4-question-to-enhanced-prompt), which conforms to the [Lambda Contract](/reference/contract).

**Function signature (conceptual):**

```
extract_text(html: str, max_tokens: int) -> str
```

* `html` — the raw HTML body of a 10-Q filing retrieved from SEC EDGAR.
* `max_tokens` — the maximum number of tokens the returned text should approximate (a budget, not a hard guarantee).
* Returns plain text with HTML tags stripped and whitespace normalized, truncated to fit within `max_tokens`.

## Required Reading

* Python standard library: [`html.parser`](https://docs.python.org/3.12/library/html.parser.html)
* [Bedrock runtime quotas](https://docs.aws.amazon.com/bedrock/latest/userguide/quotas.html) — understand input token limits
* [Models](/reference/models) — the canonical model and its context window size
* [Filter Submissions and Retrieve Doc](/foundations/project-sec-edgar-api-library/filter-submissions-and-retrieve-doc) — how your EDGAR library retrieves filing HTML

## Constraints

* Use Python 3.12.
* Use only the Python standard library for HTML parsing (no BeautifulSoup, no lxml). The stdlib `html.parser` module is sufficient for this task.
* Deploy any supporting infrastructure via CDK.
* Your extraction must handle the common structure of SEC 10-Q filings: nested tables, inline styles, `<ix:` XBRL tags, and `&nbsp;` entities.
* The token estimate does not need to be exact. A simple heuristic (characters divided by four, or words multiplied by 1.3) is acceptable. Do not call the Bedrock tokenizer API for this step.
* Your truncation strategy must preserve complete sentences or paragraphs — do not cut mid-word or mid-sentence.

## Acceptance Criteria

1. Given a raw 10-Q HTML document, your function returns readable plain text with no HTML tags, no inline CSS, and no XBRL markup.
2. Whitespace is normalized: no runs of blank lines, no leading/trailing whitespace on lines, no tab characters.
3. When the extracted text exceeds `max_tokens` (estimated), the function truncates to the budget while preserving sentence boundaries.
4. Your function handles at least three different companies' 10-Q filings without crashing or producing garbled output.
5. You can demonstrate the token reduction: show the raw HTML character count versus the extracted text character count for a sample filing.

## Hints

Start with a subclass of `html.parser.HTMLParser`. Override `handle_data` to collect text content and `handle_starttag`/`handle_endtag` to insert whitespace where block-level elements end (e.g., `</p>`, `</div>`, `</tr>`). Skip content inside `<style>` and `<script>` tags entirely.

SEC filings use XBRL inline tags (`<ix:nonFraction>`, `<ix:nonNumeric>`, etc.) that wrap the actual text. Your parser should treat these as transparent — extract the text inside them without emitting the tag names.

For `&nbsp;` and other HTML entities, `HTMLParser` calls `handle_entityref` or `handle_charref`. Convert them to their plain-text equivalents (a non-breaking space becomes a regular space).

For token estimation, a simple ratio works: one token is roughly four characters of English text. If your extracted text has 40,000 characters, that is approximately 10,000 tokens. This is imprecise but sufficient for budgeting. The model will not reject a prompt that is slightly over or under — you are avoiding the failure mode of sending a 200,000-token document into a model with a smaller context window.

Truncation strategy: split the text into paragraphs (double newline). Accumulate paragraphs until adding the next one would exceed the budget. This preserves document structure and avoids cutting mid-thought. If even the first paragraph exceeds the budget, fall back to sentence-level splitting.

Test with filings from different companies. SEC formatting varies — some use deeply nested tables for financial statements, others use flat `<p>` tags. Your parser should handle both gracefully.

***

Last verified: 2026-06


# Part 4: Question to Enhanced Prompt

## Goal

Build and deploy a CDK-backed Lambda that accepts a question, a stock ticker, a filing year, and a period — retrieves the relevant 10-Q filing from SEC EDGAR, extracts clean text using your Part 3 utility, constructs an enhanced prompt with the filing as context, invokes the [canonical course model](/reference/models), and returns the answer. This is the end-to-end pipeline: the Lambda receives a structured request and returns a grounded answer that the front-end (Partner Bot Web Page) will consume.

## Contract

Your Lambda's input and output must conform to the [Lambda Contract](/reference/contract).

**Request:**

```json
{
  "question": "What was the company's total revenue this quarter?",
  "ticker": "AMZN",
  "year": 2024,
  "period": "Q3"
}
```

**Response:**

```json
{
  "answer": "Based on the 10-Q filing, total net sales for the quarter were $158.9 billion, an increase of 11% compared to the same quarter in the prior year.",
  "meta": {
    "model": "the canonical model from reference/models.md",
    "input_tokens": 9842,
    "output_tokens": 287,
    "latency_ms": 3200
  }
}
```

The `period` field accepts `Q1`, `Q2`, `Q3`, `Q4`, or `FY`. Use the period to locate the correct filing on SEC EDGAR.

## Required Reading

* [Lambda Contract](/reference/contract) — the schema your Lambda must conform to
* [Models](/reference/models) — use the canonical model identifier
* [Converse API Reference](https://docs.aws.amazon.com/bedrock/latest/APIReference/API_runtime_Converse.html)
* [Part 3: Text Extraction](/cloud-deployment-cdk/project-10q-inference/part-3-text-extraction) — the utility that converts filing HTML to prompt-ready text
* [Filter Submissions and Retrieve Doc](/foundations/project-sec-edgar-api-library/filter-submissions-and-retrieve-doc) — filing retrieval via EDGAR
* [Project: CDK Init](/cloud-deployment-cdk/cdk-bridge/project-cdk-init) — CDK project structure

## Constraints

* Use Python 3.12 and boto3.
* Deploy via CDK. Define the Lambda function, its IAM role (Bedrock invoke permission, network egress for SEC EDGAR), and any supporting resources in your CDK stack.
* Use the model identifier from [Models](/reference/models). Do not hardcode a model ID.
* Validate the incoming event against the contract schema. Return a structured error (not a stack trace) for invalid requests.
* Use your Part 3 text-extraction utility to convert the filing HTML to plain text before injecting it into the prompt.
* Set a token budget for the filing context that leaves room for the question, the system instructions, and the model's response. A reasonable default: reserve 80% of the model's context window for the filing text.
* Include a custom `User-Agent` header on all SEC EDGAR HTTP requests.
* Do not provide complete solution code. Your implementation is the deliverable.

## Acceptance Criteria

1. Your Lambda accepts a valid contract request and returns a valid contract response.
2. Given an invalid request (missing field, invalid `period` value, non-uppercase ticker), your Lambda returns a structured validation error — not an unhandled exception.
3. The Lambda retrieves the correct 10-Q filing for the specified ticker, year, and period from SEC EDGAR.
4. The filing HTML is converted to plain text using your Part 3 extraction utility, respecting the token budget.
5. The enhanced prompt includes both the user's question and the extracted filing text as context.
6. The model's response answers the question using information from the filing (verifiable by reading the source document).
7. The `meta` object in the response includes the model identifier, input token count, output token count, and latency.
8. The Lambda is deployed via CDK with appropriate IAM permissions (Bedrock `InvokeModel`, network egress).

## Hints

The Lambda handler orchestrates four steps in sequence:

1. **Validate** — check that all required fields are present and conform to the contract. Return early with an error response if validation fails.
2. **Retrieve** — use your EDGAR API library to fetch the filing HTML for the given ticker, year, and period. Map the `period` value to the correct fiscal quarter endpoint on EDGAR.
3. **Extract** — pass the HTML through your Part 3 text-extraction function with a token budget.
4. **Invoke** — construct the enhanced prompt and call the model via the Converse API.

For the prompt structure, a pattern that works well:

```
Using only the SEC filing text provided below, answer the following question. If the answer is not contained in the filing, say so explicitly.

Question: {question}

Filing ({ticker} {period} {year}):
{extracted_text}
```

The instruction to answer "only from the filing" reduces hallucination. Including the ticker, period, and year in the prompt gives the model context about what document it is reading.

For the `meta` object, the Converse API response includes token usage in the `usage` field (`inputTokens`, `outputTokens`). Capture latency by timing the API call with `time.perf_counter()`.

Map `period` to the fiscal quarter when searching EDGAR submissions. A company filing a 10-Q for `Q2` of `2024` reports the quarter ended June 30, 2024 (for most calendar-year filers). Your EDGAR library's `filter_submissions` function should accept a form type (`10-Q`) and date range to locate the correct filing.

For `FY` (full-year), the relevant form type is `10-K`, not `10-Q`. Handle this case explicitly in your retrieval logic.

Pick a recent filing and verify the Lambda's answer against the source document. The integration test is: invoke the Lambda, read the answer, open the filing on EDGAR, and confirm the figure appears in the text.

***

Last verified: 2026-06


# Partner Bot Web Page

This module connects your 10Q Inference Lambda to a browser-based front end. By the end you will have a working React application — hosted on AWS Amplify — that lets a user select a company, filing period, and question, then displays the LLM-generated answer.

The module is the midpoint deliverable of the course: everything before it builds backend services; everything after it extends or secures the full stack. Completing it proves your Lambda, your deployment pipeline, and your front-end tooling all work together end to end.

## Technology choices

| Layer                           | Tool                            | Why                                                                        |
| ------------------------------- | ------------------------------- | -------------------------------------------------------------------------- |
| Hosting & backend orchestration | AWS Amplify Gen 2               | Declarative infrastructure, sandbox dev environments, CI-friendly deploys  |
| Build tool                      | Vite                            | Fast HMR, native ESM, zero-config React support                            |
| UI components                   | Amplify UI for React            | Accessible, themeable primitives that integrate with Amplify data and auth |
| Auth (later)                    | Amazon Cognito via Amplify Auth | Token-based identity with minimal custom code                              |

## Module sequence

| Page                                                                                                    | What you will do                                                             |
| ------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- |
| [Amplify Gen 2 Setup](/full-stack-integration/partner-bot-web-page/amplify-gen2-setup)                  | Initialize a Vite + React project with Amplify Gen 2 and deploy a sandbox    |
| [Build the Chat Form](/full-stack-integration/partner-bot-web-page/build-the-chat-form)                 | Compose a query form from Amplify UI components and render inference results |
| [Connect to Lambda via API Gateway](/full-stack-integration/partner-bot-web-page/connect-to-lambda)     | Wire the form to your 10Q Inference Lambda through an HTTP API               |
| [Authentication with Cognito](/full-stack-integration/partner-bot-web-page/authentication-with-cognito) | Add sign-in/sign-up and protect the API route with a JWT authorizer          |

## Prerequisites

* A deployed 10Q Inference Lambda that accepts requests conforming to the [Lambda Contract](/reference/contract)
* Node.js LTS and npm installed
* An AWS account with Amplify and Bedrock access enabled ([Setup: AWS Account](/reference/setup-aws-account))
* Git configured with a remote repository ([GitHub Setup](/foundations/introduction-to-git-and-github/project-github-setup))

***

Last verified: 2026-06


# Amplify Gen 2 Setup

## Goal

Scaffold a React application with Vite, initialize an Amplify Gen 2 backend, and deploy a cloud sandbox you can develop against. When finished you will have a running local dev server connected to a live Amplify sandbox environment.

## Contract

This page produces no Lambda interaction. The deliverable is a working project structure:

```
partner-bot/
├── amplify/
│   ├── backend.ts
│   └── ...
├── src/
│   ├── App.tsx
│   └── main.tsx
├── index.html
├── package.json
├── tsconfig.json
└── vite.config.ts
```

Amplify Gen 2 generates an `amplify_outputs.json` at the project root when you deploy a sandbox. Your React app imports this file to discover backend endpoints at runtime.

## Required Reading

Read these pages before you begin — the exercise assumes familiarity with the concepts they cover.

* Amplify Gen 2 getting started: <https://docs.amplify.aws/react/start/quickstart/>
* Vite React guide: <https://vite.dev/guide/>
* Amplify Gen 2 sandbox environments: <https://docs.amplify.aws/react/deploy-and-host/sandbox-environments/setup/>

## Constraints

1. Use **Vite** as the build tool. Do not use any other React scaffolding tool.
2. Use **TypeScript** for both the React app and the Amplify backend definition.
3. The Amplify backend must target the **Gen 2** resource model (`amplify/backend.ts`, `defineBackend`). Do not use the Gen 1 CLI (`amplify init`).
4. Deploy a personal **sandbox** environment (`npx ampx sandbox`) rather than a shared branch environment during development.
5. Add `amplify_outputs.json` and `node_modules/` to `.gitignore` — these are generated artifacts that must not be committed.

## Acceptance Criteria

* [ ] `npm run dev` starts a Vite dev server on `localhost:5173` and renders a default page.
* [ ] `amplify/backend.ts` exists and exports a valid backend definition (even if it defines no resources yet).
* [ ] Running `npx ampx sandbox` deploys without errors and produces `amplify_outputs.json`.
* [ ] The React app loads `amplify_outputs.json` via `Amplify.configure()` at startup without console errors.
* [ ] `.gitignore` excludes `amplify_outputs.json` and `node_modules/`.

## Hints

<details>

<summary>Scaffolding the project</summary>

`npm create vite@latest partner-bot -- --template react-ts` generates the Vite + React + TypeScript starter. From inside that directory, `npm create amplify@latest` initializes the Gen 2 backend structure.

</details>

<details>

<summary>Configuring Amplify in the app entry point</summary>

In `src/main.tsx`, import the generated outputs and call `Amplify.configure()` before rendering the root component:

```tsx
import outputs from "../amplify_outputs.json";
import { Amplify } from "aws-amplify";

Amplify.configure(outputs);
```

Vite resolves JSON imports natively — no loader config needed.

</details>

<details>

<summary>Sandbox lifecycle</summary>

`npx ampx sandbox` watches your `amplify/` directory for changes and hot-deploys them. Press `Ctrl+C` to stop watching; the sandbox resources remain deployed. Run `npx ampx sandbox delete` when you want to tear them down.

</details>

***

Last verified: 2026-06


# Build the Chat Form

## Goal

Compose a query form from Amplify UI components that collects the four fields required by the [Lambda Contract](/reference/contract), submits them to a handler function, and renders the inference response. This page focuses on the UI layer only — connecting to the live Lambda happens in the next page.

## Contract

Your form must produce a request body conforming to the [Lambda Contract](/reference/contract):

```json
{
  "question": "What were the key investments disclosed this quarter?",
  "ticker": "AMZN",
  "year": 2024,
  "period": "Q2"
}
```

The response shape (also defined in the contract) is:

```json
{
  "answer": "...",
  "meta": {}
}
```

Your UI renders the `answer` field to the user.

## Required Reading

* Amplify UI React introduction: <https://ui.docs.amplify.aws/react/getting-started/introduction>
* SelectField: <https://ui.docs.amplify.aws/react/components/selectfield>
* TextField: <https://ui.docs.amplify.aws/react/components/textfield>
* Button: <https://ui.docs.amplify.aws/react/components/button>
* View: <https://ui.docs.amplify.aws/react/components/view>
* [Lambda Contract](/reference/contract)

## Constraints

1. Use **Amplify UI components** (`SelectField`, `TextField`, `Button`, `View`) — do not introduce a separate component library.
2. The company `SelectField` must display human-readable names (e.g., "Apple") but your submit handler must map them to the uppercase stock ticker (`"AAPL"`) before building the request body.
3. The `year` field must submit as an integer, not a string.
4. The `period` field must offer exactly the enum values defined in the contract: `Q1`, `Q2`, `Q3`, `Q4`, `FY`.
5. Do not hardcode a Lambda endpoint in this page. Wire a **stub handler** that logs the request body to the console and returns a fake response. The real integration is the next page's concern.
6. Display a loading indicator while the handler is in flight and an error message if it rejects.

## Acceptance Criteria

* [ ] The form renders four inputs: company (select), year (select), period (select), and question (text).
* [ ] Submitting the form logs a JSON object to the browser console whose shape matches the Lambda Contract request schema.
* [ ] The `ticker` value in the logged object is the mapped stock ticker, not the display name.
* [ ] The `year` value is a number.
* [ ] After submission, the stub response's `answer` field renders in a `View` component below the form.
* [ ] A loading state is visible between submission and response.
* [ ] If the handler throws, an error message appears in the UI.

## Hints

<details>

<summary>Mapping company names to tickers</summary>

Define a mapping object outside your component:

```tsx
const COMPANIES: Record<string, string> = {
  Apple: "AAPL",
  Amazon: "AMZN",
  Microsoft: "MSFT",
};
```

Iterate over its keys to build `SelectField` options, then look up the ticker on submit.

</details>

<details>

<summary>Stub handler pattern</summary>

Create an async function that simulates a network call:

```tsx
async function submitQuery(body: RequestBody): Promise<ResponseBody> {
  console.log("Request:", JSON.stringify(body, null, 2));
  await new Promise((r) => setTimeout(r, 800));
  return { answer: "Stub response — replace with real Lambda call.", meta: {} };
}
```

Replace this function with a real `fetch` call in the next page.

</details>

<details>

<summary>Managing loading and error state</summary>

Use React state to track submission status:

```tsx
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
```

Wrap your handler in a try/catch that sets these values. Amplify UI's `Button` accepts an `isLoading` prop; `View` can conditionally render the error.

</details>

<details>

<summary>Year select — generating a range</summary>

Generate year options dynamically so the form stays current:

```tsx
const currentYear = new Date().getFullYear();
const years = Array.from({ length: 5 }, (_, i) => currentYear - i);
```

</details>

***

Last verified: 2026-06


# Connect to Lambda via API Gateway

## Goal

Replace the stub handler from the previous page with a live connection to your deployed inference Lambda. You will create an API Gateway HTTP API, integrate it with your existing Lambda, configure CORS for your Amplify domains, and call the endpoint from your React app using the Vite environment variable pattern.

## Contract

Your frontend sends a POST request to the API Gateway endpoint. The request body conforms to the [Lambda Contract](/reference/contract):

```json
{
  "question": "What were the key revenue drivers this quarter?",
  "ticker": "MSFT",
  "year": 2024,
  "period": "Q2"
}
```

The Lambda returns a response conforming to the [Lambda Contract](/reference/contract):

```json
{
  "answer": "...",
  "meta": {}
}
```

API Gateway uses **Lambda proxy integration** with **payload format version 2.0**, so your Lambda receives the full HTTP event and must return `statusCode`, `headers`, and `body`.

## Required Reading

* API Gateway HTTP API concepts: <https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api.html>
* Working with HTTP API Lambda integrations: <https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html>
* HTTP API payload format version 2.0: <https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html#http-api-develop-integrations-lambda.proxy-format>
* Configuring CORS for HTTP APIs: <https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-cors.html>
* Vite environment variables: <https://vite.dev/guide/env-and-mode>
* [Lambda Contract](/reference/contract)

## Constraints

1. Use **API Gateway HTTP API** (not REST API) with Lambda proxy integration.
2. Payload format version must be **2.0** — your Lambda receives the event shape documented in the required reading above.
3. CORS allowed origins must include both your local dev URL (`http://localhost:5173`) and your deployed Amplify domain.
4. Store the API endpoint in a Vite environment variable (`VITE_INFERENCE_API`) — do not hardcode URLs in component source.
5. The frontend `fetch` call must send `Content-Type: application/json` and handle non-2xx responses by surfacing an error to the user.
6. Do not add authentication yet — that is the next page's concern. The endpoint is open for now (CORS-restricted only).

## Acceptance Criteria

* [ ] An HTTP API exists in API Gateway with a `POST /inference` route integrated with your Lambda.
* [ ] A curl request to the endpoint with a valid contract-conforming body returns HTTP 200 and a JSON response containing `answer` and `meta`.
* [ ] CORS is configured: a preflight OPTIONS request from your Amplify origin returns the correct `Access-Control-Allow-*` headers.
* [ ] The React app reads `import.meta.env.VITE_INFERENCE_API` for the endpoint URL.
* [ ] Submitting the form in the browser calls the live Lambda and renders the `answer` below the form.
* [ ] A malformed request (missing a required field) returns HTTP 400 and the error displays in the UI.

## Hints

<details>

<summary>API Gateway console vs CLI</summary>

The console workflow is: Create HTTP API, add a Lambda integration (select your function), add route `POST /inference`, create a `prod` stage with auto-deploy, then note the invoke URL.

With the AWS CLI, the key commands are `aws apigatewayv2 create-api`, `create-integration`, `create-route`, and `create-stage`. You also need `aws lambda add-permission` to grant API Gateway invoke access.

</details>

<details>

<summary>Invoke permission</summary>

API Gateway needs explicit permission to invoke your Lambda. The source ARN pattern is:

```
arn:aws:execute-api:{region}:{account}:{api-id}/*/POST/inference
```

Without this, you get a 500 from the gateway even though your route and integration look correct.

</details>

<details>

<summary>Vite environment variable pattern</summary>

Create `.env.local` (for development) and `.env.production` (for the deployed build):

```
VITE_INFERENCE_API=https://abc123.execute-api.us-east-1.amazonaws.com/inference
```

Access it in code as `import.meta.env.VITE_INFERENCE_API`. Vite only exposes variables prefixed with `VITE_`.

</details>

<details>

<summary>Replacing the stub handler</summary>

In the previous page you defined a stub function. Replace it with a real fetch:

```tsx
const response = await fetch(import.meta.env.VITE_INFERENCE_API, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify(payload),
});
```

Parse the response and handle errors as before.

</details>

<details>

<summary>CORS troubleshooting</summary>

If the browser shows a CORS error but curl works, check that your HTTP API CORS configuration includes the exact origin (protocol + host + port). Common issues: missing `http://localhost:5173` in the allowed origins list, or `Authorization` not listed in allowed headers (needed in the next page).

</details>

<details>

<summary>Lambda response format for proxy integration</summary>

With payload format 2.0, your Lambda must return an object with at least `statusCode` and `body` (a JSON string). If you omit `statusCode`, API Gateway returns 500.

</details>

***

Last verified: 2026-06


# Authentication with Cognito

## Goal

Protect your API Gateway endpoint with a Cognito User Pool authorizer so that only signed-in users can invoke the inference Lambda. You will create an edge Lambda that validates payloads, reads JWT claims for caller identity, and invokes your core Lambda via boto3. The React frontend attaches the Cognito ID token using Amplify Gen 2's `fetchAuthSession`.

## Contract

The frontend sends the same [Lambda Contract](/reference/contract) request body as before, but now includes an `Authorization` header containing the Cognito ID token (JWT).

API Gateway verifies the JWT before the request reaches your edge Lambda. The edge Lambda receives the verified claims at:

```
event["requestContext"]["authorizer"]["jwt"]["claims"]
```

The edge Lambda validates the request body, invokes the core Lambda with the contract-conforming payload, and returns the core Lambda's response to the browser.

## Required Reading

* HTTP API JWT authorizers: <https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-jwt-authorizer.html>
* API Gateway HTTP API payload format v2.0 — `requestContext.authorizer`: <https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html#http-api-develop-integrations-lambda.proxy-format>
* Amplify Gen 2 `fetchAuthSession`: <https://docs.amplify.aws/react/build-a-backend/auth/connect-your-frontend/sign-in/#sign-in-with-an-external-identity-provider>
* boto3 Lambda invoke: <https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/lambda/client/invoke.html>
* [Lambda Contract](/reference/contract)

## Constraints

1. Use an **API Gateway JWT authorizer** configured with your Amplify Cognito User Pool — not a Lambda authorizer.
2. The edge Lambda runtime must be **Python 3.12**.
3. The edge Lambda must **validate the request body** against the contract fields (`question`, `ticker`, `year`, `period`) and return HTTP 400 with missing field names if validation fails.
4. The edge Lambda must invoke the core Lambda using boto3's `invoke` with `InvocationType="RequestResponse"`. Check the `FunctionError` key in the response to detect core Lambda failures.
5. The edge Lambda's IAM role must have an inline policy granting `lambda:InvokeFunction` on the core Lambda only — not a wildcard.
6. The frontend must use `fetchAuthSession` from `aws-amplify/auth` to retrieve the ID token, and pass it in the `Authorization` header. Use the Amplify Gen 2 configuration pattern (`amplify_outputs.json`) and Vite environment variables (`import.meta.env.VITE_*`).
7. CORS allowed headers must include `Authorization`.

## Acceptance Criteria

* [ ] A Cognito JWT authorizer is attached to the `POST /inference` route on your HTTP API.
* [ ] An unauthenticated request (no `Authorization` header) returns HTTP 401.
* [ ] A request with a valid token but a malformed body (missing required fields) returns HTTP 400 with the missing field names in the response.
* [ ] A request with a valid token and valid body round-trips through the edge Lambda to the core Lambda and returns the inference answer.
* [ ] The edge Lambda checks for `FunctionError` in the boto3 invoke response and returns HTTP 502 if the core Lambda errored.
* [ ] The React app attaches the ID token from `fetchAuthSession` and successfully calls the protected endpoint when the user is signed in.
* [ ] When the user is not signed in, the app does not attempt the API call (or handles the 401 gracefully in the UI).

## Hints

<details>

<summary>Architecture diagram</summary>

```
[ React + Amplify Authenticator ]
         | fetchAuthSession() → ID token
         v
[ fetch POST /inference, Authorization: <token> ]
         |
         v
[ API Gateway HTTP API ]
   └─ JWT Authorizer (Cognito User Pool)
         |  (token verified → claims injected)
         v
[ Edge Lambda (Python 3.12) ]
   - Reads claims from event.requestContext.authorizer.jwt.claims
   - Validates body fields against the contract
   - Invokes core Lambda via boto3
         |
         v
[ Core Lambda (existing inference logic) ]
         |
         v
[ Response flows back: Edge → API Gateway → Browser ]
```

</details>

<details>

<summary>Creating the JWT authorizer</summary>

In the API Gateway console under Authorizers, create a JWT authorizer. The issuer URL is your Cognito User Pool's URL:

```
https://cognito-idp.{region}.amazonaws.com/{userPoolId}
```

The audience is your User Pool App Client ID. Attach this authorizer to your `POST /inference` route.

</details>

<details>

<summary>Reading claims in the edge Lambda</summary>

With a JWT authorizer on an HTTP API (payload v2.0), verified claims arrive at:

```python
claims = event["requestContext"]["authorizer"]["jwt"]["claims"]
user_sub = claims["sub"]
email = claims.get("email", "")
```

This path is specific to HTTP API v2.0. REST APIs use a different event structure.

</details>

<details>

<summary>Detecting core Lambda errors</summary>

After calling `lambda_client.invoke(...)`, check for the `FunctionError` key:

```python
response = lambda_client.invoke(
    FunctionName=target_name,
    InvocationType="RequestResponse",
    Payload=json.dumps(payload).encode(),
)
if "FunctionError" in response:
    # The core Lambda threw an unhandled exception
    ...
```

Read the `Payload` stream for the error details.

</details>

<details>

<summary>Frontend: attaching the token</summary>

Use the Amplify Gen 2 auth pattern:

```tsx
import { fetchAuthSession } from "aws-amplify/auth";

const { tokens } = await fetchAuthSession();
const idToken = tokens?.idToken?.toString() ?? "";

const response = await fetch(import.meta.env.VITE_INFERENCE_API, {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    Authorization: idToken,
  },
  body: JSON.stringify(payload),
});
```

</details>

<details>

<summary>Security options beyond JWT authorizer (advanced — skim)</summary>

* **Lambda authorizer:** Custom logic (e.g., allow-listed emails, API keys) — more flexible but more code to maintain.
* **IAM auth with federated identities:** Strongest, but adds SigV4 client complexity.
* **Public endpoint:** CORS-limited only; no identity verification. Acceptable for non-sensitive demos but never for production.

</details>

<details>

<summary>Common pitfalls</summary>

* **CORS mismatch after adding auth:** You must add `Authorization` to the `Access-Control-Allow-Headers` list in your HTTP API CORS config. Without it, the browser's preflight fails.
* **Wrong token type:** The JWT authorizer expects the ID token, not the access token. `fetchAuthSession` returns both — use `tokens.idToken`.
* **Edge Lambda timeout:** Set to at least 15 seconds. The core Lambda may take time; ensure the edge Lambda's timeout exceeds the core Lambda's expected duration while staying under API Gateway's 30-second limit.
* **Missing invoke permission:** The edge Lambda's role needs `lambda:InvokeFunction` on the specific core Lambda ARN. A generic Lambda role does not include this.

</details>

***

Last verified: 2026-06


# MCP Module

The Model Context Protocol (MCP) gives LLM-based applications a standardized way to discover and invoke external tools. Instead of hard-wiring each integration, an MCP server exposes capabilities — tools, resources, prompts — through a JSON-RPC interface that any compliant client can consume.

In this module you first experience MCP as a user (connecting a client to a live endpoint), then build your own MCP server that wraps the Lambda functions you deployed in the SEC Lambda and 10Q Inference modules. The result is a tool-equipped endpoint that any MCP-compatible agent can call to retrieve and analyze SEC filings on demand.

## Module structure

| Page                                                                   | Focus                                                                                     |
| ---------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
| [MCP Concepts](/advanced-modules/mcp-module/mcp-concepts)              | Protocol fundamentals, architecture, and a live demo using the course site's MCP endpoint |
| [Project: MCP Server](/advanced-modules/mcp-module/project-mcp-server) | Build an MCP server that wraps your existing Lambda functions                             |

## Prerequisites

* Completed [Project: SEC Lambda (SAM)](/cloud-deployment-sam/project-sec-lambda) — you have a deployed Lambda function conforming to the [Lambda Contract](/reference/contract)
* Completed [Project: 10Q Inference (CDK)](/cloud-deployment-cdk/project-10q-inference) — you have a working inference pipeline
* Python 3.12
* Familiarity with JSON-RPC (covered briefly in [Introduction to APIs](/foundations/introduction-to-apis))

## Why MCP matters

Lambda functions are powerful but isolated. A user must know the exact request schema, invoke the function directly, and parse the response. MCP removes that friction: an LLM client discovers available tools at runtime, understands their parameters from schema metadata, and invokes them without custom glue code.

Building an MCP server around your existing Lambda means any compatible client — Claude Desktop, Cursor, a custom agent — can query SEC filings through your infrastructure without modification.

***

Last verified: 2026-06


# MCP Concepts

The Model Context Protocol (MCP) is an open standard that defines how LLM applications communicate with external data sources and tools. It decouples the client (the LLM application) from the server (the tool provider) through a well-defined JSON-RPC 2.0 interface.

## Core architecture

MCP follows a client-server model:

```
┌──────────────┐         JSON-RPC 2.0         ┌──────────────┐
│  MCP Client  │ ◄──────────────────────────► │  MCP Server  │
│ (LLM app)    │    stdio / HTTP+SSE          │ (tool host)  │
└──────────────┘                              └──────────────┘
```

The **client** is any LLM application that wants to use external tools — Claude Desktop, Cursor, or a custom agent. The **server** exposes capabilities the client can discover and invoke at runtime.

## Transport

MCP supports two transport mechanisms:

* **stdio** — the client spawns the server as a subprocess and communicates over standard input/output. Best for local development and desktop integrations.
* **HTTP with Server-Sent Events (SSE)** — the client connects to the server over HTTP. The server streams responses via SSE. Best for remote deployments and shared services.

Both transports carry identical JSON-RPC 2.0 messages. Your server code stays the same regardless of which transport a client uses.

## Capability types

An MCP server can expose three kinds of capabilities:

### Tools

Tools are functions the LLM can call. Each tool has a name, a description, and an input schema (JSON Schema). The client presents available tools to the LLM, which decides when and how to call them based on the user's request.

Example: a `query_sec_filing` tool that accepts a ticker, year, and period, then returns the filing analysis.

### Resources

Resources are data the client can read. Unlike tools, resources are not invoked — they are fetched. A resource has a URI, a MIME type, and content.

Example: a resource at `sec://filings/AAPL/2024/Q3` that returns the raw 10-Q text.

### Prompts

Prompts are reusable prompt templates the server offers to clients. They let the server suggest how to frame a request, while the client retains control over whether and how to use them.

Example: a `filing_analysis` prompt template that structures a question about quarterly earnings.

## How MCP relates to your Lambda

The Lambda functions you built earlier accept a structured JSON request and return an answer. Wrapping them in an MCP server means:

1. The tool's input schema maps directly to the [Lambda Contract](/reference/contract) request fields (`question`, `ticker`, `year`, `period`).
2. The tool's output is the Lambda response (`answer` and `meta`).
3. Any MCP client can discover and call your filing-analysis capability without knowing the underlying AWS infrastructure.

The Lambda stays unchanged. The MCP server is a thin adapter that translates between the MCP protocol and your existing function.

## Live demo: connect to the course MCP endpoint

Before building your own server, experience MCP from the client side. This course's GitBook site exposes an MCP endpoint that lets you query the course content itself.

### Endpoint

```
https://llm-aws.course.gspivey.com/~gitbook/mcp
```

### Connect with Claude Desktop

1. Open your Claude Desktop configuration file:
   * macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
   * Windows: `%APPDATA%\Claude\claude_desktop_config.json`
2. Add the course endpoint as an MCP server:

```json
{
  "mcpServers": {
    "llm-aws-course": {
      "url": "https://llm-aws.course.gspivey.com/~gitbook/mcp"
    }
  }
}
```

3. Restart Claude Desktop. The course tools appear in the tools menu (the hammer icon).
4. Ask Claude a question that requires course content:

   > "What is the Lambda request schema used in this course?"

   Claude calls the course MCP endpoint, retrieves the relevant content, and answers using the actual course material.

### What to observe

* Claude discovers the available tools automatically — you did not write any integration code.
* The tool call is visible in the response (expand the tool-use block to see the request and response).
* The server returns course content that Claude uses to ground its answer, reducing hallucination.

This is the same pattern you will implement in the project: expose a capability via MCP, and any compatible client can use it without custom wiring.

### Connect with other MCP clients

The endpoint works with any client that supports HTTP+SSE transport. If you use Cursor, VS Code with an MCP extension, or another compatible tool, add the URL as a remote MCP server following that client's configuration documentation.

## Protocol lifecycle

A typical MCP session follows this sequence:

1. **Initialize** — the client sends an `initialize` request; the server responds with its capabilities (which of tools/resources/prompts it supports).
2. **Discover** — the client calls `tools/list`, `resources/list`, or `prompts/list` to enumerate available capabilities.
3. **Invoke** — the client calls `tools/call` with the tool name and arguments; the server executes the tool and returns the result.
4. **Close** — either side terminates the session.

All messages are JSON-RPC 2.0. The client never calls a tool it has not first discovered — discovery is mandatory.

## Key design principles

* **Server declares, client decides.** The server advertises what it can do; the LLM decides whether and when to call a tool.
* **Schema-driven.** Every tool includes a JSON Schema for its inputs. Clients use this to validate arguments before sending them.
* **Stateless tools.** Each tool invocation is independent. The server does not maintain conversational state between calls (though it may cache data internally).
* **Transport-agnostic logic.** Your tool implementation does not depend on whether the client connected via stdio or HTTP.

## Next step

In [Project: MCP Server](/advanced-modules/mcp-module/project-mcp-server) you build an MCP server that exposes your SEC Lambda as a tool, deploy it, and test it with a real client.

***

Last verified: 2026-06


# Project: MCP Server

Build an MCP server that wraps your existing SEC filing Lambda function, making it available as a tool to any MCP-compatible client.

## Goal

Create a Python MCP server that exposes a `query_sec_filing` tool. The tool accepts the same parameters as your Lambda function (conforming to the [Lambda Contract](/reference/contract)), invokes the Lambda via the AWS SDK, and returns the result to the MCP client. When complete, you will be able to point Claude Desktop (or any MCP client) at your server and ask questions about SEC filings — with the Lambda doing the actual work behind the scenes.

## Contract

Your MCP server exposes one tool:

**Tool name:** `query_sec_filing`

**Input schema** (maps to the [Lambda Contract](/reference/contract) request):

| Parameter  | Type    | Description                                    |
| ---------- | ------- | ---------------------------------------------- |
| `question` | string  | The natural-language question to answer        |
| `ticker`   | string  | Stock ticker symbol                            |
| `year`     | integer | Filing year (four digits)                      |
| `period`   | enum    | Filing period: `Q1`, `Q2`, `Q3`, `Q4`, or `FY` |

**Output:** The tool returns the Lambda response `answer` field as text content. The `meta` object is included in a separate metadata block for client inspection.

The server must respond to the standard MCP lifecycle methods: `initialize`, `tools/list`, and `tools/call`.

## Required Reading

* [MCP Concepts](/advanced-modules/mcp-module/mcp-concepts) — protocol architecture, transport, and capability types
* [Lambda Contract](/reference/contract) — the request/response schema your tool wraps
* [Models](/reference/models) — the canonical model your Lambda invokes
* [MCP Specification](https://modelcontextprotocol.io/specification) — the official protocol reference

## Constraints

* Python 3.12.
* Use the `mcp` Python SDK (`pip install mcp`). Do not implement the JSON-RPC protocol from scratch.
* The server must support both **stdio** and **HTTP+SSE** transports. The `mcp` SDK handles this via its built-in server class — you configure it, not implement it.
* Invoke the Lambda function using `boto3`. Do not duplicate the Lambda's logic inside the MCP server — call the deployed function.
* The tool's input schema must match the [Lambda Contract](/reference/contract) request fields exactly. Do not add parameters the Lambda does not accept.
* Validate the `period` parameter against the allowed enum values (`Q1`, `Q2`, `Q3`, `Q4`, `FY`) before invoking the Lambda. Return a clear error through the MCP error mechanism if validation fails.
* The server must include a descriptive `tool.description` that tells the LLM when this tool is appropriate to call.
* Do not hardcode AWS credentials. The server relies on the standard credential chain (`AWS_PROFILE`, environment variables, or instance role).
* Do not hardcode the Lambda function name. Accept it via an environment variable (`SEC_LAMBDA_FUNCTION_NAME`).

## Acceptance Criteria

1. Running the server locally via stdio and calling `tools/list` returns a single tool named `query_sec_filing` with the correct input schema.
2. Calling `tools/call` with valid parameters invokes the Lambda and returns the answer text.
3. Calling `tools/call` with an invalid `period` value returns an MCP error without invoking the Lambda.
4. The server starts in HTTP+SSE mode when configured to do so, and a remote MCP client can connect and invoke the tool.
5. Adding the server to Claude Desktop's configuration (stdio mode) allows Claude to answer SEC filing questions by calling the tool.

## Hints

* The `mcp` SDK provides a `Server` class with decorator-based tool registration. A minimal server needs fewer than 50 lines of application code.
* Use `@server.tool()` to register your tool function. The decorator reads the function's type hints and docstring to generate the JSON Schema and description automatically.
* For the Lambda invocation, use `boto3.client("lambda").invoke(...)` with `InvocationType="RequestResponse"`. Parse the response payload as JSON.
* The `FunctionError` field in the Lambda invoke response indicates the function raised an exception. Check for it before parsing the payload — if present, return an MCP tool error rather than a malformed result.
* To run in stdio mode: `python server.py` (the SDK's `server.run()` defaults to stdio).
* To run in HTTP+SSE mode: `python server.py --transport sse --port 8080`. The `mcp` SDK's CLI runner handles this flag.
* Test your tool in isolation first with `mcp dev server.py` — this launches an interactive inspector that lets you call tools without configuring a full client.
* Structure your project directory alongside your CDK stack or as a standalone package — either works. The server is a thin wrapper, not a large application.

***

Last verified: 2026-06


# LangChain and RAG

The 10Q Inference module proved that injecting an entire filing into the prompt produces grounded answers. That approach works for short documents, but a full 10-K filing runs to hundreds of pages — far exceeding any model's context window. Retrieval Augmented Generation (RAG) solves this by retrieving only the relevant passages before prompting.

This module teaches you to build a RAG pipeline using LangChain on AWS. You will chunk SEC filings into manageable pieces, embed them into a vector store, and retrieve only the passages that are relevant to a given question. The retrieved context feeds into the same inference pattern you built earlier — grounding the model's answer in real data without exhausting its context window.

## Module structure

| Page                                                                              | Focus                                                                                 |
| --------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |
| [RAG Concepts](/advanced-modules/langchain-and-rag/rag-concepts)                  | Chunking, embedding, vector stores, retrieval, and LangChain overview                 |
| [Project: RAG Pipeline](/advanced-modules/langchain-and-rag/project-rag-pipeline) | Build a chunked-and-embedded retrieval pipeline on top of the 10Q Inference data flow |

## Prerequisites

* Completed [Project: 10Q Inference (CDK)](/cloud-deployment-cdk/project-10q-inference) — you have a working inference pipeline that retrieves filings and prompts the model with context
* Completed [MCP Module](/advanced-modules/mcp-module) — familiarity with wrapping Lambda functions as external tools
* Bedrock model access enabled per [Setup: Bedrock Access](/reference/setup-bedrock-access)
* Python 3.12

## Why RAG

Stuffing the full document into the prompt is fragile:

* **Token limits.** Large filings exceed the model's context window, causing truncation or rejection.
* **Cost.** Every input token is billed. Sending 200 pages when you need two paragraphs wastes money.
* **Noise.** Irrelevant sections dilute the model's attention, degrading answer quality.

RAG addresses all three by selecting only the passages most likely to contain the answer. The model sees less text, produces more focused answers, and costs less per invocation.

## Key references

* [Models](/reference/models) — the canonical model identifier for all Bedrock calls
* [Lambda Contract](/reference/contract) — the request/response schema your pipeline must conform to

***

Last verified: 2026-06


# RAG Concepts

Retrieval Augmented Generation (RAG) is a pattern that combines information retrieval with LLM prompting. Instead of relying on the model's training data alone, a RAG system fetches relevant documents at query time and injects them as context — the same principle you used in the 10Q Inference module, now scaled to handle arbitrarily large document collections.

## The RAG pipeline at a glance

```
┌──────────┐     ┌──────────┐     ┌──────────────┐     ┌───────────┐
│  Ingest  │ ──► │  Chunk   │ ──► │   Embed &    │ ──► │  Vector   │
│  (docs)  │     │  (split) │     │   Store      │     │  Store    │
└──────────┘     └──────────┘     └──────────────┘     └─────┬─────┘
                                                              │
                                                         query time
                                                              │
┌──────────┐     ┌──────────────┐     ┌──────────┐          │
│  Answer  │ ◄── │  Prompt +    │ ◄── │ Retrieve │ ◄────────┘
│  (LLM)   │     │  Context     │     │ (search) │
└──────────┘     └──────────────┘     └──────────┘
```

Two phases:

1. **Ingestion** — documents are split into chunks, each chunk is converted to a numerical vector (embedding), and those vectors are stored in a searchable index.
2. **Query** — the user's question is embedded using the same model, the vector store returns the most similar chunks, and those chunks are injected as context into the LLM prompt.

## Chunking

A 10-Q filing is a single large document. Feeding it whole to an embedding model is impractical (embedding models have their own token limits) and unhelpful (a single vector for a 100-page document captures no fine-grained meaning). Chunking splits the document into smaller, semantically coherent pieces.

### Chunking strategies

| Strategy            | How it works                                                                                                   | When to use                                            |
| ------------------- | -------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------ |
| Fixed-size          | Split every N characters or tokens                                                                             | Simple baseline; works when structure is uniform       |
| Recursive character | Split on paragraph breaks, then sentences, then characters — progressively smaller separators until chunks fit | Good default for prose documents                       |
| Section-based       | Split on document headings or HTML tags                                                                        | Works when the document has reliable structural markup |
| Semantic            | Use an embedding model to detect topic boundaries                                                              | Expensive but produces the most coherent chunks        |

For SEC filings, recursive character splitting with overlap works well. Filings are structured prose with inconsistent heading levels, so heading-based splitting is unreliable.

### Overlap

Adjacent chunks share a small overlap (typically 10-20% of chunk size) so that information spanning a chunk boundary is not lost. If a key sentence sits at the boundary between chunk 47 and chunk 48, overlap ensures at least one of them contains the full sentence.

### Chunk size tradeoffs

* **Too small** — each chunk lacks context, retrieval returns fragments that are hard to interpret.
* **Too large** — each chunk mixes topics, retrieval returns noise alongside signal. Embedding quality degrades as the text diverges from a single idea.
* **Practical range** — 500 to 1500 characters (roughly 100 to 400 tokens) per chunk for most RAG applications.

## Embedding

An embedding model converts text into a fixed-length numerical vector that captures semantic meaning. Texts with similar meaning produce vectors that are close together in the embedding space.

### How embeddings work in RAG

1. Each chunk is passed through the embedding model to produce a vector (e.g., 1024 dimensions).
2. The vectors are stored in a vector database alongside the original chunk text.
3. At query time, the user's question is embedded with the same model.
4. The vector store finds the stored chunks whose vectors are closest to the query vector.

### Amazon Bedrock embeddings

Amazon Titan Embeddings is available through Bedrock and produces vectors suitable for RAG. You call it the same way you call a text model — via the Bedrock Runtime API — but the response is a vector rather than generated text.

*(advanced -- skim)* Other embedding models (Cohere Embed, open-source models via SageMaker) work the same way conceptually. The choice of embedding model affects vector dimensionality and retrieval quality, but the pipeline architecture stays identical.

## Vector stores

A vector store is a database optimized for similarity search over high-dimensional vectors. Unlike a traditional database that matches exact values, a vector store finds the N vectors closest to a query vector using distance metrics (cosine similarity, Euclidean distance, or dot product).

### Options on AWS

| Store                                 | Type                 | When to use                                                                          |
| ------------------------------------- | -------------------- | ------------------------------------------------------------------------------------ |
| FAISS (in-memory)                     | Library              | Local development, small corpora, Lambda functions with predictable data size        |
| OpenSearch Serverless (vector engine) | Managed service      | Production workloads needing scalable, persistent vector search                      |
| PostgreSQL + pgvector                 | Managed (RDS/Aurora) | Teams already running PostgreSQL who want to add vector search without a new service |

For this course project, FAISS is sufficient. The filing corpus is small enough to fit in Lambda memory, and FAISS requires no additional infrastructure. A production system handling thousands of filings would move to OpenSearch Serverless.

### Indexing

When you ingest a document:

1. Chunk the text.
2. Embed each chunk.
3. Insert each (vector, chunk\_text, metadata) tuple into the vector store.

Metadata typically includes the source file name, chunk index, and any structural tags (section heading, page number) that help the user trace an answer back to its origin.

### Retrieval

When a user asks a question:

1. Embed the question using the same embedding model.
2. Query the vector store for the top-K nearest vectors (K is typically 3 to 10).
3. Return the associated chunk texts as context for the LLM prompt.

The retrieved chunks are the bridge between the user's question and the answer. The LLM sees only these chunks plus the question — not the full document.

## Retrieval quality

Retrieval quality directly determines answer quality. If the retriever returns irrelevant chunks, the LLM produces an irrelevant answer no matter how capable it is.

Factors that affect retrieval quality:

* **Chunk granularity** — chunks too large dilute the embedding; chunks too small lose context.
* **Embedding model quality** — better models produce embeddings where semantic similarity correlates with topical relevance.
* **Query formulation** — the raw user question may not embed well. Techniques like query expansion or hypothetical document embedding (HyDE) can improve retrieval.
* **Top-K selection** — too few results may miss the answer; too many results add noise to the prompt.

## LangChain overview

LangChain is a Python framework that provides composable abstractions for building LLM-powered applications. It is not required to build a RAG pipeline — you could wire up chunking, embedding, vector search, and prompting yourself — but it reduces boilerplate by providing standard interfaces for each step.

### Core abstractions relevant to RAG

| Abstraction    | Role in the pipeline                                                     |
| -------------- | ------------------------------------------------------------------------ |
| `Document`     | A chunk of text plus metadata                                            |
| `TextSplitter` | Splits raw text into `Document` objects using a chunking strategy        |
| `Embeddings`   | Interface to an embedding model (wraps Bedrock, OpenAI, or local models) |
| `VectorStore`  | Interface to a vector database (wraps FAISS, OpenSearch, pgvector, etc.) |
| `Retriever`    | Queries the vector store and returns relevant documents                  |
| `Chain`        | Connects retriever output to an LLM prompt and returns the final answer  |

### Why LangChain for this project

* **Bedrock integration** — `langchain-aws` provides `ChatBedrock` and `BedrockEmbeddings` classes that handle the Bedrock API details.
* **Swappable components** — switch from FAISS to OpenSearch by changing one class instantiation. The rest of the pipeline stays unchanged.
* **Standardized retrieval interface** — the `Retriever` abstraction means your chain code does not depend on the vector store implementation.

### LangChain installation

LangChain uses a modular package structure. For a Bedrock-based RAG pipeline, install:

```bash
pip install langchain langchain-aws langchain-community faiss-cpu
```

* `langchain` — core framework (chains, prompts, document loaders)
* `langchain-aws` — Bedrock LLM and embedding integrations
* `langchain-community` — community-maintained integrations including FAISS vector store
* `faiss-cpu` — the FAISS library itself (CPU-only build, sufficient for Lambda)

## Connecting RAG to the 10Q Inference pipeline

Your 10Q Inference module already:

1. Retrieves a 10-Q filing from SEC EDGAR.
2. Extracts text from the filing.
3. Builds an enhanced prompt with the filing text as context.
4. Invokes the model and returns an answer conforming to the [Lambda Contract](/reference/contract).

The RAG enhancement replaces step 3's "stuff the whole document" approach with a targeted retrieval:

1. Retrieve the filing (unchanged).
2. Extract text (unchanged).
3. **Chunk** the extracted text into passages.
4. **Embed** each chunk and store in a vector index.
5. **Retrieve** the top-K chunks most relevant to the user's question.
6. Build the prompt using only the retrieved chunks as context.
7. Invoke the model and return the answer (unchanged contract).

The external interface — the [Lambda Contract](/reference/contract) request and response — does not change. The improvement is internal: better answers from less context, at lower cost.

## Next step

In [Project: RAG Pipeline](/advanced-modules/langchain-and-rag/project-rag-pipeline) you implement this pipeline end-to-end using LangChain and FAISS, deployed via CDK.

***

Last verified: 2026-06


# Project: RAG Pipeline

Build a retrieval-augmented generation pipeline that chunks and embeds SEC filing text, retrieves relevant passages at query time, and produces grounded answers — improving on the full-document approach from the 10Q Inference module.

## Goal

Create a CDK-deployed Lambda function that accepts the same request as your 10Q Inference Lambda (conforming to the [Lambda Contract](/reference/contract)) but uses a RAG approach internally. Instead of stuffing the entire filing into the prompt, your function chunks the filing text, embeds the chunks, retrieves the most relevant passages for the user's question, and prompts the model with only those passages as context. The external contract is identical; the internal strategy is smarter.

## Contract

Your RAG Lambda conforms to the [Lambda Contract](/reference/contract):

**Input:** `question`, `ticker`, `year`, `period`

**Output:** `answer`, `meta`

The `meta` object should additionally include:

| Field              | Type    | Description                                          |
| ------------------ | ------- | ---------------------------------------------------- |
| `chunks_retrieved` | integer | Number of chunks used as context                     |
| `chunk_strategy`   | string  | Chunking method used (e.g., `"recursive_character"`) |

These extra fields are informational and do not break contract conformance (the contract specifies `meta` as an object without restricting its keys).

## Required Reading

* [RAG Concepts](/advanced-modules/langchain-and-rag/rag-concepts) — chunking, embedding, vector stores, and retrieval fundamentals
* [Lambda Contract](/reference/contract) — the request/response schema your Lambda must conform to
* [Models](/reference/models) — the canonical model identifier for Bedrock invocations
* [Project: 10Q Inference (CDK)](/cloud-deployment-cdk/project-10q-inference) — the existing pipeline your RAG version builds on
* [LangChain documentation: Retrieval](https://python.langchain.com/docs/concepts/retrieval/) — the official retrieval abstractions reference

## Constraints

* Python 3.12.
* Deploy with **AWS CDK** (Python). This Lambda lives in the same CDK app as your 10Q Inference stack, or a new stack within the same app.
* Use **LangChain** (`langchain`, `langchain-aws`, `langchain-community`) for the retrieval chain. Do not re-implement vector search from scratch.
* Use **FAISS** (`faiss-cpu`) as the vector store. It runs in-memory within the Lambda — no additional infrastructure required.
* Use **Amazon Titan Embeddings** via Bedrock for embedding (accessed through `langchain-aws`'s `BedrockEmbeddings` class). Do not use a self-hosted embedding model.
* Use the canonical model from [Models](/reference/models) for the generation step (accessed through `langchain-aws`'s `ChatBedrock` class).
* Chunk size: between 500 and 1500 characters, with 10-20% overlap. The exact values are your design choice.
* Retrieve the top-K chunks where K is between 3 and 8. Again, your choice — but document it.
* The Lambda must retrieve the filing from SEC EDGAR at invocation time (reuse your EDGAR library from earlier modules). Do not pre-index filings into a persistent store.
* Do not hardcode model identifiers. Use environment variables for the model ID and embedding model ID, configured in CDK.
* The Lambda function must include a custom `User-Agent` header on all SEC EDGAR requests.
* Do not include complete solution code. Build incrementally using the hints.

## Acceptance Criteria

1. The Lambda accepts a request conforming to the [Lambda Contract](/reference/contract) and returns a valid response.
2. The filing text is chunked (not passed whole) before being used as context.
3. Only the top-K most relevant chunks appear in the prompt sent to the model.
4. The `meta` field in the response includes `chunks_retrieved` and `chunk_strategy`.
5. Invoking the Lambda with the same question and filing produces a more focused answer than the full-document approach (qualitative — compare the two side by side).
6. The function deploys via `cdk deploy` without manual steps beyond what CDK handles.
7. Invalid `period` values return a validation error without invoking the model.

## Hints

* Start from your 10Q Inference Lambda. The filing retrieval and text extraction steps are identical. You are replacing the "build prompt" step with a retrieval chain.
* LangChain's `RecursiveCharacterTextSplitter` handles chunking with overlap in a single call. Instantiate it with your chosen `chunk_size` and `chunk_overlap`.
* `BedrockEmbeddings` from `langchain-aws` wraps the Titan Embeddings model. You only need to supply the model ID and a `boto3` client.
* `FAISS.from_documents(chunks, embeddings)` builds the vector index in one line. The index lives in Lambda memory — no persistence needed since the filing is fetched fresh on each invocation.
* Use the retriever interface: `retriever = vector_store.as_retriever(search_kwargs={"k": your_k})`. Then `retriever.invoke(question)` returns the relevant documents.
* Build the final prompt by joining the retrieved chunk texts with newlines, then wrapping them in a system message that instructs the model to answer using only the provided context.
* For the CDK stack, bundle your Lambda dependencies using a `requirements.txt` that includes `langchain`, `langchain-aws`, `langchain-community`, `faiss-cpu`, and `requests`. CDK's `PythonFunction` construct (from `aws-cdk.aws-lambda-python-alpha`) handles `pip install` during deployment.
* Test locally with `sam local invoke` or a simple Python script that calls your handler function directly with a test event. Compare the output against your original 10Q Inference Lambda to confirm the RAG version produces tighter, more relevant answers.
* FAISS index construction takes a few seconds for a typical 10-Q (\~50-100 chunks). This is acceptable for a learning project. A production system would pre-index filings and persist the index.

***

Last verified: 2026-06


# Models

This page is the single source of truth for LLM model identifiers used throughout the course. Every project page links here rather than embedding a model ID directly.

## Canonical Model

| Field               | Value                                                                                                  |
| ------------------- | ------------------------------------------------------------------------------------------------------ |
| Model               | Claude Sonnet 4.5                                                                                      |
| Provider            | Anthropic via Amazon Bedrock                                                                           |
| Deployment          | Cross-Region Inference Profile                                                                         |
| Model ID            | `us.anthropic.claude-sonnet-4-5-20250514-v1:0`                                                         |
| Profile ARN pattern | `arn:aws:bedrock:{region}:{account-id}:inference-profile/us.anthropic.claude-sonnet-4-5-20250514-v1:0` |

## Cross-Region Inference Profile

A Cross-Region Inference Profile routes Bedrock requests across multiple AWS regions transparently. This improves availability and reduces throttling during peak usage without requiring application-level retry logic.

When you invoke the model, you call the inference profile ARN rather than the base model ARN. Bedrock handles region selection behind the scenes.

### Setting up the profile

1. Open the Amazon Bedrock console in your primary region (e.g., `us-east-1`).
2. Navigate to **Cross-region inference** in the left sidebar.
3. Locate the Claude Sonnet 4.5 profile and note its ARN.

Use this ARN in your Lambda function's `model_id` parameter and in any CDK or SAM configuration that references the model.

### Example: invoking the model in Python 3.12

```python
import boto3
import json

bedrock = boto3.client("bedrock-runtime")

response = bedrock.invoke_model(
    modelId="us.anthropic.claude-sonnet-4-5-20250514-v1:0",
    contentType="application/json",
    accept="application/json",
    body=json.dumps({
        "anthropic_version": "bedrock-2023-05-31",
        "max_tokens": 1024,
        "messages": [
            {"role": "user", "content": "Summarize the quarterly results."}
        ]
    })
)
```

## Updating the canonical model

When the course adopts a newer model, update only this page. All other pages link here, so a single edit propagates the change across the course.

1. Update the table above with the new model ID, ARN pattern, and name.
2. Update the Python example to reflect the new `modelId` value.
3. Run the grep-gate to confirm no other page embeds the old or new ID directly.

***

Last verified: 2026-06


# Lambda Contract

This page defines the canonical request and response schema for the course Lambda function. Every project page that specifies Lambda input or output links here and conforms to this schema.

## Request Schema

The Lambda function accepts a JSON object with the following fields:

| Field      | Type    | Required | Description                                              |
| ---------- | ------- | -------- | -------------------------------------------------------- |
| `question` | string  | yes      | The natural-language question to answer                  |
| `ticker`   | string  | yes      | Stock ticker symbol (e.g., `"AAPL"`)                     |
| `year`     | integer | yes      | Filing year in four-digit format (e.g., `2024`)          |
| `period`   | enum    | yes      | Filing period: `"Q1"`, `"Q2"`, `"Q3"`, `"Q4"`, or `"FY"` |

### Field rules

* `question` must be a non-empty string.
* `ticker` must be a non-empty string containing only uppercase letters.
* `year` must be a four-digit integer (1900 or later).
* `period` must be exactly one of: `Q1`, `Q2`, `Q3`, `Q4`, `FY`.

## Response Schema

The Lambda function returns a JSON object with the following fields:

| Field    | Type   | Description                                                      |
| -------- | ------ | ---------------------------------------------------------------- |
| `answer` | string | The LLM-generated answer text                                    |
| `meta`   | object | Metadata about the invocation (model used, token count, latency) |

## Valid Example

A well-formed request and its corresponding response:

**Request:**

```json
{
  "question": "What was the revenue growth compared to the prior quarter?",
  "ticker": "AAPL",
  "year": 2024,
  "period": "Q3"
}
```

**Response:**

```json
{
  "answer": "Based on the 10-Q filing, revenue grew 5% compared to the prior quarter, driven primarily by services segment growth.",
  "meta": {
    "model": "the canonical model from reference/models.md",
    "input_tokens": 1847,
    "output_tokens": 312,
    "latency_ms": 2150
  }
}
```

## Invalid Example

A malformed request and its expected validation error:

**Request (invalid):**

```json
{
  "question": "What were the earnings?",
  "ticker": "AAPL",
  "year": 2024,
  "period": "Annual"
}
```

**Validation error:**

```json
{
  "error": "ValidationError",
  "message": "Invalid value for 'period': 'Annual'. Must be one of: Q1, Q2, Q3, Q4, FY."
}
```

The `period` field accepts only the five enumerated values. `"Annual"` is not valid; use `"FY"` for full-year filings.


# Conventions & Glossary

## Naming conventions

These conventions apply across all course projects. Following them keeps your code consistent with examples and makes debugging easier.

### Python

| Item                          | Convention              | Example                   |
| ----------------------------- | ----------------------- | ------------------------- |
| Module/file names             | `snake_case`            | `cik_lookup.py`           |
| Function names                | `snake_case`            | `get_company_filings`     |
| Class names                   | `PascalCase`            | `EdgarClient`             |
| Constants                     | `UPPER_SNAKE_CASE`      | `BASE_URL`                |
| Virtual environment directory | `venv/` at project root | `python3.12 -m venv venv` |

### AWS resources

| Item                       | Convention                          | Example                     |
| -------------------------- | ----------------------------------- | --------------------------- |
| S3 bucket names            | lowercase, hyphens, globally unique | `sec-filings-yourname-2026` |
| Lambda function names      | kebab-case with project prefix      | `sec-lambda-fetch-filing`   |
| CDK stack names            | PascalCase                          | `TenQInferenceStack`        |
| CloudFormation stack names | PascalCase (generated by CDK)       | `TenQInferenceStack`        |
| IAM role names             | PascalCase with suffix              | `SecLambdaExecutionRole`    |

### Repository and files

| Item                      | Convention                           | Example                                 |
| ------------------------- | ------------------------------------ | --------------------------------------- |
| Branch names              | `feature/short-description`          | `feature/add-error-handling`            |
| Commit messages           | imperative mood, under 72 characters | `Add User-Agent header to SEC requests` |
| Markdown file names       | kebab-case                           | `lambda-project-setup.md`               |
| Directory names (modules) | kebab-case                           | `project-sec-lambda/`                   |

## Python version

All course projects use **Python 3.12**. This applies to local development, Lambda runtime configuration, CDK project setup, and SAM templates.

## User-Agent header

Every HTTP request to SEC EDGAR must include a User-Agent header identifying you, per the SEC Fair Access policy. Use the format:

```
CompanyName YourEmail
```

For a student context:

```
CourseName your.email@university.edu
```

## Glossary

| Term                               | Definition                                                                                                                  |
| ---------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| **10-K**                           | An annual report filed by public companies with the SEC. Contains audited financial statements.                             |
| **10-Q**                           | A quarterly report filed by public companies with the SEC. Contains unaudited financial statements for one quarter.         |
| **ARN**                            | Amazon Resource Name. A unique identifier for any AWS resource.                                                             |
| **Bedrock**                        | AWS managed service for accessing foundation models (LLMs) via API.                                                         |
| **CDK**                            | AWS Cloud Development Kit. An infrastructure-as-code framework that uses Python (in this course) to define cloud resources. |
| **CIK**                            | Central Index Key. A 10-digit numeric identifier the SEC assigns to every filing entity.                                    |
| **Converse API**                   | A Bedrock API that provides a unified interface across models. Alternative to `InvokeModel`.                                |
| **Cross-Region Inference Profile** | A Bedrock feature that routes model requests across regions for higher availability and throughput.                         |
| **EDGAR**                          | Electronic Data Gathering, Analysis, and Retrieval. The SEC's system for receiving, processing, and distributing filings.   |
| **IAM**                            | Identity and Access Management. AWS service for controlling who can do what in your account.                                |
| **InvokeModel**                    | The Bedrock API call used to send a prompt to a model and receive a response.                                               |
| **Lambda**                         | AWS Lambda. A serverless compute service that runs your code in response to events.                                         |
| **Layer (Lambda)**                 | A .zip archive containing libraries or dependencies, attached to a Lambda function.                                         |
| **MCP**                            | Model Context Protocol. A standard for exposing tools and resources to LLM-based agents.                                    |
| **RAG**                            | Retrieval Augmented Generation. A pattern that supplements an LLM prompt with retrieved context documents.                  |
| **SAM**                            | AWS Serverless Application Model. A CLI and template format for defining and deploying serverless applications.             |
| **Ticker**                         | A stock symbol identifying a publicly traded company (e.g., `AAPL` for Apple).                                              |


# Setup: AWS Account

This page walks through creating an AWS account, setting up an IAM user for programmatic access, and configuring the AWS CLI on your machine. Complete these steps before starting the Cloud Deployment modules.

## Create an AWS account

1. Go to <https://aws.amazon.com/> and choose **Create an AWS Account**.
2. Provide an email address, account name, and password for the root user.
3. Enter payment information. AWS requires a valid payment method even for Free Tier usage.
4. Verify your identity via phone or text.
5. Select the **Basic (Free)** support plan unless your organization requires otherwise.

After creation, sign in to the AWS Management Console using the root user email and password.

## Create an IAM user

The root user has unrestricted access to every service and billing setting. Day-to-day work should use an IAM user with scoped permissions.

1. Open the **IAM** console: search for "IAM" in the console search bar.
2. In the left sidebar, choose **Users**, then **Create user**.
3. Enter a username (e.g., `course-dev`).
4. On the permissions page, choose **Attach policies directly** and attach `AdministratorAccess`. For a learning environment this is acceptable; production accounts should use least-privilege policies.
5. Complete the wizard and note the user's ARN.

### Create an access key

1. From the IAM user's **Security credentials** tab, choose **Create access key**.
2. Select **Command Line Interface (CLI)** as the use case.
3. Copy the **Access key ID** and **Secret access key**. Store them securely — the secret is shown only once.

## Install the AWS CLI

Install version 2 of the AWS CLI. Follow the instructions for your operating system:

* **macOS**: `brew install awscli` or download the `.pkg` installer from the [AWS CLI install page](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html).
* **Linux / WSL**: download and run the bundled installer per the install page linked above.
* **Windows**: download the `.msi` installer from the same page.

Verify the installation:

```bash
aws --version
```

You should see output starting with `aws-cli/2.x.x`.

## Configure credentials

Run the configure command and enter the access key ID and secret from the previous step:

```bash
aws configure
```

When prompted:

| Prompt                | Value                                  |
| --------------------- | -------------------------------------- |
| AWS Access Key ID     | Your access key ID                     |
| AWS Secret Access Key | Your secret access key                 |
| Default region name   | `us-east-1` (or your preferred region) |
| Default output format | `json`                                 |

This writes credentials to `~/.aws/credentials` and config to `~/.aws/config`. The AWS CLI and boto3 (used in Python 3.12 Lambda functions) both read from these files automatically.

### Verify access

```bash
aws sts get-caller-identity
```

A successful response returns your account ID, user ARN, and user ID.

## Region selection

This course uses `us-east-1` in examples. Bedrock model availability varies by region — see [Setup: Bedrock Access](/reference/setup-bedrock-access) for details. If you choose a different region, use it consistently across all projects.

***

Last verified: 2026-06


# Setup: Bedrock Access

Amazon Bedrock requires you to explicitly enable access to each model before you can invoke it. This page covers enabling model access for the course's canonical model. For the model identifier and invocation details, see [Models](/reference/models).

## Enable model access

1. Open the **Amazon Bedrock** console in your chosen region (e.g., `us-east-1`).
2. In the left sidebar, choose **Model access**.
3. Choose **Modify model access**.
4. Locate **Anthropic** in the provider list and check the box for the canonical model listed on the [Models](/reference/models) page.
5. Choose **Next**, review the selection, and choose **Submit**.

Access typically activates within a few minutes. The status column changes from "Available to request" to "Access granted."

## Verify access

Use the AWS CLI to confirm the model responds. Replace `MODEL_ID` below with the canonical model ID from [Models](/reference/models):

```bash
aws bedrock-runtime invoke-model \
  --model-id "MODEL_ID" \
  --content-type "application/json" \
  --accept "application/json" \
  --body '{"anthropic_version":"bedrock-2023-05-31","max_tokens":64,"messages":[{"role":"user","content":"Say hello."}]}' \
  /dev/stdout
```

A successful response returns a JSON body with the model's reply. If you receive `AccessDeniedException`, model access has not finished activating or you selected the wrong model in the console.

## Cross-Region Inference

The course uses a Cross-Region Inference Profile, which routes requests across multiple regions for availability. The profile is preconfigured by AWS once you enable the model — no additional setup is required on your part. See [Models](/reference/models) for the profile ARN pattern and usage instructions.

## Region availability

Not all Bedrock models are available in every region. At the time of writing, the canonical course model is available in `us-east-1`, `us-west-2`, and `eu-west-1` among others. If you configured a different default region in [Setup: AWS Account](/reference/setup-aws-account), confirm model availability in the Bedrock console before proceeding.

## Troubleshooting

| Symptom                                                         | Cause                               | Fix                                                                           |
| --------------------------------------------------------------- | ----------------------------------- | ----------------------------------------------------------------------------- |
| `AccessDeniedException`                                         | Model access not granted            | Check the Model access page; request access if status is not "Access granted" |
| `ValidationException: The provided model identifier is invalid` | Incorrect model ID or model retired | Confirm the model ID matches the one on [Models](/reference/models)           |
| `ThrottlingException`                                           | Too many requests in a short window | The Cross-Region Inference Profile mitigates this; retry after a brief pause  |

***

Last verified: 2026-06


# AWS Services Reference

This page covers the AWS services used in this course. Each service is introduced briefly here; you will work with them hands-on in the project modules that follow.

## Amazon S3

Amazon S3 (Simple Storage Service) is an object storage service for storing and retrieving any amount of data. In this course, S3 holds SEC filing documents that your Lambda functions read at inference time.

Open-source alternative: [MinIO](https://min.io/) provides an S3-compatible object storage API you can run locally for development.

## AWS Lambda

AWS Lambda is a serverless compute service that runs your code in response to events without requiring you to provision servers. You write a handler function, deploy it, and Lambda executes it when triggered. This course builds multiple Lambda functions — starting with SAM CLI deployments and transitioning to CDK.

Open-source alternative: [Apache OpenWhisk](https://openwhisk.apache.org/) provides a serverless platform you can self-host.

## Amazon API Gateway

Amazon API Gateway creates, deploys, and manages HTTP APIs that front your Lambda functions. In the Partner Bot Web Page module, API Gateway exposes your inference Lambda as a REST endpoint the front-end application calls.

Open-source alternative: [Kong](https://konghq.com/) offers API gateway and management features with a self-hosted option.

## Amazon Bedrock

Amazon Bedrock is a managed service providing API access to large language models from multiple providers. This course uses Bedrock to invoke the canonical model (see [Models](/reference/models)) for inference. Bedrock handles hosting and scaling; you interact with it through the AWS SDK.

Open-source alternative: [Hugging Face Inference Endpoints](https://huggingface.co/inference-endpoints) lets you deploy open-weight models behind an API, though you manage capacity yourself.

## Amazon EventBridge

Amazon EventBridge is a serverless event bus that routes events between AWS services on a schedule or in response to state changes. In this course, an EventBridge rule triggers the daily SEC filing refresh Lambda on a cron schedule.

Open-source alternative: [Temporal](https://temporal.io/) provides durable workflow orchestration including scheduled triggers.

## Amazon Cognito

Amazon Cognito handles user authentication and authorization. In the Partner Bot Web Page module, Cognito protects the API Gateway endpoint so only authenticated users can invoke the inference Lambda.

Open-source alternative: [Keycloak](https://www.keycloak.org/) is a self-hosted identity and access management server supporting OAuth 2.0 and OpenID Connect.

***

Last verified: 2026-06


