Skip to content
Draft
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
59 changes: 59 additions & 0 deletions .github/workflows/publish-inspect-python.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# ─── Before the first publish (integrations-inspect-python-v0.1.0) ───────────
#
# 1. Tighten the SDK dependency. integrations/inspect-python/pyproject.toml pins
# `learning-commons-evaluators>=0.2.0`, but the integration requires the
# LLMGeneratorProtocol / llm_provider support that ships in SDK 0.3.0. Raise
# the floor to `>=0.3.0` once 0.3.0 is on PyPI — and do not publish this
# package before then, or installs resolve a SDK without llm_provider.
#
# 2. Remove `release-as: "0.1.0"` from the integrations/inspect-python block in
# release-please-config.json IMMEDIATELY after 0.1.0 lands on PyPI. It is a
# sticky pin: every later release-please PR stays at 0.1.0 until removed, and
# `skip-existing: true` below silently hides the duplicate-version rejection.
#
# ─────────────────────────────────────────────────────────────────────────────

name: Publish Inspect integration

on:
release:
types: [published]
workflow_dispatch:

jobs:
publish:
if: github.event_name == 'workflow_dispatch' || startsWith(github.event.release.tag_name, 'integrations-inspect-python-v')
runs-on: ubuntu-latest
environment: pypi
permissions:
contents: read
id-token: write
defaults:
run:
working-directory: integrations/inspect-python
concurrency:
group: publish-inspect-python
cancel-in-progress: false
steps:
- name: ⬇️ Checkout repo
uses: actions/checkout@v6

- name: 🐍 Setup Python
uses: actions/setup-python@v6
with:
python-version: "3.13"

- name: 📥 Install build tooling
run: python -m pip install --upgrade build twine

- name: 📦 Build sdist + wheel
run: python -m build

