Skip to content
Open
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
3 changes: 2 additions & 1 deletion .github/workflows/cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ on:
type: choice
options:
- docling-metrics-core
# Add future packages here
- docling-metric-hello-world
- docling-metric-hello-world-cpp

jobs:
code-checks:
Expand Down
6 changes: 6 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,9 @@ jobs:
uses: ./.github/workflows/job-checks.yml
secrets:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
build-wheels:
if: ${{ github.event_name == 'push' || (github.event.pull_request.head.repo.full_name != 'docling-project/docling-metrics') }}
uses: ./.github/workflows/wheels.yml
with:
publish: false
package: docling-metric-hello-world-cpp
45 changes: 36 additions & 9 deletions .github/workflows/pypi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,27 +8,54 @@ permissions:
contents: read

jobs:
build-and-publish:
meta:
runs-on: ubuntu-latest
outputs:
package: ${{ steps.meta.outputs.package }}
is_native: ${{ steps.meta.outputs.is_native }}
steps:
- uses: actions/checkout@v5
- name: Identify package from tag
id: meta
run: |
TAG="${{ github.event.release.tag_name }}"
PKG="${TAG%-v*}"
echo "package=${PKG}" >> "$GITHUB_OUTPUT"
if grep -q "scikit_build_core" "packages/${PKG}/pyproject.toml" 2>/dev/null; then
echo "is_native=true" >> "$GITHUB_OUTPUT"
else
echo "is_native=false" >> "$GITHUB_OUTPUT"
fi

build-and-publish-pure:
if: needs.meta.outputs.is_native == 'false'
needs: [meta]
runs-on: ubuntu-latest
environment:
name: pypi
permissions:
id-token: write # IMPORTANT: mandatory for trusted publishing
id-token: write
steps:
- uses: actions/checkout@v5
- name: Install uv and set the python version
uses: astral-sh/setup-uv@v6
with:
enable-cache: true
- name: Identify package from tag
id: meta
run: |
TAG="${{ github.event.release.tag_name }}"
PKG="${TAG%-v*}"
echo "package=${PKG}" >> "$GITHUB_OUTPUT"
- name: Install dependencies
run: uv sync --all-extras --all-packages
- name: Build
run: uv build --package ${{ steps.meta.outputs.package }}
run: uv build --package ${{ needs.meta.outputs.package }}
- name: Publish distribution 📦 to PyPI
uses: pypa/gh-action-pypi-publish@release/v1

build-and-publish-native:
if: needs.meta.outputs.is_native == 'true'
needs: [meta]
uses: ./.github/workflows/wheels.yml
with:
publish: true
package: ${{ needs.meta.outputs.package }}
secrets: inherit
permissions:
id-token: write
contents: write
83 changes: 83 additions & 0 deletions .github/workflows/wheels.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
on:
workflow_call:
inputs:
publish:
type: boolean
description: "If true, the packages will be published."
default: false
package:
type: string
description: "Package to build"
required: true

jobs:
build_sdist:
name: Build sdist
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: astral-sh/setup-uv@v6
- name: Install build tools
run: uv pip install --group build
- name: Build sdist
run: uv build --package ${{ inputs.package }}
- uses: actions/upload-artifact@v4
with:
name: sdist
path: dist/

build_wheels:
name: Build wheels for ${{ matrix.python-version }} on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
python-version: ["3.10", "3.11", "3.12", "3.13"]
os: ["ubuntu-latest", "macos-14", "windows-2025"]
steps:
- uses: actions/checkout@v5
- uses: astral-sh/setup-uv@v6
with:
python-version: ${{ matrix.python-version }}
- name: Set CPython tag [posix]
if: runner.os != 'Windows'
run: |
version=${{ matrix.python-version }}
echo "CIBW_BUILD=cp${version/./}" >> "$GITHUB_ENV"
- name: Set CPython tag [windows]
if: runner.os == 'Windows'
shell: pwsh
run: |
$version = "${{ matrix.python-version }}"
$cp_version = "cp$($version -replace '\.', '')"
Add-Content -Path $env:GITHUB_ENV -Value "CIBW_BUILD=$cp_version"
- name: Install build tools
run: uv pip install --group build
- name: Build wheels
working-directory: packages/${{ inputs.package }}
env:
CIBW_BUILD_VERBOSITY: 2
run: uv run python -m cibuildwheel --output-dir ../../dist
- uses: actions/upload-artifact@v4
with:
name: wheels-${{ matrix.python-version }}-${{ matrix.os }}
path: dist/

publish:
name: Publish to PyPI
if: inputs.publish
needs: [build_sdist, build_wheels]
runs-on: ubuntu-latest
environment:
name: pypi
url: https://pypi.org/p/${{ inputs.package }}
permissions:
id-token: write
contents: write
steps:
- uses: actions/download-artifact@v4
with:
merge-multiple: true
path: dist/
- name: Publish distribution to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
*~
scratch/
.worktrees/

