Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion .github/workflows/production-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ jobs:
environment: "production"
version-bump: ${{ inputs.version-bump }}
branch: ${{ inputs.branch }}
deployed-environment-variables: '[\"OPENAI_API_KEY\",\"OPENAI_MODEL\",\"GOOGLE_AI_API_KEY\",\"GOOGLE_AI_MODEL\"]'
deployed-environment-variables: '[\"OPENAI_API_KEY\",\"OPENAI_MODEL\",\"GOOGLE_AI_API_KEY\",\"GOOGLE_AI_MODEL\",\"OPENROUTER_API_KEY\",\"OPENROUTER_MODEL\",\"OPENROUTER_BASE_URL\"]'
secrets:
aws-key-id: ${{ secrets.LAMBDA_CONTAINER_PIPELINE_AWS_ID }}
aws-secret-key: ${{ secrets.LAMBDA_CONTAINER_PIPELINE_AWS_SECRET }}
Expand Down
5 changes: 4 additions & 1 deletion .github/workflows/staging-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ jobs:
OPENAI_MODEL: ${{ vars.OPENAI_MODEL }}
GOOGLE_AI_API_KEY: ${{ secrets.GOOGLE_AI_API_KEY }}
GOOGLE_AI_MODEL: ${{ vars.GOOGLE_AI_MODEL }}
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
OPENROUTER_MODEL: ${{ vars.OPENROUTER_MODEL }}
OPENROUTER_BASE_URL: ${{ vars.OPENROUTER_BASE_URL }}
steps:
- name: Checkout Code
uses: actions/checkout@v4
Expand Down Expand Up @@ -67,7 +70,7 @@ jobs:
with:
template-repository-name: "lambda-feedback/chat-function-boilerplate"
environment: "staging"
deployed-environment-variables: '[\"OPENAI_API_KEY\",\"OPENAI_MODEL\",\"GOOGLE_AI_API_KEY\",\"GOOGLE_AI_MODEL\"]'
deployed-environment-variables: '[\"OPENAI_API_KEY\",\"OPENAI_MODEL\",\"GOOGLE_AI_API_KEY\",\"GOOGLE_AI_MODEL\",\"OPENROUTER_API_KEY\",\"OPENROUTER_MODEL\",\"OPENROUTER_BASE_URL\"]'
secrets:
aws-key-id: ${{ secrets.LAMBDA_CONTAINER_PIPELINE_AWS_ID }}
aws-secret-key: ${{ secrets.LAMBDA_CONTAINER_PIPELINE_AWS_SECRET }}
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/test-lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ jobs:
OPENAI_MODEL: ${{ vars.OPENAI_MODEL }}
GOOGLE_AI_API_KEY: ${{ secrets.GOOGLE_AI_API_KEY }}
GOOGLE_AI_MODEL: ${{ vars.GOOGLE_AI_MODEL }}
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
OPENROUTER_MODEL: ${{ vars.OPENROUTER_MODEL }}
OPENROUTER_BASE_URL: ${{ vars.OPENROUTER_BASE_URL }}
steps:
- name: Checkout
uses: actions/checkout@v4
Expand Down
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 chat function connecting students to an AI educational chatbot that is integrated 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 chat function connecting students to an AI educational chatbot that is integrated 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
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co

## Project Overview

This is a chat function connecting students to an AI educational chatbot that is integrated 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). Incoming requests follow the [muEd API](https://mued.org/) schema (`context`, `user`, `messages`).
This is a chat function connecting students to an AI educational chatbot that is integrated 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

Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,4 @@ ENV FUNCTION_RPC_TRANSPORT="ipc"

ENV FUNCTION_WORKER_SEND_TIMEOUT="170s"

ENV LOG_LEVEL="debug"
ENV LOG_LEVEL="debug"
20 changes: 8 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,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
2 changes: 1 addition & 1 deletion docs/dev.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ POST URL:
http://localhost:8080/chat
```

Input body (requests must include an `X-Api-Version: 0.1.0` header):
Body (requests may include an `X-Api-Version: 0.1.0` header):

```JSON
{"conversationId": "12345Test", "messages": [{"role": "USER", "content": "hi"}], "user": {"type": "LEARNER"}}
Expand Down
17 changes: 17 additions & 0 deletions src/agent/llm_factory.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import os
from typing import Optional

from langchain_openai import AzureChatOpenAI
from langchain_openai import AzureOpenAIEmbeddings
Expand Down Expand Up @@ -82,3 +83,19 @@ def __init__(self, temperature: int = 0):

def get_llm(self):
return self._google_llm

class ChatOpenRouterProvider:
def __init__(self, temperature: int = 0, model: Optional[str] = None):
model_name = model or os.environ['OPENROUTER_MODEL']
key = os.environ['OPENROUTER_API_KEY']
base_url = os.environ['OPENROUTER_BASE_URL']

self._openrouter_llm = ChatOpenAI(
model=model_name,
temperature=temperature,
api_key=key,
base_url=base_url,
)

def get_llm(self):
return self._openrouter_llm
2 changes: 1 addition & 1 deletion src/module.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ def chat_health_module() -> ChatHealthResponse:
status=HealthStatus.OK,
capabilities=ChatCapabilities(
supportsChat=True,
supportsUserPreferences=True,
supportsUserPreferences=False,
supportsStreaming=False,
supportsDataPolicy=DataPolicySupport.NOT_SUPPORTED,
),
Expand Down
1 change: 0 additions & 1 deletion tests/manual_agent_requests.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import requests
import json

"""
Script that sends requests straight to shimmy's muEd chat routes on the
Expand Down
Loading