diff --git a/.gitignore b/.gitignore index 9e3544f..43e147d 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,5 @@ -mathpass \ No newline at end of file +mathpass + +# Local, non-Docker dev checkout of toolkit-wolfram -- see +# scripts/setup-toolkit.sh and .toolkit-wolfram-version. +/toolkit-wolfram/ \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 5cfab77..2dd3981 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,17 +1,21 @@ FROM ghcr.io/lambda-feedback/evaluation-function-base/wolfram:latest as base -# Command to start the evaluation function with -ENV FUNCTION_COMMAND="wolframscript" - -# Args to start the evaluation function with -ENV FUNCTION_ARGS="-f,/app/evaluation_function.wl" - # Interface to use for the evaluation function -ENV FUNCTION_INTERFACE="file" +ENV FUNCTION_INTERFACE="rpc" +ENV FUNCTION_RPC_TRANSPORT="tcp" +ENV FUNCTION_WORKER_START_TIMEOUT="60s" ENV LOG_LEVEL="DEBUG" -# Copy the evaluation function to the app directory -COPY ./evaluation_function.wl /app/evaluation_function.wl +# The shared Wolfram evaluation-function toolkit (JSON comms layer) is +# installed in the base image -- see evaluation-function-base/wolfram/Dockerfile. +# +# Optional local-dev override: run scripts/sync-local-toolkit.sh to populate +# ./.local-toolkit from a local toolkit-wolfram checkout before building, to +# test unreleased toolkit-wolfram changes here. Left empty (the default, +# tracked via .local-toolkit/.gitkeep), this COPY is a no-op and the image +# keeps using the toolkit-wolfram version pinned in the base image. +#COPY ./.local-toolkit /opt/lambda-feedback/toolkit-wolfram + COPY ./evaluate.m /app/evaluate.m COPY ./preview.m /app/preview.m \ No newline at end of file diff --git a/README.md b/README.md index 270b2f1..820b413 100644 --- a/README.md +++ b/README.md @@ -10,12 +10,24 @@ This repository contains an implementation of a Wolfram evaluation function that You can choose between running the Wolfram evaluation function itself, ore using Shimmy to run the function. +This repository's JSON comms (reading the request, dispatching `eval`/`preview`, writing the response) come from the shared [`toolkit-wolfram`](https://github.com/lambda-feedback/toolkit-wolfram) paclet (`LambdaFeedback/EvaluationFunctionToolkit`) rather than being implemented in this repo — `evaluate.m`/`preview.m` here just provide `EvaluationFunction`/`PreviewFunction`, and the toolkit's `Bootstrap.wl` wires everything together (it's what the Docker image's Shimmy setup runs). To run locally, fetch the pinned toolkit version (see `.toolkit-wolfram-version`) with: + +```bash +scripts/setup-toolkit.sh +``` + +This clones it into `./toolkit-wolfram` (gitignored; re-run any time to reset it). Then point `LF_TOOLKIT_PATH` at it: + +```bash +export LF_TOOLKIT_PATH=./toolkit-wolfram +``` + **Local** Use the following command to run the evaluation function directly: ```bash -wolframscript -f evaluation_function.wl request.json response.json +wolframscript -f ./toolkit-wolfram/Bootstrap.wl request.json response.json ``` This will run the evaluation function using the input data from `request.json` and write the output to `response.json`. @@ -23,12 +35,12 @@ An example `request.json` is: ``` { - "method": "eval", + "command": "eval", "params": { "answer":"Sin[p x + q]", "response":"Sin[a x + b]", "params":{ - "comparisonType":"structure", + "type":"structure", "named_variables":"{x}", "correct_response_feedback":"Your answer is correct!", "incorrect_response_feedback":"Your answer is incorrect!" @@ -44,8 +56,7 @@ Which gives the response: "command": "eval", "result": { "is_correct": true, - "feedback": "Your answer is correct!", - "error": null + "feedback": "Your answer is correct!" } } ``` @@ -63,19 +74,20 @@ Which gives the response: build.yml # builds the public evaluation function image deploy.yml # deploys the evaluation function to Lambda Feedback -evaluation_function.wl # evaluation function source code +evaluate.m # EvaluationFunction source code +preview.m # PreviewFunction source code -config.json # evaluation function deployment configuration file +config.json # evaluation function deployment configuration file ``` ### Development Workflow -In its most basic form, the development workflow consists of writing the evaluation function in the `evaluation_function.wl` file and testing it locally. As long as the evaluation function adheres to the Evaluation Function API, a development workflow which incorporates using Shimmy is not necessary. +In its most basic form, the development workflow consists of writing the evaluation function in `evaluate.m`/`preview.m` and testing it locally. As long as the evaluation function adheres to the Evaluation Function API, a development workflow which incorporates using Shimmy is not necessary. -Testing the evaluation function can be done by running the script using the Wolfram Engine / WolframScript like so: +Testing the evaluation function can be done by running the script using the Wolfram Engine / WolframScript like so (see [Run the Script](#run-the-script) above for the one-time `scripts/setup-toolkit.sh`/`LF_TOOLKIT_PATH` setup this depends on): ```bash -wolframscript -f evaluation_function.wl request.json response.json +wolframscript -f ./toolkit-wolfram/Bootstrap.wl request.json response.json ``` > [!NOTE] @@ -115,7 +127,7 @@ curl --location 'http://localhost:8080/wolframEvaluationFunction' \ "answer":"Sin[p x + q]", "response":"Sin[a x + b]", "params":{ - "comparisonType":"structure", + "type":"structure", "named_variables":"{x}", "correct_response_feedback":"Your answer is correct!", "incorrect_response_feedback":"Your answer is incorrect!" diff --git a/evaluate.m b/evaluate.m index 1a02ec7..81be2b8 100644 --- a/evaluate.m +++ b/evaluate.m @@ -18,7 +18,8 @@ (* Declare package context *) BeginPackage["evaluate`"]; -EvaluationFunction[type_, answer_, response_, params_] := Module[{result, feedback}, +EvaluationFunction[answer_, response_, params_] := Module[{result, feedback, type}, + type = params["type"]; Print["Running Evaluation Function"]; result = evalQ[type, answer, response, params]; feedback = If[result["is_correct"], @@ -35,7 +36,7 @@ Begin["`Private`"]; -equalQNumeric[answer_, response_, params_] := Module[{tolerance}, +equalQNumeric[answer_, response_, params_] := Module[{tolerance, error}, Print["Evaluating Equal Numeric"]; tolerance = If[Lookup[params, "tolerance_is_absolute", False], Lookup[params, "tolerance", 0], @@ -43,7 +44,7 @@ ]; error = Abs[answer - response]; <| - "error" -> error, + "error" -> Null, "is_correct" -> TrueQ[error <= tolerance] |> ] diff --git a/evaluation_function.wl b/evaluation_function.wl deleted file mode 100644 index af7b7a4..0000000 --- a/evaluation_function.wl +++ /dev/null @@ -1,86 +0,0 @@ -(* ::Package:: *) - -(* The code that handles incoming messages and passes them to evaluate or preview accordingly*) - -<< "evaluate.m"; -<< "preview.m"; - - -processEvaluate[jsonData_] := Module[{result, requestData, answer, response, params, type}, - requestData = jsonData["params"]; - answer = requestData["answer"]; - response = requestData["response"]; - params = requestData["params"]; - type = params["type"]; - - Print["Evaluating Response Against Answer"]; - result = EvaluationFunction[type, answer, response, params]; - Print["Output: ", result]; - - If[result["error"] != Null, - Return[ - <| "command" -> "eval", - "error" -> <| - "message" -> result["error"] - |> - |> - ] - ]; - - <| "command" -> "eval", - "result" -> <| - "is_correct" -> result["is_correct"], - "feedback" -> result["feedback"] - |> - |> -] - -processPreview[jsonData_] := Module[{result, requestData, response}, - requestData = jsonData["params"]; - response = requestData["response"]; - - Print["Previewing Response"]; - - result = PreviewFunction[response]; - Print["Result: ", result]; - - If[result["error"] != Null, - Return[ - <| "command" -> "eval", - "error" -> <| - "message" -> result["error"] - |> - |> - ] - ]; - - <| "command" -> "preview", - "result" -> - <|"preview" -> result|> - |> -] - -evalQuestionIO = Function[ - Module[{jsonData, command, resultAssoc, response}, - jsonData = Import[#1, "JSON"] //. List :> Association; - - Print["Input"]; - Print[jsonData]; - - command = Lookup[jsonData, "command", "unknown"]; - - resultAssoc = Which[ - command == "eval", processEvaluate[jsonData], - command == "preview", processPreview[jsonData], - True, <| "status" -> "error", "message" -> "Incorrect command" |> - ]; - - Print["Outputted JSON"]; - Print[resultAssoc]; - Export[#2, resultAssoc, "JSON", "Compact" -> True] - ] -]; - -argv = Rest[$ScriptCommandLine]; -evalQuestionIO[argv[[1]], argv[[2]]] - diff --git a/preview.m b/preview.m index 224ef2e..793bbf1 100644 --- a/preview.m +++ b/preview.m @@ -18,7 +18,7 @@ (* Declare package context *) BeginPackage["preview`"]; -PreviewFunction[response_] := Module[{latexString, wolframString, parsedResponse}, +PreviewFunction[response_, params_] := Module[{latexString, wolframString, parsedResponse}, Print["Running Preview Function"]; Print["Preview Input:", response]; diff --git a/scripts/setup-toolkit.sh b/scripts/setup-toolkit.sh new file mode 100755 index 0000000..2da2eac --- /dev/null +++ b/scripts/setup-toolkit.sh @@ -0,0 +1,25 @@ +#!/bin/bash +# Fetches the pinned toolkit-wolfram version (see .toolkit-wolfram-version) +# into ./toolkit-wolfram, for running/testing this evaluation function +# locally without Docker/podman. Re-run any time to reset to the pinned +# version (e.g. after bumping .toolkit-wolfram-version). +# +# Usage: +# scripts/setup-toolkit.sh +# +# Then, to run the evaluation function directly: +# export LF_TOOLKIT_PATH=./toolkit-wolfram +# wolframscript -f ./toolkit-wolfram/Bootstrap.wl request.json response.json + +set -euo pipefail + +cd "$(dirname "$0")/.." + +VERSION=$(cat .toolkit-wolfram-version) +DEST="toolkit-wolfram" + +rm -rf "$DEST" +git clone --branch "$VERSION" --depth 1 \ + https://github.com/lambda-feedback/toolkit-wolfram.git "$DEST" + +echo "Fetched toolkit-wolfram $VERSION -> $DEST"