- name: 🧪 Validate distribution metadata
run: python -m twine check dist/*

- name: 🚀 Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
packages-dir: integrations/inspect-python/dist
skip-existing: true
63 changes: 63 additions & 0 deletions .github/workflows/test-inspect-python.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
name: 🔍 Test Inspect integration

# Runs only when the integration package itself changes, or when the Python SDK
# it depends on changes (a SDK change — including a version bump — can break the
# integration, so we re-verify here too).
on:
push:
branches:
- main
paths:
- "integrations/inspect-python/**"
- "sdks/python/**"
- "sdks/settings/**"
- ".github/workflows/test-inspect-python.yml"
pull_request:
paths:
- "integrations/inspect-python/**"
- "sdks/python/**"
- "sdks/settings/**"
- ".github/workflows/test-inspect-python.yml"

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
verify:
name: Verify (Python ${{ matrix.python-version }})
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.10", "3.11", "3.12", "3.13"]
steps:
- name: Checkout
uses: actions/checkout@v6

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}

- name: Install in-repo SDK + integration package
# Install the SDK from source first: the integration relies on
# LLMGeneratorProtocol (SDK >=0.3.0), which is not yet on PyPI. The
# editable install satisfies the >=0.2.0 floor while providing the
# unreleased protocol code, so CI tests against current monorepo state.
run: |
python -m pip install --upgrade pip
pip install -e ./sdks/python
pip install -e "./integrations/inspect-python[dev]"

- name: Lint
working-directory: integrations/inspect-python
run: |
python -m ruff check .
python -m ruff format --check .

- name: Test
working-directory: integrations/inspect-python
run: python -m pytest
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@
.venv/
__pycache__/
*.pyc
*.egg-info/
dist/
build/
.pytest_cache/
.mypy_cache/
.ruff_cache/

# Inspect AI eval logs
logs/

# Jupyter Notebook
.ipynb_checkpoints/
Expand Down
3 changes: 2 additions & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"evals/prompts": "1.5.0",
"sdks/python": "0.2.0",
"sdks/typescript": "0.7.0"
"sdks/typescript": "0.7.0",
"integrations/inspect-python": "0.1.0"
}
6 changes: 6 additions & 0 deletions integrations/inspect-python/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
*.egg-info/
dist/
build/
__pycache__/
.pytest_cache/
.mypy_cache/
1 change: 1 addition & 0 deletions integrations/inspect-python/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Changelog
82 changes: 82 additions & 0 deletions integrations/inspect-python/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# learning-commons-inspect-scorers

[Inspect AI](https://inspect.aisi.org.uk/) scorer wrappers for the [Learning Commons evaluators](https://github.com/learning-commons-org/evaluators) SDK.

## Installation

```bash
pip install learning-commons-inspect-scorers
```

> **Note:** Requires `learning-commons-evaluators>=0.2.0`. During local development
> install the SDK from the repo root first:
> ```bash
> pip install -e sdks/python
> pip install -e integrations/inspect-python
> ```

## Usage

### Grade Level Appropriateness scorer

Evaluates whether model output (or generated artifact files) is written at the
appropriate reading level for a target K-12 grade band.

```python
from inspect_ai import Task, task
from inspect_ai.dataset import csv_dataset, FieldSpec
from inspect_ai.solver import generate
from learning_commons_inspect_scorers import gla_scorer

@task
def my_eval():
return Task(
dataset=csv_dataset("samples.csv"), # requires target_grade column
solver=[generate()],
scorer=gla_scorer(),
)
```

The dataset CSV must include a `target_grade` metadata column with one of:
`K-1`, `2-3`, `4-5`, `6-8`, `9-10`, `11-CCR`.

### Scoring text your task produced (not the completion)

By default the scorer grades `state.output.completion`. To grade text from
somewhere else — files your solver wrote, a specific message, filtered content —
pass a `text_fn`. This keeps task-specific knowledge (file layout, naming,
formatting) in your task, not in this package:

```python
def student_artifact_text(state):
# read whatever your task produced; return None to skip the sample
return "\n\n".join(read_my_files(state)) or None

scorer=gla_scorer(text_fn=student_artifact_text)
```

### Re-scoring an existing log from the CLI

Once installed, scorers are registered via Inspect's entry point system:

```bash
inspect score logs/my-eval.eval --scorer learning_commons_inspect_scorers/gla_scorer
```

## Configuration

| Parameter | Default | Description |
|---|---|---|
| `grader_model` | `"anthropic/claude-opus-4-8"` | Inspect model string for the grading LLM. Uses Inspect's model system — no separate API key configuration needed. |
| `target_grade_key` | `"target_grade"` | Metadata key holding the expected grade band. |
| `allow_adjacent` | `True` | If `True`, the one grade band above or below the target also passes. |
| `text_fn` | `None` | Callable `(TaskState) -> str \| None` returning the text to grade. Defaults to `state.output.completion`. Return `None`/empty to skip the sample. Caller keeps text within the evaluator's input-length limit. |

## Development

```bash
# From repo root
pip install -e sdks/python
pip install -e "integrations/inspect-python[dev]"
pytest integrations/inspect-python/tests/
```
75 changes: 75 additions & 0 deletions integrations/inspect-python/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
[build-system]
requires = ["setuptools>=61", "wheel"]
build-backend = "setuptools.build_meta"

[project]
name = "learning-commons-inspect-scorers"
version = "0.1.0"
description = "Inspect AI scorer wrappers for Learning Commons evaluators"
readme = "README.md"
license = { text = "MIT" }
requires-python = ">=3.10"
authors = [{ name = "Learning Commons" }]
keywords = ["education", "evaluators", "inspect", "evals", "scoring"]
classifiers = [
"Development Status :: 3 - Alpha",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Topic :: Education",
]
dependencies = [
"learning-commons-evaluators>=0.2.0",
# Score.unscored() — used in every skip/error path of gla_scorer — was added in inspect-ai 0.3.214.
"inspect-ai>=0.3.214",
]

[project.optional-dependencies]
dev = [
"pytest>=7.0.0",
"pytest-asyncio>=0.21.0",
"ruff>=0.9.0",
"mypy>=1.14.0",
]

[project.urls]
Homepage = "https://github.com/learning-commons-org/evaluators"
Repository = "https://github.com/learning-commons-org/evaluators/tree/main/integrations/inspect-python"
Documentation = "https://docs.learningcommons.org/evaluators"
"Bug Tracker" = "https://github.com/learning-commons-org/evaluators/issues"

# Registers scorers with Inspect's component discovery via setuptools entry points.
# Once installed, scorers are accessible as e.g. `learning_commons_inspect_scorers/gla_scorer`
# from the CLI: inspect score log.eval --scorer learning_commons_inspect_scorers/gla_scorer
[project.entry-points.inspect_ai]
learning_commons_inspect_scorers = "learning_commons_inspect_scorers._registry"

[tool.setuptools.packages.find]
where = ["src"]

[tool.setuptools.package-data]
learning_commons_inspect_scorers = ["py.typed"]

[tool.pytest.ini_options]
asyncio_mode = "auto"
testpaths = ["tests"]

[tool.ruff]
target-version = "py310"
line-length = 100

[tool.ruff.lint]
select = ["E", "W", "F", "I", "UP", "B", "SIM"]
ignore = ["E501"]

[tool.mypy]
python_version = "3.10"
mypy_path = ["src", "tests"]
explicit_package_bases = true
plugins = ["pydantic.mypy"]
warn_unused_configs = true
show_error_codes = true
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
"""Learning Commons Inspect scorers — Inspect AI wrappers for LC evaluators."""

from learning_commons_inspect_scorers.adapter import InspectModelAdapter
from learning_commons_inspect_scorers.gla import gla_scorer

__all__ = ["InspectModelAdapter", "gla_scorer"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
"""Entry point registry — imported by Inspect via the inspect_ai setuptools entry point.

Importing this module registers all scorers with Inspect's component system,
making them accessible by name (e.g. learning_commons_inspect_scorers/gla_scorer)
from both the Python API and the CLI.
"""

from learning_commons_inspect_scorers.gla import (
gla_scorer, # noqa: F401 — import triggers @scorer registry side-effect
)
Loading