# Created by https://www.toptal.com/developers/gitignore/api/python,macos,virtualenv,pycharm,visualstudiocode,emacs,vim,jupyternotebooks
# Edit at https://www.toptal.com/developers/gitignore?templates=python,macos,virtualenv,pycharm,visualstudiocode,emacs,vim,jupyternotebooks
Expand Down
18 changes: 18 additions & 0 deletions packages/docling-metric-hello-world-cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
cmake_minimum_required(VERSION 3.18)
project(docling_metric_hello_world_cpp LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

find_package(pybind11 CONFIG REQUIRED)

pybind11_add_module(_hello_world_cpp cpp/hello_world.cpp)

target_compile_features(_hello_world_cpp PRIVATE cxx_std_17)

install(
TARGETS _hello_world_cpp
LIBRARY DESTINATION docling_metric_hello_world_cpp
RUNTIME DESTINATION docling_metric_hello_world_cpp
ARCHIVE DESTINATION docling_metric_hello_world_cpp
)
35 changes: 35 additions & 0 deletions packages/docling-metric-hello-world-cpp/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# docling-metric-hello-world-cpp

A minimal example metric implementation demonstrating a C++ (pybind11) backend
on top of `docling-metrics-core`.

## Overview

This package provides a simple "Hello World" metric that:
- Accepts string payloads as input samples
- Always returns a score of `1.0` for each sample evaluation
- Demonstrates a C++ extension using pybind11 and scikit-build-core

## Installation

```bash
pip install docling-metric-hello-world-cpp
```

## Usage

```python
from docling_metric_hello_world_cpp import HelloWorldMetric, StringInputSample

metric = HelloWorldMetric()

sample_a = StringInputSample(id="sample-1", payload="Hello")
sample_b = StringInputSample(id="sample-1", payload="World")

result = metric.evaluate_sample(sample_a, sample_b)
print(result.score) # Always 1.0
```

## License

MIT
17 changes: 17 additions & 0 deletions packages/docling-metric-hello-world-cpp/cpp/hello_world.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#include <pybind11/pybind11.h>
#include <string>

namespace py = pybind11;

double evaluate_sample(
const std::string &id,
const std::string &payload_a,
const std::string &payload_b
) {
return 1.0;
}

PYBIND11_MODULE(_hello_world_cpp, m) {
m.doc() = "Hello World C++ metric bindings";
m.def("evaluate_sample", &evaluate_sample, "Return constant score");
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
"""Docling metric hello world C++ package."""

from .hello_world_metric import (
HelloWorldAggregateResult,
HelloWorldMetric,
HelloWorldSampleResult,
StringInputSample,
)

__all__ = [
"HelloWorldAggregateResult",
"HelloWorldMetric",
"HelloWorldSampleResult",
"StringInputSample",
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from typing import Final

def evaluate_sample(id: str, payload_a: str, payload_b: str) -> float: ...

__all__: Final = ["evaluate_sample"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
"""Hello World metric implementation using a C++ backend."""

from typing import Annotated, Iterable, Tuple

from docling_metrics_core.base_types import (
BaseAggregateResult,
BaseInputSample,
BaseMetric,
BaseSampleResult,
)
from pydantic import Field

from ._hello_world_cpp import evaluate_sample as cpp_evaluate_sample


class StringInputSample(BaseInputSample):
"""Input sample with a string payload."""

payload: Annotated[str, Field(description="The string payload for this sample")]


class HelloWorldSampleResult(BaseSampleResult):
"""Result of evaluating a single sample pair."""

score: Annotated[
float, Field(description="The evaluation score (always 1.0 for this metric)")
]


class HelloWorldAggregateResult(BaseAggregateResult):
"""Aggregated result from multiple sample evaluations."""

mean_score: Annotated[
float, Field(description="Mean score across all evaluated samples")
]


class HelloWorldMetric(BaseMetric):
"""A minimal example metric that always returns 1.0, backed by C++."""

def evaluate_sample( # type: ignore[override]
self, sample_a: StringInputSample, sample_b: StringInputSample
) -> HelloWorldSampleResult:
score = float(
cpp_evaluate_sample(sample_a.id, sample_a.payload, sample_b.payload)
)
return HelloWorldSampleResult(id=sample_a.id, score=score)

def aggregate( # type: ignore[override]
self, results: Iterable[HelloWorldSampleResult]
) -> HelloWorldAggregateResult:
results_list = list(results)
sample_count = len(results_list)
if sample_count == 0:
return HelloWorldAggregateResult(sample_count=0, mean_score=0.0)

total_score = sum(r.score for r in results_list)
return HelloWorldAggregateResult(
sample_count=sample_count, mean_score=total_score / sample_count
)

def evaluate_dataset( # type: ignore[override]
self, sample_pairs: Iterable[Tuple[StringInputSample, StringInputSample]]
) -> HelloWorldAggregateResult:
results = [
self.evaluate_sample(sample_a, sample_b)
for sample_a, sample_b in sample_pairs
]
return self.aggregate(results)
Loading