-
Notifications
You must be signed in to change notification settings - Fork 5
Updated evaluation function docs #37
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
57ebae2
Updated evaluation function docs
m-messer a50d4e8
Expanded evaluation function documentation to include tooling for Wol…
m-messer 576144d
Clarified evaluation function documentation structure, expanded secti…
m-messer eeb4bea
Expanded evaluation function documentation to include instructions fo…
m-messer File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
114 changes: 77 additions & 37 deletions
114
docs/advanced/evaluation_functions/alternate_languages.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,37 +1,77 @@ | ||
| # Alternate Evaluation Function Languages | ||
| --- | ||
|
|
||
| ## Lambda-Compatible Images | ||
| ### Extending a pre-built Lambda image | ||
| - Available for: Node.js, Python, Java, .NET, Go, Ruby | ||
| - [Docs](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-images.html#runtimes-images-lp) | ||
| - [Repo](https://github.com/aws/aws-lambda-base-images) | ||
| - These base images are regularly updated, and the most widely used (more docs) | ||
| - They also come with pre-packaged runtime interface clients - a HTTP interface for runtimes to receive invocation events and respond | ||
| - Good for local development | ||
|
|
||
| ### Creating custom base images | ||
| - Using the [lambda/provided](https://gallery.ecr.aws/lambda/provided) image | ||
| - This "contains all the required components to run functions packaged as container images on Lambda" | ||
| - Building a custom runtime from scratch | ||
| - [Custom AWS Lambda runtimes](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-custom.html#runtimes-custom-build) | ||
| - [Runtimes walkthrough tutorial](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-walkthrough.html) | ||
| - Emulate execution locally? | ||
| > Lambda provides a runtime interface emulator (RIE) for you to test your function locally. The AWS base images for Lambda and base images for custom runtimes include the RIE. For other base images, you can download the [Runtime interface emulator](https://github.com/aws/aws-lambda-runtime-interface-emulator) from the AWS GitHub repository. | ||
|
|
||
| ### Misc Notes/Sources | ||
| - [The Lambda Execution Environment](https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtime-environment.html) | ||
| - [Create Images from Alternative base images](https://docs.aws.amazon.com/lambda/latest/dg/images-create.html#images-create-from-alt) | ||
|
|
||
| ## Development Philosophy | ||
| Ultimately we want to call a function made by a user in any language. Two ways to do this: | ||
|
|
||
| - We write and provide runtime in all the different languages. This means that all the logic happens in that language. We write the code that actually receives the requests from lambda function events. In this case, the user function can be imported from those handlers. | ||
| - Writing handlers in each of those languages requires time and extensive knowledge (in order to write robust code) | ||
| - Handler code needs to: | ||
| - Have clean and reliable error catching | ||
|
|
||
| - We write a global runtime, which makes a call to their function via a sub-process. We call their script, which must recieve the payload as a commandline argument. | ||
| - User has to write more code | ||
| - For allowing cmdline arguments, and parsing of inputs | ||
| - Might be slower than in other languages. Since another script has to be executed. | ||
| # Evaluation Functions in Other Languages | ||
|
|
||
| [Shimmy](https://github.com/lambda-feedback/shimmy) — the [base layer](specification.md#base-layer) | ||
| in front of every evaluation function — is language-agnostic. It handles the HTTP API, request | ||
| validation and the feedback `cases` loop, then runs *your* function as a child process over one | ||
| of two interfaces. Writing a function in another language means providing that child process. | ||
|
|
||
| ## Base images | ||
|
|
||
| All base images bundle Shimmy and are published under | ||
| [`ghcr.io/lambda-feedback/evaluation-function-base`](https://github.com/lambda-feedback/evaluation-function-base): | ||
|
|
||
| | Image | For | | ||
| | --- | --- | | ||
| | `evaluation-function-base/python` | Python functions (uses [`lf_toolkit`](module.md)) | | ||
| | `evaluation-function-base/wolfram` | Wolfram Language / `wolframscript` functions | | ||
| | `evaluation-function-base/lean` | Lean functions (compiled binary) | | ||
| | `evaluation-function-base/scratch` | Any other language — a minimal Debian image with just Shimmy | | ||
|
|
||
| Your `Dockerfile` does `FROM` one of these, installs your toolchain and code, and sets the | ||
| environment variables below. | ||
|
|
||
| ## Worker interfaces | ||
|
|
||
| Shimmy chooses the interface from the `FUNCTION_INTERFACE` environment variable. | ||
|
|
||
| ### RPC (default) | ||
|
|
||
| The worker is a long-lived process that speaks [JSON-RPC 2.0](https://www.jsonrpc.org/specification), | ||
| one method per command (`eval`, `preview`, `healthcheck`). Transport is set by | ||
| `FUNCTION_RPC_TRANSPORT`: | ||
|
|
||
| - `stdio` (default) — messages over the process's stdin/stdout, framed with `Content-Length` headers; | ||
| - `ipc` — a Unix domain socket. | ||
|
|
||
| Python's [`lf_toolkit`](module.md) implements this interface, so Python functions just call | ||
| `create_server()` / `run()` in `evaluation_function/main.py` and never deal with the wire format. | ||
| The Wolfram base image bundles [`toolkit-wolfram`](https://github.com/lambda-feedback/toolkit-wolfram), | ||
| which handles the transport wiring for `wolframscript` functions in the same way. | ||
|
|
||
| ### File | ||
|
|
||
| Shimmy starts a **fresh process per request**, appending two paths as the final arguments — an | ||
| input file and an output file. The worker reads the request JSON, writes the response JSON and | ||
| exits. This suits languages without a convenient long-running-server story, and large payloads | ||
| (e.g. base64 images). | ||
|
|
||
| The request file is *wrapped*: | ||
|
|
||
| ```json | ||
| { | ||
| "command": "eval", | ||
| "params": { "response": "...", "answer": "...", "params": {} } | ||
| } | ||
| ``` | ||
|
|
||
| The worker writes the same `{"command": ..., "result": {...}}` / `{"error": {...}}` shape the | ||
| [Legacy API](specification.md#legacy-api) returns. | ||
|
|
||
| ## Setting the worker command | ||
|
|
||
| The base layer reads these from the `Dockerfile`: | ||
|
|
||
| ```dockerfile | ||
| ENV FUNCTION_COMMAND="wolframscript" | ||
| ENV FUNCTION_ARGS="-f,evaluation_function.wl" # comma-separated | ||
| ENV FUNCTION_INTERFACE="file" | ||
| ``` | ||
|
|
||
| ## Boilerplates | ||
|
|
||
| - [`evaluation-function-boilerplate-python`](https://github.com/lambda-feedback/evaluation-function-boilerplate-python) — RPC interface via `lf_toolkit` | ||
| - [`evaluation-function-boilerplate-wolfram`](https://github.com/lambda-feedback/evaluation-function-boilerplate-wolfram) — file interface, `wolframscript -f evaluation_function.wl request.json response.json` | ||
| - [`evaluation-function-boilerplate-lean`](https://github.com/lambda-feedback/evaluation-function-boilerplate-lean) — file interface, compiled `.lake/build/bin/evaluation request.json response.json` | ||
|
|
||
| Each boilerplate's `README.md` has the full build, run and local-test instructions for that | ||
| language. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,65 +1,162 @@ | ||
| # Running and Testing Functions Locally | ||
|
|
||
| ## Simple | ||
| Evaluation functions are developed and tested locally **without** the base-image server: you call | ||
| your function directly and run its test suite. The full container — your function behind the | ||
| [Shimmy](https://github.com/lambda-feedback/shimmy) base layer — is normally exercised by CI and | ||
| in deployment, but you can also [build and run it locally](#testing-against-the-container) to | ||
| check the real HTTP API before pushing. | ||
|
|
||
| !!! info "This page is about Python functions" | ||
| It covers functions built from the current | ||
| [`evaluation-function-boilerplate-python`](https://github.com/lambda-feedback/evaluation-function-boilerplate-python), | ||
| which uses [Poetry](https://python-poetry.org/) and an `evaluation_function/` package — the | ||
| commands below (`poetry`, `pytest`, `python -m evaluation_function.dev`) are all | ||
| Python-specific. For Wolfram, Lean or other languages the local loop differs; see | ||
| [Other Languages](alternate_languages.md) and the relevant boilerplate's `README.md`. | ||
| Functions still on the older AWS Lambda base layer (those with an `app/` directory) are | ||
| covered [at the bottom of this page](#older-aws-lambda-base-layer). | ||
|
|
||
| ## Using Docker [:material-docker:](https://www.docker.com/) | ||
| This method builds and runs evaluation functions in the same way they are deployed on AWS as Lambda functions. Extending a pre-built and AWS-maintained [base python image](https://docs.aws.amazon.com/lambda/latest/dg/python-image.html#python-image-base), the container contains a HTTP client which can be used to locally simulate Lambda execution events. | ||
| ## Run unit tests | ||
|
|
||
| Note that this is different from the [simple](#simple) method proposed, in that it gives access to all the functionality provided by the base layer. This means that commands such as `docs` and `healthcheck` can be tested. | ||
| Install dependencies and run the test suite with [`pytest`](https://docs.pytest.org/) from the | ||
| repository root: | ||
|
|
||
| 1. Install [Docker](https://docs.docker.com/get-docker/) on your machine | ||
| ```bash | ||
| poetry install | ||
| poetry run pytest | ||
| ``` | ||
|
|
||
| 2. Navigate to the root directory of your function | ||
| This is the same suite the CI pipeline runs on every push and pull request; a function is not | ||
| deployed unless it passes. | ||
|
|
||
| 3. Build the image. This will pull our base image from Dockerhub, extend it with files specific to your evaluation function and name it `eval-tmp`. | ||
| ```bash | ||
| docker image build -t eval-tmp app | ||
| ``` | ||
| ## Call the function directly | ||
|
|
||
| 4. Spin up a container using the image built in the previous step. | ||
| ```bash | ||
| docker run --rm -d --name eval-function -p 9000:8080 eval-tmp | ||
| ``` | ||
| The boilerplate ships an `evaluation_function/dev.py` helper that calls your `evaluation_function` | ||
| directly — the quickest loop while iterating on comparison logic: | ||
|
|
||
| 5. You can now simulate requests to the function using any request client (like [Insomnia](https://insomnia.rest/) or [Postman](https://www.postman.com/)). By default, the url you can hit is: | ||
| ```url | ||
| http://localhost:9000/2015-03-31/functions/function/invocations | ||
| ``` | ||
| ```bash | ||
| python -m evaluation_function.dev "<response>" "<answer>" '<params-json>' | ||
| ``` | ||
|
|
||
| ???+ warning | ||
| *When deployed, our Lambda functions are triggered by calls made through an AWS [API Gateway](https://aws.amazon.com/api-gateway/). This means that when testing locally, events sent should follow the structure of events triggered by that resource. That is, if you want to simulate what it would be like to make web requests to the deployed function.* | ||
| For example: | ||
|
|
||
| Specifically, this means structuring requests in the following way: | ||
| ```json | ||
| { | ||
| "headers": { | ||
| "command": "eval" | ||
| }, | ||
| "body": { | ||
| "response": "a", | ||
| "answer": "a", | ||
| "params": { | ||
| "garlic": "moreish" | ||
| } | ||
| } | ||
| } | ||
| ``` | ||
| ```bash | ||
| python -m evaluation_function.dev "2*x" "x + x" '{}' | ||
| ``` | ||
|
|
||
| The main difference is that `headers` and `body` are sent as keys in the main body of the local request. When hitting the deployed function through the API Gateway, the `command` field would instead be passed in the actual HTTP headers of the request - and the actual request body would only contain the `response`, `answer` and `params` fields. | ||
| `answer` and the params JSON are optional. See the script's `--help` for its exact arguments, | ||
| which vary slightly between functions. | ||
|
|
||
| 6. *(Optional)* The `run` command specifies the **-d** flag, which spins up the container in detached mode. If you want to inspect the logs of the function, you can run: | ||
| ```bash | ||
| docker container logs -f eval-function | ||
| ``` | ||
| ## Testing against the container | ||
|
|
||
| ??? note "Tip" | ||
| You will very rarely need this, but you can peek into the running container by opening a shell within it using: | ||
| Building the image and sending it real HTTP requests runs the **same container CI builds and | ||
| deployment ships**: your function behind [Shimmy](https://github.com/lambda-feedback/shimmy), | ||
| serving the API on port `8080`. Use it for the end-to-end checks that calling the function | ||
| directly and `pytest` don't cover — schema validation, the µEd and Legacy wire formats, and the | ||
| [feedback `cases`](feedback.md) loop. | ||
|
|
||
| ```bash | ||
| docker exec -it eval-function bash | ||
| ``` | ||
| !!! info "Applies to any base image" | ||
| The steps below use the Python `evaluation_function/` layout for their examples, but the | ||
| build and run commands are the same for Wolfram, Lean and `scratch` functions — only the | ||
| `Dockerfile` contents differ. See [Other Languages](alternate_languages.md). | ||
|
|
||
| ## Useful Links | ||
| ### Build the image | ||
|
|
||
| - | ||
| From the repository root (where the `Dockerfile` is): | ||
|
|
||
| ```bash | ||
| docker build -t my-eval-function . | ||
| ``` | ||
|
|
||
| !!! tip "Podman works too" | ||
| [Podman](https://podman.io/) is a drop-in replacement — swap `docker` for `podman` in every | ||
| command on this page and the arguments are identical. | ||
|
|
||
| ### Run the container | ||
|
|
||
| Expose Shimmy's port `8080`: | ||
|
|
||
| ```bash | ||
| docker run --rm -p 8080:8080 my-eval-function | ||
| ``` | ||
|
|
||
| Add `--name my-eval-function` if you want to `docker exec` / `docker cp` into the running | ||
| container, and `-e SANDBOX_ENABLED=true` to also exercise the optional | ||
| [nsjail](https://github.com/google/nsjail) sandbox that Shimmy applies in production. | ||
|
|
||
| ### Health checks | ||
|
|
||
| ```bash | ||
| curl http://localhost:8080/health | ||
| curl --header 'X-Api-Version: 0.1.0' http://localhost:8080/evaluate/health | ||
| ``` | ||
|
|
||
| `GET /health` is a plain liveness probe; `GET /evaluate/health` is the µEd health route. | ||
|
|
||
| ### Send a µEd request | ||
|
|
||
| `POST /evaluate` with an `X-Api-Version: 0.1.0` header — the request the platform sends for | ||
| newly registered functions: | ||
|
|
||
| ```bash | ||
| curl --request POST \ | ||
| --url http://localhost:8080/evaluate \ | ||
| --header 'Content-Type: application/json' \ | ||
| --header 'X-Api-Version: 0.1.0' \ | ||
| --data '{ | ||
| "submission": { "type": "OTHER", "content": { "value": "x + x" } }, | ||
| "task": { "referenceSolution": { "expression": "2*x" } } | ||
| }' | ||
| ``` | ||
|
|
||
| See the [µEd API](specification.md#ed-api) section of the specification for the full | ||
| request/response contract. | ||
|
|
||
| ### Send a Legacy request | ||
|
|
||
| `POST /` with the command in a `command` header and a bare `response` / `answer` / `params` | ||
| body: | ||
|
|
||
| ```bash | ||
| curl --request POST \ | ||
| --url http://localhost:8080/ \ | ||
| --header 'Content-Type: application/json' \ | ||
| --header 'command: eval' \ | ||
| --data '{ "response": "2*x", "answer": "x + x", "params": {} }' | ||
| ``` | ||
|
|
||
| The response is `{"command": "eval", "result": {...}}`, or `{"error": {"message": ...}}` if the | ||
| function raised — see [Legacy API](specification.md#legacy-api). Swapping the header for | ||
| `command: healthcheck` runs the function's own test suite inside the container and returns a | ||
| pass/fail summary. | ||
|
|
||
| ### Postman and other clients | ||
|
|
||
| Any HTTP client works — `curl`, [Insomnia](https://insomnia.rest/), | ||
| [Postman](https://www.postman.com/). Point it at the running container: | ||
|
|
||
| - **µEd** — `POST http://localhost:8080/evaluate`, headers `Content-Type: application/json` and | ||
| `X-Api-Version: 0.1.0`, body as the µEd JSON above. | ||
| - **Legacy** — `POST http://localhost:8080/`, header `Content-Type: application/json` plus a | ||
| `command` header (`eval`, `preview` or `healthcheck`), body `{ "response": ..., "answer": ..., | ||
| "params": {} }`. | ||
|
|
||
| ## Older AWS Lambda base layer | ||
|
|
||
| ??? note "Functions not yet migrated" | ||
| A small number of functions (for example | ||
| [`compareExpressions`](https://github.com/lambda-feedback/compareExpressions)) still extend | ||
| the older `ghcr.io/lambda-feedback/baseevalutionfunctionlayer` image and keep the `app/` | ||
| directory layout. Their tests run with `python -m unittest app.evaluation_tests`, and the | ||
| built image is exercised locally with the AWS | ||
| [Runtime Interface Emulator](https://github.com/aws/aws-lambda-runtime-interface-emulator) | ||
| (`docker run -p 9000:8080 …`, then POST an API-Gateway-style event to | ||
| `http://localhost:9000/2015-03-31/functions/function/invocations`). See the function's own | ||
| `README.md` for the details. | ||
|
|
||
| ## Useful links | ||
|
|
||
| - [`evaluation-function-boilerplate-python`](https://github.com/lambda-feedback/evaluation-function-boilerplate-python) — template for new Python functions | ||
| - [`toolkit-python`](https://github.com/lambda-feedback/toolkit-python) — the `lf_toolkit` helper package | ||
| - [`evaluation-function-base`](https://github.com/lambda-feedback/evaluation-function-base) — the base images (Python, Wolfram, Lean, scratch) | ||
| - [µEd API specification](https://mued.org/) | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.