From 5fddcdca4d3b8d62cc0a379095d91b19d9d679fc Mon Sep 17 00:00:00 2001 From: Eric Law <39393654+acn-ericlaw@users.noreply.github.com> Date: Mon, 2 Nov 2026 15:48:29 -0800 Subject: [PATCH] refactor(llm-helper): graduate the LLM helper from examples/ to extensions/ The helper is no longer a sample to copy but a function host an installation runs to give its engines an LLM, so it moves to extensions/llm-helper/; examples/ keeps the demo app. Files, routes, contract and configuration keys are unchanged. The sdist and the type check include the new folder, the tests load the helper from it, and the README commands, the demo app's links, the consumer folder table, the test report and the CHANGELOG (Unreleased, Changed 2) follow. The Node.js twin still lives in mercury-nodejs examples/llm-helper until it moves the same way. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 5 +++++ docs/test-reports/llm-helper-certification.md | 4 ++-- examples/demo-app/README.md | 4 ++-- {examples => extensions}/llm-helper/README.md | 8 ++++---- {examples => extensions}/llm-helper/llm_helper.py | 2 +- .../llm-helper/resources/application.yml | 2 +- memory/instructions.md | 5 +++-- pyproject.toml | 5 +++-- system/AGENTS.md | 1 + tests/test_llm_helper.py | 4 ++-- 10 files changed, 24 insertions(+), 16 deletions(-) rename {examples => extensions}/llm-helper/README.md (98%) rename {examples => extensions}/llm-helper/llm_helper.py (99%) rename {examples => extensions}/llm-helper/resources/application.yml (97%) diff --git a/CHANGELOG.md b/CHANGELOG.md index fa2589b..e8607a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,11 @@ found and `AppConfig.resources_dir` names the folder; the startup log reports `Configuration: `. **Upgrade note:** an application that kept `resources/` in the working directory with its application file elsewhere passes `--config`, or moves the folder next to the file. +2. **The LLM helper graduates from an example to an extension: `extensions/llm-helper/`.** It is no longer a sample to copy + but a function host an installation runs to give its engines an LLM, so it moves out of `examples/`, which keeps the + demo app. The files, the routes, the contract and the configuration keys are unchanged; the sdist ships the new folder. + **Upgrade note:** start it as `mercury-serve extensions/llm-helper/llm_helper.py` (or + `python extensions/llm-helper/llm_helper.py`); a script or a deployment that names the old path changes it. ## Version 4.12.21, 10/7/2026 diff --git a/docs/test-reports/llm-helper-certification.md b/docs/test-reports/llm-helper-certification.md index 0823dc8..dfcba78 100644 --- a/docs/test-reports/llm-helper-certification.md +++ b/docs/test-reports/llm-helper-certification.md @@ -14,7 +14,7 @@ keywords: [llm helper, llm.chat, llm.stream, claude, anthropic, streaming, certi pairs, the three layers, the progressive-rendering evidence and the findings — is the [engine report](https://accenture.github.io/mercury-composable/test-reports/llm-helper-certification/), which the Rust engine's docs carry too. The helper is -[`examples/llm-helper`](https://github.com/Accenture/mercury-python/tree/main/examples/llm-helper); its README holds the contract.* +[`extensions/llm-helper`](https://github.com/Accenture/mercury-python/tree/main/extensions/llm-helper); its README holds the contract.* ## Verified without a credential @@ -100,7 +100,7 @@ model generation. The full account of the surfaces and the gates is Finding 7 of pip install -e '.[dev]' pytest tests/test_llm_helper.py # token-free export ANTHROPIC_API_KEY=... # live: the credential reaches the helper only -mercury-serve examples/llm-helper/llm_helper.py -Dlog.format=compact -Dllm.log.batches=true +mercury-serve extensions/llm-helper/llm_helper.py -Dlog.format=compact -Dllm.log.batches=true ``` The commands for the engines, the deploy folder and the `curl` calls for each layer are in the diff --git a/examples/demo-app/README.md b/examples/demo-app/README.md index 4763534..54cb243 100644 --- a/examples/demo-app/README.md +++ b/examples/demo-app/README.md @@ -3,7 +3,7 @@ The minimal polyglot function host: seven small functions that show what a Mercury engine can reach over Event-over-HTTP and what a function host does with a call. Run it to watch a Python function answer an engine, and copy it as the starting point for your own app. It never calls a model and needs -no credential; the LLM routes are a separate app, [`llm-helper`](../llm-helper/README.md). +no credential; the LLM routes are a separate app, [`llm-helper`](../../extensions/llm-helper/README.md). ```text caller ── REST ──> Java or Rust engine ── Event-over-HTTP ──> demo app @@ -26,7 +26,7 @@ Either way the app reads `resources/application.yml` next to `demo_app.py`, whic command runs from. The app listens on port 8086 (`rest.server.port` in `resources/application.yml`; `-Drest.server.port=8090` -overrides it). The [LLM helper](../llm-helper/README.md) defaults to 8086 as well, so give one of the two +overrides it). The [LLM helper](../../extensions/llm-helper/README.md) defaults to 8086 as well, so give one of the two another port when you run both on one machine. Map a route from the engine's `event-over-http.yaml`: ```yaml diff --git a/examples/llm-helper/README.md b/extensions/llm-helper/README.md similarity index 98% rename from examples/llm-helper/README.md rename to extensions/llm-helper/README.md index 6b52aca..1450b41 100644 --- a/examples/llm-helper/README.md +++ b/extensions/llm-helper/README.md @@ -20,8 +20,8 @@ speaks the same contract. Both run one shared vector file, so they cannot drift ```bash pip install 'mercury-composable[llm]' # the SDK is this app's one dependency export ANTHROPIC_API_KEY=... # from the environment, never from a config file -mercury-serve examples/llm-helper/llm_helper.py -python examples/llm-helper/llm_helper.py # the same without mercury-serve +mercury-serve extensions/llm-helper/llm_helper.py +python extensions/llm-helper/llm_helper.py # the same without mercury-serve ``` Either way the helper reads `resources/application.yml` next to `llm_helper.py`, whichever directory the @@ -33,7 +33,7 @@ AWS credential chain, and a region is required: ```bash pip install 'mercury-composable[llm,bedrock]' export AWS_REGION=us-east-1 # or llm.aws.region; the AWS credential chain signs each call -mercury-serve examples/llm-helper/llm_helper.py -Dllm.backend=bedrock +mercury-serve extensions/llm-helper/llm_helper.py -Dllm.backend=bedrock ``` If the account is enabled for Bedrock's classic runtime and not for its Anthropic-shaped endpoint (the default @@ -41,7 +41,7 @@ surface answers `403 ... not available for this account` on a model the console' surface and name the model as an inference profile id, exactly as the account lists it: ```bash -mercury-serve examples/llm-helper/llm_helper.py -Dllm.backend=bedrock -Dllm.aws.api=runtime \ +mercury-serve extensions/llm-helper/llm_helper.py -Dllm.backend=bedrock -Dllm.aws.api=runtime \ -Dllm.model=us.anthropic.claude-haiku-4-5-20251001-v1:0 ``` diff --git a/examples/llm-helper/llm_helper.py b/extensions/llm-helper/llm_helper.py similarity index 99% rename from examples/llm-helper/llm_helper.py rename to extensions/llm-helper/llm_helper.py index 4495d12..90df2de 100644 --- a/examples/llm-helper/llm_helper.py +++ b/extensions/llm-helper/llm_helper.py @@ -6,7 +6,7 @@ control flow while the LLM advises within it. Run: pip install 'mercury-composable[llm]' - mercury-serve examples/llm-helper/llm_helper.py + mercury-serve extensions/llm-helper/llm_helper.py The credential comes from the environment, never from a config file: ANTHROPIC_API_KEY for the Claude API, the AWS credential chain (or AWS_BEARER_TOKEN_BEDROCK) for AWS Bedrock. diff --git a/examples/llm-helper/resources/application.yml b/extensions/llm-helper/resources/application.yml similarity index 97% rename from examples/llm-helper/resources/application.yml rename to extensions/llm-helper/resources/application.yml index fe1e322..f9ac7af 100644 --- a/examples/llm-helper/resources/application.yml +++ b/extensions/llm-helper/resources/application.yml @@ -3,7 +3,7 @@ # resources folder next to the application file (this one), never from the working directory. # Any key can be overridden at # run time with the engines' -D syntax, e.g.: -# mercury-serve examples/llm-helper/llm_helper.py -Dllm.model=claude-haiku-4-5 +# mercury-serve extensions/llm-helper/llm_helper.py -Dllm.model=claude-haiku-4-5 application.name: 'llm-helper' info.app.description: 'Mercury Composable LLM helper (llm.chat / llm.stream on the Anthropic SDK)' diff --git a/memory/instructions.md b/memory/instructions.md index 63a5a8c..237c4be 100644 --- a/memory/instructions.md +++ b/memory/instructions.md @@ -27,8 +27,9 @@ the engines by design. The legacy pre-composable language pack lives in git hist `cli.py` (`mercury-serve`); `py.typed` ships type information. - `tests/` — pytest suite; `tests/vectors/vectors.json` holds the **golden conformance vectors shared with the Java and Rust engines** (the wire-compatibility proof). -- `examples/demo-app/demo_app.py` — the minimal runnable function app (README and `resources/` beside it); - `examples/llm-helper/llm_helper.py` — the LLM helper app (`llm.chat`, `llm.stream`, `llm.health` on the Anthropic SDK). +- `examples/demo-app/demo_app.py` — the minimal runnable function app (README and `resources/` beside it). +- `extensions/llm-helper/llm_helper.py` — the LLM helper (`llm.chat`, `llm.stream`, `llm.health` on the + Anthropic SDK), graduated from `examples/` to an extension on 2026-11-02 (Eric). - Root `README.md` — the consumer-facing guide (quick start, function contract, configuration, wire compatibility); the root `AGENTS.md` fork routes consumers there. diff --git a/pyproject.toml b/pyproject.toml index 74f170d..fcce8bf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,7 +38,7 @@ Repository = "https://github.com/Accenture/mercury-python" Changelog = "https://github.com/Accenture/mercury-python/blob/main/CHANGELOG.md" [project.optional-dependencies] -# the LLM helper app (examples/llm-helper) - the package itself stays SDK-free +# the LLM helper (extensions/llm-helper) - the package itself stays SDK-free llm = ["anthropic>=1,<2"] # AWS Bedrock as the helper's backend (llm.backend: bedrock) - boto3 signs each call with the # AWS credential chain; the Claude API install stays lean without it @@ -58,6 +58,7 @@ only-include = [ "src/mercury_composable", "tests", "examples", + "extensions", "README.md", "CHANGELOG.md", "LICENSE.txt", @@ -81,7 +82,7 @@ extend-select = ["I"] # by agent-memory and excluded. typeCheckingMode = "standard" reportMissingParameterType = "error" -include = ["src", "examples", "tests"] +include = ["src", "examples", "extensions", "tests"] exclude = ["agent-skills", ".venv"] [tool.pytest.ini_options] diff --git a/system/AGENTS.md b/system/AGENTS.md index 79396d9..fe8405c 100644 --- a/system/AGENTS.md +++ b/system/AGENTS.md @@ -34,6 +34,7 @@ wrapper's docs cover only the Python function surface. |------|------| | `src/` | the `mercury_composable` package — Event API host, `PostOffice`, envelope codec, actuators | | `examples/` | runnable reference functions | +| `extensions/` | function hosts that add a capability to an installation — the LLM helper (`llm.chat`, `llm.stream`) | | `docs/` | the MkDocs guide site source | | `tests/` | unit and integration tests | diff --git a/tests/test_llm_helper.py b/tests/test_llm_helper.py index eb954b4..44f5c34 100644 --- a/tests/test_llm_helper.py +++ b/tests/test_llm_helper.py @@ -1,4 +1,4 @@ -"""The LLM helper (examples/llm-helper/llm_helper.py) - token-free. +"""The LLM helper (extensions/llm-helper/llm_helper.py) - token-free. One shared contract file, tests/vectors/llm-helper-vectors.json (byte-identical in mercury-nodejs), drives every case below against a fake of the Anthropic SDK: the exact SDK @@ -29,7 +29,7 @@ from mercury_composable import AppException _TESTS = Path(__file__).resolve().parent -_HELPER = _TESTS.parent / "examples" / "llm-helper" / "llm_helper.py" +_HELPER = _TESTS.parent / "extensions" / "llm-helper" / "llm_helper.py" _VECTORS = _TESTS / "vectors" / "llm-helper-vectors.json" # the Node.js twin pins the same digest: change the file in both packs or in neither _VECTORS_SHA256 = "7bfffbf36c52488c246f250f36afc4e8012f119fd56c93e9de1cbe93bf5988b6"