From b570e9b89ab25068c949130fc864623dfc0cb3f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A9d=C3=A9ric=20Hurier=20=28Fmind=29?= Date: Mon, 10 Aug 2026 20:34:16 +0200 Subject: [PATCH] feat(docs): check external links on a schedule, and fix the nine that were dead The neptune.ai breakage in #39 was found by a reader, not by the repository. A course is mostly links to other people's work, and nothing here noticed when one of them rotted. A full lychee run over the README and every chapter found nine genuinely dead links besides the neptune ones: - three of this project's own MLOps Community articles, which moved to /blog/ when that site restructured - Microsoft Learn's VS Code module, VS Code Live Share, Jupyter Book's cell hiding page, ZenML's starter guide, the OpenRAIL announcement, and Marquez, all moved or renamed - Censius, whose TLS handshake now fails outright; the two explainability definitions it backed now point at Molnar's Interpretable Machine Learning `check:links` is deliberately NOT part of `check`. The first run reported 63 failures against 706 unique URLs, and only 9 were real: 37 were HTTP 429 and 14 were bot-blocking 403s from hosts that serve a reader fine. A gate that cries wolf 85% of the time teaches people to ignore it. It runs monthly and on demand from links.yml instead, with a .lycheeignore for the hosts that refuse scanners and 429 accepted as proof a host is alive rather than evidence it is broken. The whole set now reports 0 errors. --- .github/workflows/links.yml | 30 +++++++++++++++++++ .lycheeignore | 14 +++++++++ docs/1. Initializing/1.6. VS Code.md | 2 +- docs/2. Prototyping/2.0. Notebooks.md | 2 +- docs/2. Prototyping/2.5. Modeling.md | 2 +- docs/3. Productionizing/3.2. Paradigms.md | 2 +- .../3.6. VS Code Workspace.md | 2 +- docs/6. Sharing/6.1. License.md | 2 +- docs/6. Sharing/6.5. Workstations.md | 2 +- docs/7. Observability/7.1. Monitoring.md | 2 +- docs/7. Observability/7.3. Lineage.md | 2 +- docs/7. Observability/7.5. Explainability.md | 4 +-- mise.lock | 14 +++++++++ mise.toml | 12 ++++++++ 14 files changed, 81 insertions(+), 11 deletions(-) create mode 100644 .github/workflows/links.yml create mode 100644 .lycheeignore diff --git a/.github/workflows/links.yml b/.github/workflows/links.yml new file mode 100644 index 0000000..9a0fd22 --- /dev/null +++ b/.github/workflows/links.yml @@ -0,0 +1,30 @@ +name: Links +# The course is mostly links to other people's work, and those rot silently: nothing in +# push CI notices when a referenced article disappears. This is the counterpart — slow, +# network-bound, and scheduled, so a dead link is found by a job nobody is waiting on +# rather than by a reader. +on: + schedule: + - cron: "23 4 1 * *" + workflow_dispatch: +permissions: + contents: read +concurrency: + group: links + cancel-in-progress: true +jobs: + check: + name: External link check + runs-on: ubuntu-24.04 + timeout-minutes: 20 + steps: + - name: Checkout repository + uses: actions/checkout@v7 + with: + persist-credentials: false # the checker only reads the checkout + - name: Install mise toolchain + uses: jdx/mise-action@v4 + with: + cache: true + - name: Check external links + run: mise run check:links diff --git a/.lycheeignore b/.lycheeignore new file mode 100644 index 0000000..ceba578 --- /dev/null +++ b/.lycheeignore @@ -0,0 +1,14 @@ +# Hosts that answer a link checker with 403 or 429 rather than the page a reader gets. +# These are not broken links: they are anti-bot measures and rate limits. Keeping them +# out means a finding in `mise run check:links` is worth acting on. +https://medium\.com/.* +https://.*\.medium\.com/.* +https://.*\.readthedocs\.io/.* +https://www\.reddit\.com/.* +https://neptune\.ai/.* +https://twitter\.com/.* +https://x\.com/.* +https://www\.linkedin\.com/.* +# Illustrative URLs in the prose, not references to anything on the internet. +https?://localhost.* +https?://127\.0\.0\.1.* diff --git a/docs/1. Initializing/1.6. VS Code.md b/docs/1. Initializing/1.6. VS Code.md index 2f1a615..ca9efcb 100644 --- a/docs/1. Initializing/1.6. VS Code.md +++ b/docs/1. Initializing/1.6. VS Code.md @@ -33,7 +33,7 @@ To accelerate your learning curve, explore these excellent resources: - **[Official Documentation](https://code.visualstudio.com/docs)**: The definitive source for every feature. - **[VS Code Tips and Tricks](https://code.visualstudio.com/docs/getstarted/tips-and-tricks)**: A collection of shortcuts and best practices to boost your productivity. -- **[Microsoft Learn](https://docs.microsoft.com/en-us/learn/modules/use-vscode/)**: Free, hands-on tutorials for various development scenarios. +- **[Microsoft Learn](https://learn.microsoft.com/en-us/training/browse/?products=vs-code)**: Free, hands-on tutorials for various development scenarios. - **[Official YouTube Channel](https://www.youtube.com/@code)**: Visual guides and tutorials for all skill levels. ## Which extensions are essential for MLOps? diff --git a/docs/2. Prototyping/2.0. Notebooks.md b/docs/2. Prototyping/2.0. Notebooks.md index 0dce67a..35ca4ef 100644 --- a/docs/2. Prototyping/2.0. Notebooks.md +++ b/docs/2. Prototyping/2.0. Notebooks.md @@ -55,7 +55,7 @@ Saving notebook outputs in your Git repository is generally not recommended due - **Collaboration**: Sharing outputs can lead to conflicts and confusion when multiple users work on the same notebook, as the outputs may not match the code execution. - **Code Focus**: The primary focus of version control systems like Git is on tracking changes to code, not data or outputs. Including outputs can distract from the main purpose of the repository. -Instead of saving outputs directly in the repository, consider using tools like [Jupyter's nbconvert](https://nbconvert.readthedocs.io/en/latest/) to export notebooks to different formats (e.g., HTML, PDF) that can be shared on other platforms or included in documentation. You can also use [Jupyter's built-in cell tags](https://jupyterbook.org/interactive/hiding.html) to hide or exclude specific cells from the exported version, allowing you to control what information is shared while keeping the repository clean and focused on code. +Instead of saving outputs directly in the repository, consider using tools like [Jupyter's nbconvert](https://nbconvert.readthedocs.io/en/latest/) to export notebooks to different formats (e.g., HTML, PDF) that can be shared on other platforms or included in documentation. You can also use [Jupyter's built-in cell tags](https://jupyterbook.org/en/stable/interactive/hiding.html) to hide or exclude specific cells from the exported version, allowing you to control what information is shared while keeping the repository clean and focused on code. ## Additional Resources diff --git a/docs/2. Prototyping/2.5. Modeling.md b/docs/2. Prototyping/2.5. Modeling.md index 67e3cde..3e0c5fe 100644 --- a/docs/2. Prototyping/2.5. Modeling.md +++ b/docs/2. Prototyping/2.5. Modeling.md @@ -11,7 +11,7 @@ description: Learn how to build, refine, and compare machine learning models dir [Pipelines in machine learning](https://scikit-learn.org/stable/modules/compose.html#pipeline) provide a streamlined way to organize sequences of data preprocessing and modeling steps. They encapsulate a series of data transformations followed by the application of a model, facilitating both simplicity and efficiency in the development process. Pipelines can be broadly categorized as follows: - **Model Pipeline**: Focuses specifically on sequences related to preparing data for machine learning models and applying these models. For instance, scikit-learn's [`Pipeline`](https://scikit-learn.org/stable/modules/compose.html#pipeline) class allows for chaining [preprocessors](https://scikit-learn.org/stable/modules/preprocessing.html) and [estimators](https://scikit-learn.org/stable/developers/develop.html). -- **Data Pipeline**: Encompasses a wider scope, including steps for data gathering, cleaning, and transformation. Tools such as [Prefect](https://www.prefect.io/) and [ZenML](https://docs.zenml.io/user-guide/starter-guide/create-an-ml-pipeline) offer capabilities for building comprehensive data pipelines. +- **Data Pipeline**: Encompasses a wider scope, including steps for data gathering, cleaning, and transformation. Tools such as [Prefect](https://www.prefect.io/) and [ZenML](https://docs.zenml.io/user-guides/starter-guide/create-an-ml-pipeline) offer capabilities for building comprehensive data pipelines. - **Orchestration Pipeline**: Targets the automation of a series of tasks, including data and model pipelines, ensuring they execute in an orderly fashion or under specific conditions. Examples include [Apache Airflow](https://airflow.apache.org/) for creating directed acyclic graphs (DAGs) and [Vertex AI for managing ML workflows](https://cloud.google.com/vertex-ai/docs/pipelines/introduction). For the purposes of this discussion, we'll focus on **model pipelines**, crucial for efficiently prototyping machine learning solutions. The code example are based on [scikit-learn pipeline](https://scikit-learn.org/stable/modules/generated/sklearn.pipeline.Pipeline.html), as this toolkit is simple to understand and its concept can be generalized to other types of pipeline like [Dagster](https://dagster.io/), [Prefect](https://www.prefect.io/), or [Metaflow](https://metaflow.org/). diff --git a/docs/3. Productionizing/3.2. Paradigms.md b/docs/3. Productionizing/3.2. Paradigms.md index b601190..774c1b9 100644 --- a/docs/3. Productionizing/3.2. Paradigms.md +++ b/docs/3. Productionizing/3.2. Paradigms.md @@ -321,7 +321,7 @@ Therefore, the most effective approach is to build an object-oriented foundation ## What is the hybrid OOP and functional style? -A [hybrid style](https://mlops.community/finding-harmony-in-mlops-balancing-functional-and-object-oriented-approaches-%E2%98%AF/) strategically combines OOP's structure with FP's discipline to create clean, robust, and maintainable code. It involves using objects as the primary architectural component but applying functional principles to their design. +A [hybrid style](https://mlops.community/blog/finding-harmony-in-mlops-balancing-functional-and-object-oriented-approaches) strategically combines OOP's structure with FP's discipline to create clean, robust, and maintainable code. It involves using objects as the primary architectural component but applying functional principles to their design. Key principles of the hybrid style include: diff --git a/docs/3. Productionizing/3.6. VS Code Workspace.md b/docs/3. Productionizing/3.6. VS Code Workspace.md index 3feefb5..ec93f5a 100644 --- a/docs/3. Productionizing/3.6. VS Code Workspace.md +++ b/docs/3. Productionizing/3.6. VS Code Workspace.md @@ -94,7 +94,7 @@ For team projects, it's best to use **Workspace Settings** to ensure everyone sh ## How can you optimize your VS Code User Settings for productivity? -While workspace settings standardize team environments, your personal user settings can significantly boost your individual productivity. This article from the MLOps Community provides an in-depth guide: [How to configure VS Code for AI, ML and MLOps development in Python 🛠️️](https://mlops.community/how-to-configure-vs-code-for-ai-ml-and-mlops-development-in-python-%f0%9f%9b%a0%ef%b8%8f%ef%b8%8f/). +While workspace settings standardize team environments, your personal user settings can significantly boost your individual productivity. This article from the MLOps Community provides an in-depth guide: [How to configure VS Code for AI, ML and MLOps development in Python 🛠️️](https://mlops.community/blog/how-to-configure-vs-code-for-ai-ml-and-mlops-development-in-python). Key takeaways include: - **Essential Extensions**: Beyond project recommendations, consider tools like GitLens (for Git history), Docker, and Thunder Client (for API testing). diff --git a/docs/6. Sharing/6.1. License.md b/docs/6. Sharing/6.1. License.md index cc2c23b..aadfb5f 100644 --- a/docs/6. Sharing/6.1. License.md +++ b/docs/6. Sharing/6.1. License.md @@ -64,7 +64,7 @@ For a guided experience, use tools like [ChooseALicense.com](https://choosealice Yes, licensing in AI/ML is more complex because you're often dealing with three distinct components: the **code**, the **model artifacts** (the trained weights), and the **data**. Each may require its own license. - **Licensing the Code**: Standard software licenses like MIT or Apache 2.0 work perfectly well for the source code that trains or runs your model. -- **Licensing the Model**: This is an emerging area. You must decide if you are licensing the *use* of the model. Can others use it commercially? Can they build upon it? Specialized licenses like [OpenRAIL](https://www.licenses.ai/blog/2022/8/18/bigscience-open-rail-m-license) (Responsible AI Licenses) are designed to restrict the use of models in certain sensitive or unethical applications. +- **Licensing the Model**: This is an emerging area. You must decide if you are licensing the *use* of the model. Can others use it commercially? Can they build upon it? Specialized licenses like [OpenRAIL](https://huggingface.co/blog/open_rail) (Responsible AI Licenses) are designed to restrict the use of models in certain sensitive or unethical applications. - **Licensing the Data**: Data licensing is critical. You must ensure you have the right to use the training data and clearly state how it can be used by others. [Creative Commons](https://creativecommons.org/) licenses are a popular standard for datasets, allowing you to specify whether commercial use is allowed or if derivatives must be shared. ## How do you manage licenses for your dependencies? diff --git a/docs/6. Sharing/6.5. Workstations.md b/docs/6. Sharing/6.5. Workstations.md index c5916f0..4d230c0 100644 --- a/docs/6. Sharing/6.5. Workstations.md +++ b/docs/6. Sharing/6.5. Workstations.md @@ -47,7 +47,7 @@ By committing this file to your repository, you ensure that anyone who opens the Cloud workstations act as a centralized hub for development, making collaboration seamless: -- **Real-Time Co-Editing**: Tools like [Visual Studio Code Live Share](https://code.visualstudio.com/learn/collaboration/live-share) are natively integrated, allowing multiple developers to edit, debug, and run code in the same session simultaneously. +- **Real-Time Co-Editing**: Tools like [Visual Studio Code Live Share](https://visualstudio.microsoft.com/services/live-share/) are natively integrated, allowing multiple developers to edit, debug, and run code in the same session simultaneously. - **Shared Terminals**: You can share a terminal session, which is invaluable for collaborative debugging or walking a teammate through a complex command-line workflow. - **Consistent Environment**: Because everyone is working in an identical, containerized environment, you eliminate time wasted debugging environment-specific discrepancies. If the code runs in one developer's workstation, it will run in everyone's. diff --git a/docs/7. Observability/7.1. Monitoring.md b/docs/7. Observability/7.1. Monitoring.md index 7bc2fec..d47f9cb 100644 --- a/docs/7. Observability/7.1. Monitoring.md +++ b/docs/7. Observability/7.1. Monitoring.md @@ -159,5 +159,5 @@ This process creates a detailed report that helps you quickly diagnose if and ho - **[Example from the MLOps Python Package](https://github.com/fmind/mlops-python-package/blob/main/src/bikes/jobs/evaluations.py)** - **[MLflow Evaluate API](https://mlflow.org/docs/latest/model-evaluation/index.html)** - **[EvidentlyAI](https://www.evidentlyai.com/)** -- [Is AI/ML Monitoring just Data Engineering? 🤔](https://mlops.community/is-ai-ml-monitoring-just-data-engineering-%F0%9F%A4%94/) +- [Is AI/ML Monitoring just Data Engineering? 🤔](https://mlops.community/blog/is-ai-ml-monitoring-just-data-engineering) - [Model Monitoring: What it is and why it's so hard](https://christophergs.com/machine%20learning/2020/03/14/how-to-monitor-machine-learning-models/) diff --git a/docs/7. Observability/7.3. Lineage.md b/docs/7. Observability/7.3. Lineage.md index 887bbe7..00d5f1e 100644 --- a/docs/7. Observability/7.3. Lineage.md +++ b/docs/7. Observability/7.3. Lineage.md @@ -122,4 +122,4 @@ To build a world-class lineage system, consider these best practices: - **[Example from the MLOps Python Package](https://github.com/fmind/mlops-python-package/blob/main/src/bikes/io/datasets.py)** - [MLflow Tracking Dataset](https://mlflow.org/docs/latest/tracking.html#tracking-datasets) -- [OpenLineage](https://openlineage.io/) and [Marquez](https://marquezproject.github.io/) +- [OpenLineage](https://openlineage.io/) and [Marquez](https://marquezproject.ai/) diff --git a/docs/7. Observability/7.5. Explainability.md b/docs/7. Observability/7.5. Explainability.md index d8315ec..34c55ad 100644 --- a/docs/7. Observability/7.5. Explainability.md +++ b/docs/7. Observability/7.5. Explainability.md @@ -21,10 +21,10 @@ description: Explore the significance of explainability in AI/ML, learn how to u Explainability techniques are typically categorized by their scope: -- **[Local Explainability](https://censius.ai/blogs/global-local-cohort-explainability#blogpost-toc-13)**: Explains a *single* prediction. It answers the question: "Why did the model make this specific decision for this particular input?" This is vital for analyzing individual cases, such as understanding why a specific customer's loan application was denied. +- **[Local Explainability](https://christophm.github.io/interpretable-ml-book/shap.html)**: Explains a *single* prediction. It answers the question: "Why did the model make this specific decision for this particular input?" This is vital for analyzing individual cases, such as understanding why a specific customer's loan application was denied. - **Example**: [SHAP (SHapley Additive exPlanations)](https://shap.readthedocs.io/en/latest/) calculates the impact of each feature on an individual prediction, showing which factors pushed the prediction higher or lower. -- **[Global Explainability](https://censius.ai/blogs/global-local-cohort-explainability#blogpost-toc-11)**: Explains the *overall* behavior of a model across the entire dataset. It answers the question: "What are the most important features driving the model's predictions in general?" This is useful for understanding the model's core logic. +- **[Global Explainability](https://christophm.github.io/interpretable-ml-book/feature-importance.html)**: Explains the *overall* behavior of a model across the entire dataset. It answers the question: "What are the most important features driving the model's predictions in general?" This is useful for understanding the model's core logic. - **Example**: Feature importance plots from tree-based models provide a global view by ranking features based on their average contribution to the model's accuracy. Effective model analysis often requires both: global explainability to understand the general strategy of the model and local explainability to audit and trust individual outcomes. diff --git a/mise.lock b/mise.lock index 3bb81d2..5b75493 100644 --- a/mise.lock +++ b/mise.lock @@ -56,6 +56,20 @@ checksum = "sha256:1dd4dc7b4c50efb1f9d9122cd6535c793738d6e59751c228d49f768ec9dbb url = "https://github.com/evilmartians/lefthook/releases/download/v2.1.10/lefthook_2.1.10_MacOS_arm64.gz" url_api = "https://api.github.com/repos/evilmartians/lefthook/releases/assets/470227499" +[[tools.lychee]] +version = "0.24.2" +backend = "aqua:lycheeverse/lychee" + +[tools.lychee."platforms.linux-x64"] +checksum = "sha256:73657a111819a30c47c08352896796f23d64e4eb2b3ed39b6d32149241566fc5" +url = "https://github.com/lycheeverse/lychee/releases/download/lychee-v0.24.2/lychee-x86_64-unknown-linux-musl.tar.gz" +url_api = "https://api.github.com/repos/lycheeverse/lychee/releases/assets/409959271" + +[tools.lychee."platforms.macos-arm64"] +checksum = "sha256:c9d3740ea2d891854d37116c9fba840f37b6e7c89d330e7db84ac333631c4977" +url = "https://github.com/lycheeverse/lychee/releases/download/lychee-v0.24.2/lychee-aarch64-apple-darwin.tar.gz" +url_api = "https://api.github.com/repos/lycheeverse/lychee/releases/assets/409957951" + [[tools.python]] version = "3.14.7" backend = "core:python" diff --git a/mise.toml b/mise.toml index 8ba47ff..996ed98 100644 --- a/mise.toml +++ b/mise.toml @@ -12,6 +12,7 @@ actionlint = "latest" dprint = "latest" gitleaks = "latest" lefthook = "latest" +lychee = "latest" # The course teaches readers to pin Python 3.14; the site that teaches it pins the same # interpreter, so `mise-action` in CI provisions it instead of inheriting the runner's. python = "3.14" @@ -81,6 +82,17 @@ run = ["actionlint", "zizmor --offline .github/workflows/"] description = "Build the site in strict mode to catch broken links and config" run = "uv run mkdocs build --strict" +[tasks."check:links"] +description = "Check external links in the README and every chapter (lychee)" +# Deliberately NOT in `check`. It takes minutes, hits ~700 external URLs, and most of +# what it reports is other people's rate limiting: a full run found 63 failures, of which +# 37 were HTTP 429 and 14 were bot-blocking 403s against only 9 genuinely dead links. +# Wiring that into the commit gate would train everyone to ignore a red check. It runs +# monthly and on demand from `.github/workflows/links.yml` instead, where a human reads it. +# 429 is accepted on purpose: a host that rate-limits a scanner is a host that is up, so +# treating it as a failure would report the opposite of what the check exists to find. +run = "lychee --no-progress --max-concurrency 8 --timeout 20 --accept 200..=299,429 README.md AGENTS.md 'docs/**/*.md'" + [tasks."check:format"] description = "Check formatting and config validity" run = [