Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
687b313
initial boilerplate code with helpers
neagualexa Jan 22, 2025
3c14c98
update workflows
neagualexa Jan 22, 2025
fb65a5c
fix: tests & simplify call agent
neagualexa Jan 22, 2025
7ec827b
fix: cache workflow test env
neagualexa Jan 22, 2025
07303da
fix: update docker ignore
neagualexa Jan 22, 2025
b373f03
comments removed
neagualexa Jan 24, 2025
85d4ef9
fix: cached steps
neagualexa Jan 24, 2025
6c3eb67
fix: remove cached as venv pytest error
neagualexa Jan 24, 2025
9b500ab
fix: secrets to workflow
neagualexa Jan 24, 2025
63d63c3
fix: token as input to workflow
neagualexa Jan 24, 2025
444c663
revert to passing secret
neagualexa Jan 27, 2025
38ffd30
fix: provide permission
neagualexa Jan 27, 2025
c8f1a49
github actions interface workflow trigger option
neagualexa Jan 28, 2025
1fb54ba
types index
neagualexa Jan 28, 2025
8d3417b
fix: load json
neagualexa Jan 28, 2025
14375b4
fix: handler input and tests
neagualexa Jan 28, 2025
aec05e4
fix: body event type
neagualexa Jan 28, 2025
731b9c2
script to test url requests
neagualexa Jan 29, 2025
6d84151
debugging prints
neagualexa Jan 29, 2025
fb7656e
return stringified
neagualexa Jan 29, 2025
aa58f0f
handler return dict
neagualexa Jan 29, 2025
e6c9bf1
fix print statements
neagualexa Jan 29, 2025
7dedfed
feat: introduce gemini calls
neagualexa Feb 10, 2025
e13f95d
Set Env variables + prompt improvements (#10)
neagualexa Feb 18, 2025
c98af4b
feat: use lf_toolkit schema
neagualexa Feb 18, 2025
93ca2d6
fix: set and question numbers (#13)
neagualexa Feb 19, 2025
fa9f091
fix: add env vars main
neagualexa Feb 19, 2025
0f98167
remove some env vars
neagualexa Feb 19, 2025
24379ed
test: no env vars
neagualexa Feb 20, 2025
6c02a74
test: with env vars
neagualexa Feb 20, 2025
c890fe4
fix: environment variables sent
neagualexa Feb 20, 2025
f8bfda9
fix: ednge case optional response areas
neagualexa Mar 7, 2025
fef5d57
up to date with python13, latest context parser
neagualexa Sep 22, 2025
d1c04c8
test chat function
neagualexa Sep 22, 2025
b367780
use python13 for actions (#21)
neagualexa Sep 22, 2025
cf6527a
Use python13 (#22)
neagualexa Sep 22, 2025
60fc265
fix use of lf_toolkit
neagualexa Sep 22, 2025
37a816e
update readme mention of secrets setup
neagualexa Sep 23, 2025
68b234e
further readme clarification
neagualexa Sep 23, 2025
0bbc96c
fix expressions rendering
neagualexa Sep 25, 2025
d151273
fix result body
neagualexa Sep 25, 2025
fb07084
add docs
neagualexa Oct 3, 2025
c274bbe
add optional tutorial parts
neagualexa Oct 3, 2025
4be2adc
Dev: refactor of boilerplate (simplification) (#30)
neagualexa Dec 8, 2025
6512a6a
Mued api adopted (#32)
neagualexa Mar 30, 2026
4320109
fix test request path
neagualexa Mar 30, 2026
c7ea85d
update readme with mention of the prompts
neagualexa Mar 31, 2026
67a5e31
new CI sync with new eval func CI
neagualexa May 7, 2026
4d5b976
more clear release request issue
neagualexa May 7, 2026
d0f7682
fix report
neagualexa May 7, 2026
225b941
Remove print statement for response logging
neagualexa Jun 4, 2026
4caf532
integrate openrouter calls
neagualexa Aug 3, 2026
f4e9e50
shimmy adoption (#37)
neagualexa Aug 19, 2026
6d5e086
Update lf_toolkit dependency to v1.1.1 in requirements.txt.
neagualexa Aug 19, 2026
ebc607f
lazy loading, remove embeddings imports
neagualexa Aug 20, 2026
8178698
lazy loading agent pkgs
neagualexa Aug 20, 2026
fe07149
Merge remote-tracking branch 'template/main'
neagualexa Aug 20, 2026
734f371
remove test
neagualexa Aug 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 22 additions & 16 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ This file provides guidance to AI agents when working with code in this reposito

## Project Overview

This is a boilerplate for creating AI educational chatbots that integrate with the **Lambda-Feedback** educational platform. It deploys as an AWS Lambda function (containerized via Docker) that receives student chat messages with educational context and returns LLM-powered chatbot responses. Incoming requests follow the [muEd API](https://mued.org/) schema (`context`, `user`, `messages`).
This is a boilerplate for creating AI educational chatbots that integrate with the **Lambda-Feedback** educational platform. It's containerized via Docker and deployed behind [shimmy](https://github.com/lambda-feedback/shimmy), a shim that spawns this function as a persistent JSON-RPC worker process and exposes it as the muEd `/chat` / `/chat/health` HTTP API (both locally and as an AWS Lambda container). It receives student chat messages with educational context and returns LLM-powered chatbot responses. Incoming requests follow the [muEd API](https://mued.org/) schema (`context`, `user`, `messages`).

## Commands

**Testing:**
```bash
pytest # Run all unit tests
PYTHONPATH=. pytest # Run all unit tests (CI sets PYTHONPATH=. too)
python tests/manual_agent_run.py # Test agent locally with example inputs
python tests/manual_agent_requests.py # Test running Docker container
```
Expand All @@ -23,39 +23,45 @@ docker run --env-file .env -p 8080:8080 llm_chat

**Manual API test (while Docker is running):**
```bash
curl -X POST http://localhost:8080/2015-03-31/functions/function/invocations \
curl -X POST http://localhost:8080/chat \
-H 'Content-Type: application/json' \
-d '{"body":"{\"messages\": [{\"role\": \"USER\", \"content\": \"hi\"}]}"}'
-H 'X-Api-Version: 0.1.0' \
-d '{"messages": [{"role": "USER", "content": "hi"}]}'

curl http://localhost:8080/chat/health -H 'X-Api-Version: 0.1.0'
```

**Run a single test:**
```bash
pytest tests/test_module.py # Run specific test file
pytest tests/test_index.py::test_function_name # Run specific test
pytest tests/test_module.py::TestChatModuleFunction::test_response_format # Run specific test
```

## Architecture

### Request Flow

```
Lambda event → index.py (handler)
→ validates via lf_toolkit ChatRequest schema
→ src/module.py (chat_module)
→ extracts muEd API context (messages, conversationId, question context, user type)
→ parses educational context to prompt text via src/agent/context.py
→ src/agent/agent.py (BaseAgent / LangGraph)
→ routes to call_llm or summarize_conversation node
→ calls LLM provider (OpenAI / Google / Azure / Ollama)
→ returns ChatResponse (output, summary, conversationalStyle, processingTime)
shimmy (shim, container entrypoint)
→ spawns index.py as a persistent worker subprocess (lf_toolkit RPC server)
→ forwards POST /chat / GET /chat/health as JSON-RPC "chat" / "chat/health" calls
→ index.py registers src/module.py's chat_module / chat_health_module as handlers
→ lf_toolkit validates the request body against the muEd ChatRequest schema
→ src/module.py (chat_module)
→ extracts muEd API context (messages, conversationId, question context, user type)
→ parses educational context to prompt text via src/agent/context.py
→ src/agent/agent.py (BaseAgent / LangGraph)
→ routes to call_llm or summarize_conversation node
→ calls LLM provider (OpenAI / Google / Azure / Ollama)
→ returns ChatResponse (output, summary, conversationalStyle, processingTime)
```

### Key Files

| File | Role |
|------|------|
| `index.py` | AWS Lambda entry point; parses event body, validates schema |
| `src/module.py` | Transforms muEd API request → invokes agent → builds ChatResponse |
| `index.py` | Worker entrypoint; registers `chat_module`/`chat_health_module` with `lf_toolkit`'s RPC server (`create_server()` + `run()`) |
| `src/module.py` | Transforms muEd API request → invokes agent → builds ChatResponse; also exposes `chat_health_module()` |
| `src/agent/agent.py` | LangGraph stateful graph; manages message history and summarization |
| `src/agent/prompts.py` | System prompts for tutor behavior, summarization, style detection |
| `src/agent/llm_factory.py` | Factory classes for each LLM provider (OpenAI, Google, Azure, Ollama) |
Expand Down
38 changes: 22 additions & 16 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co

## Project Overview

This is a boilerplate for creating AI educational chatbots that integrate with the **Lambda-Feedback** educational platform. It deploys as an AWS Lambda function (containerized via Docker) that receives student chat messages with educational context and returns LLM-powered chatbot responses. Incoming requests follow the [muEd API](https://mued.org/) schema (`context`, `user`, `messages`).
This is a boilerplate for creating AI educational chatbots that integrate with the **Lambda-Feedback** educational platform. It's containerized via Docker and deployed behind [shimmy](https://github.com/lambda-feedback/shimmy), a shim that spawns this function as a persistent JSON-RPC worker process and exposes it as the muEd `/chat` / `/chat/health` HTTP API (both locally and as an AWS Lambda container). It receives student chat messages with educational context and returns LLM-powered chatbot responses. Incoming requests follow the [muEd API](https://mued.org/) schema (`context`, `user`, `messages`).

## Commands

**Testing:**
```bash
pytest # Run all unit tests
PYTHONPATH=. pytest # Run all unit tests (CI sets PYTHONPATH=. too)
python tests/manual_agent_run.py # Test agent locally with example inputs
python tests/manual_agent_requests.py # Test running Docker container
```
Expand All @@ -23,39 +23,45 @@ docker run --env-file .env -p 8080:8080 llm_chat

**Manual API test (while Docker is running):**
```bash
curl -X POST http://localhost:8080/2015-03-31/functions/function/invocations \
curl -X POST http://localhost:8080/chat \
-H 'Content-Type: application/json' \
-d '{"body":"{\"messages\": [{\"role\": \"USER\", \"content\": \"hi\"}]}"}'
-H 'X-Api-Version: 0.1.0' \
-d '{"messages": [{"role": "USER", "content": "hi"}]}'

curl http://localhost:8080/chat/health -H 'X-Api-Version: 0.1.0'
```

**Run a single test:**
```bash
pytest tests/test_module.py # Run specific test file
pytest tests/test_index.py::test_function_name # Run specific test
pytest tests/test_module.py::TestChatModuleFunction::test_response_format # Run specific test
```

## Architecture

### Request Flow

```
Lambda event → index.py (handler)
→ validates via lf_toolkit ChatRequest schema
→ src/module.py (chat_module)
→ extracts muEd API context (messages, conversationId, question context, user type)
→ parses educational context to prompt text via src/agent/context.py
→ src/agent/agent.py (BaseAgent / LangGraph)
→ routes to call_llm or summarize_conversation node
→ calls LLM provider (OpenAI / Google / Azure / Ollama)
→ returns ChatResponse (output, summary, conversationalStyle, processingTime)
shimmy (shim, container entrypoint)
→ spawns index.py as a persistent worker subprocess (lf_toolkit RPC server)
→ forwards POST /chat / GET /chat/health as JSON-RPC "chat" / "chat/health" calls
→ index.py registers src/module.py's chat_module / chat_health_module as handlers
→ lf_toolkit validates the request body against the muEd ChatRequest schema
→ src/module.py (chat_module)
→ extracts muEd API context (messages, conversationId, question context, user type)
→ parses educational context to prompt text via src/agent/context.py
→ src/agent/agent.py (BaseAgent / LangGraph)
→ routes to call_llm or summarize_conversation node
→ calls LLM provider (OpenAI / Google / Azure / Ollama)
→ returns ChatResponse (output, summary, conversationalStyle, processingTime)
```

### Key Files

| File | Role |
|------|------|
| `index.py` | AWS Lambda entry point; parses event body, validates schema |
| `src/module.py` | Transforms muEd API request → invokes agent → builds ChatResponse |
| `index.py` | Worker entrypoint; registers `chat_module`/`chat_health_module` with `lf_toolkit`'s RPC server (`create_server()` + `run()`) |
| `src/module.py` | Transforms muEd API request → invokes agent → builds ChatResponse; also exposes `chat_health_module()` |
| `src/agent/agent.py` | LangGraph stateful graph; manages message history and summarization |
| `src/agent/prompts.py` | System prompts for tutor behavior, summarization, style detection |
| `src/agent/llm_factory.py` | Factory classes for each LLM provider (OpenAI, Google, Azure, Ollama) |
Expand Down
34 changes: 20 additions & 14 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,18 +1,14 @@
ARG PYTHON_VERSION=3.13
ARG BASE_VERSION=python:3.12

FROM public.ecr.aws/lambda/python:${PYTHON_VERSION}
# evaluation-function-base's python image bundles the shimmy binary,
# the Lambda RIE, and the entrypoint.sh that picks between them.
FROM ghcr.io/lambda-feedback/evaluation-function-base/${BASE_VERSION}

# Set working directory
WORKDIR ${LAMBDA_TASK_ROOT}
RUN apt-get update && apt-get install -y \
build-essential \
&& rm -rf /var/lib/apt/lists/*

RUN pip install --upgrade pip
RUN dnf install -y git \
&& dnf install -y \
gcc \
gcc-c++ \
make \
python3-devel \
&& dnf clean all
RUN pip install --upgrade pip

COPY requirements.txt .
RUN pip install -r requirements.txt
Expand All @@ -27,5 +23,15 @@ COPY index.py .

COPY tests ./tests

# Set the Lambda function handler
CMD ["index.handler"]
# Command shimmy uses to start the chat function worker
ENV FUNCTION_COMMAND="python"

# Args to start the chat function worker with
ENV FUNCTION_ARGS="index.py"

# The transport to use for the RPC server
ENV FUNCTION_RPC_TRANSPORT="ipc"

ENV FUNCTION_WORKER_SEND_TIMEOUT="170s"

ENV LOG_LEVEL="debug"
53 changes: 28 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,15 @@ OPENAI_MODEL
GOOGLE_AI_API_KEY
GOOGLE_AI_MODEL
```

> [!Note]
> If you decide to use another endpoint such as Azure or Ollama or any other, please update the github workflow files to use the right secrets and variables for testing.
> If you use OpenRouter:
```bash
> If you use Azure-OpenAI:
AZURE_OPENAI_API_KEY
AZURE_OPENAI_ENDPOINT
AZURE_OPENAI_API_VERSION
AZURE_OPENAI_CHAT_DEPLOYMENT_NAME
AZURE_OPENAI_EMBEDDING_3072_DEPLOYMENT
AZURE_OPENAI_EMBEDDING_1536_DEPLOYMENT
AZURE_OPENAI_EMBEDDING_3072_MODEL
AZURE_OPENAI_EMBEDDING_1536_MODEL
OPENROUTER_API_KEY
OPENROUTER_MODEL
OPENROUTER_BASE_URL
```

> [!NOTE]
> If you decide to use other providers like Azure OpenAI or Ollama, you will need to update the workflow files and the `llm_factory.py` file to include the necessary environment variables for those providers.

> For monitoring of the LLM calls (follow instructions on how to set up on langsmith online):
LANGCHAIN_TRACING_V2
Expand Down Expand Up @@ -117,7 +113,6 @@ The agent uses **two separate LLM instances** — `self.llm` for chat responses
├── manual_agent_run.py # allows testing of any LLM agent on a couple of example inputs
├── utils.py # shared test helpers
├── test_example_inputs.py # pytests for the example input files
├── test_index.py # pytests
└── test_module.py # pytests
```

Expand All @@ -128,18 +123,18 @@ To test your function, you can run the unit tests, call the code directly throug

### Run Unit Tests

You can run the unit tests using `pytest`.
You can run the unit tests using `pytest`. Run it from the repository root with `PYTHONPATH=.` set (as CI does) so the `tests` and `src` packages resolve correctly:

```bash
pytest
PYTHONPATH=. pytest
```

### Run the Chat Script

You can run the Python function itself. Make sure to have a main function in either `src/module.py` or `index.py`.
You can run the Python function itself directly — `index.py` wires `chat_module`/`chat_health_module` into `lf_toolkit`'s RPC server, the same way shimmy invokes it inside the container. This requires the `EVAL_IO`/`EVAL_RPC_TRANSPORT` environment variables shimmy would normally set (see `lf_toolkit`'s docs), so prefer the Docker or `manual_agent_run.py` routes below for everyday testing.

```bash
python src/module.py
python index.py
```

You can also use the `manual_agent_run.py` script to test the agents with example inputs from Lambda Feedback questions and synthetic conversations.
Expand Down Expand Up @@ -171,33 +166,41 @@ docker run -e OPENAI_API_KEY={your key} -e OPENAI_MODEL={your LLM model name} -p
docker run --env-file .env -it --name my-lambda-container -p 8080:8080 llm_chat
```

This will start the chat function and expose it on port `8080` and it will be open to be curl:
This starts shimmy (the [Lambda Feedback shim](https://github.com/lambda-feedback/shimmy)) as the container's entrypoint, which spawns this function as a worker subprocess and exposes it on port `8080` as the muEd chat API:

```bash
curl --location 'http://localhost:8080/2015-03-31/functions/function/invocations' \
curl --location 'http://localhost:8080/chat' \
--header 'Content-Type: application/json' \
--data '{"body":"{\"messages\": [{\"role\": \"USER\", \"content\": \"hi\"}]}"}'
--header 'X-Api-Version: 0.1.0' \
--data '{"messages": [{"role": "USER", "content": "hi"}]}'
```

Health check:

```bash
curl --location 'http://localhost:8080/chat/health' \
--header 'X-Api-Version: 0.1.0'
```

#### Call Docker Container
##### A. Call Docker with Python Requests

In the `tests/` folder you can find the `manual_agent_requests.py` script that calls the POST URL of the running docker container. It reads any kind of input files with the expected schema. You can use this to test your curl calls of the chatbot.
In the `tests/` folder you can find the `manual_agent_requests.py` script that calls the `/chat` and `/chat/health` routes of the running docker container. It reads any kind of input files with the expected schema. You can use this to test your curl calls of the chatbot.

##### B. Call Docker Container through API request

POST URL:

```bash
http://localhost:8080/2015-03-31/functions/function/invocations
http://localhost:8080/chat
```

Per the [muEd `ChatRequest` schema](https://mued.org/), only `messages` is required; `conversationId`, `user`, `context`, and `configuration` are all optional.
Per the [muEd `ChatRequest` schema](https://mued.org/), only `messages` is required; `conversationId`, `user`, `context`, and `configuration` are all optional. Requests may include an `X-Api-Version: 0.1.0` header.

**Minimal request — only required components** (stringified within `body` for the AWS Lambda Runtime Interface Emulator):
**Minimal request — only required components:**

```JSON
{"body":"{\"messages\": [{\"role\": \"USER\", \"content\": \"hi\"}]}"}
{"messages": [{"role": "USER", "content": "hi"}]}
```

**Full request as Lambda Feedback sends it** — all optional fields populated:
Expand Down
26 changes: 17 additions & 9 deletions docs/dev.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ To test your function, you can run the unit tests, call the code directly throug

### Run Unit Tests

You can run the unit tests using `pytest`.
You can run the unit tests using `pytest`. Run it from the repository root with `PYTHONPATH=.` set (as CI does) so the `tests` and `src` packages resolve correctly:

```bash
pytest
PYTHONPATH=. pytest
```

### Run the Chat Script
Expand Down Expand Up @@ -53,31 +53,39 @@ docker run -e OPENAI_API_KEY={your key} -e OPENAI_MODEL={your LLM chosen model n
docker run --env-file .env -it --name my-lambda-container -p 8080:8080 llm_chat
```

This will start the chat function and expose it on port `8080` and it will be open to be curl:
This starts shimmy (the [Lambda Feedback shim](https://github.com/lambda-feedback/shimmy)) as the container's entrypoint, which spawns this function as a worker subprocess and exposes it on port `8080` as the muEd chat API:

```bash
curl --location 'http://localhost:8080/2015-03-31/functions/function/invocations' \
curl --location 'http://localhost:8080/chat' \
--header 'Content-Type: application/json' \
--data '{"body":"{\"conversationId\": \"12345Test\", \"messages\": [{\"role\": \"USER\", \"content\": \"hi\"}], \"user\": {\"type\": \"LEARNER\"}}"}'
--header 'X-Api-Version: 0.1.0' \
--data '{"conversationId": "12345Test", "messages": [{"role": "USER", "content": "hi"}], "user": {"type": "LEARNER"}}'
```

Health check:

```bash
curl --location 'http://localhost:8080/chat/health' \
--header 'X-Api-Version: 0.1.0'
```

#### Call Docker Container
##### A. Call Docker with Python Requests

In the `tests/` folder you can find the `manual_agent_requests.py` script that calls the POST URL of the running docker container. It reads any kind of input files with the expected schema. You can use this to test your curl calls of the chatbot.
In the `tests/` folder you can find the `manual_agent_requests.py` script that calls the `/chat` and `/chat/health` routes of the running docker container. It reads any kind of input files with the expected schema. You can use this to test your curl calls of the chatbot.

##### B. Call Docker Container through API request

POST URL:

```bash
http://localhost:8080/2015-03-31/functions/function/invocations
http://localhost:8080/chat
```

Body (stringified within body for API request):
Body (requests may include an `X-Api-Version: 0.1.0` header):

```JSON
{"body":"{\"conversationId\": \"12345Test\", \"messages\": [{\"role\": \"USER\", \"content\": \"hi\"}], \"user\": {\"type\": \"LEARNER\"}}"}
{"conversationId": "12345Test", "messages": [{"role": "USER", "content": "hi"}], "user": {"type": "LEARNER"}}
```

Body with optional fields:
Expand Down
Loading
Loading