# Maintainers: Any dependency bumps should
# invoke uv with python 3.12 
[build-system]
requires = ["setuptools>=64", "setuptools_scm[toml]>=8"]
build-backend = "setuptools.build_meta"

[tool.setuptools_scm]
# This is necessary for dependabot to work successfully.
# Dependabot tries to build in a directory that doesn't have a .git directory.
# So setuptools_scm is unable to compute a version, thus we need a fallabck.
fallback_version = "0.0.0+unknown"

[tool.setuptools.packages.find]
where = ["src"]
include = ["inspect_evals*"]

[tool.setuptools.package-data]
"inspect_evals" = ["*/eval.yaml"]

[tool.ruff]
extend-exclude = [
    "agent_artefacts",
    "docs",
    "src/inspect_evals/cybench/challenges",
    "src/inspect_evals/cyberseceval_2/vulnerability_exploit/challenges",
    "src/inspect_evals/terminal_bench_2/challenges",
    "src/inspect_evals/mlrc_bench/orig_benchmark_corrections",
]
src = ["src"]
target-version = "py310"

[tool.ruff.lint]
select = [
    "E",      # pycodestyle errors
    "W",      # pycodestyle warnings
    "F",      # flake8
    "D",      # pydocstyle
    "I",      # isort
    "SIM101", # duplicate isinstance
    # "RET",    # flake8-return 694 errors (509 fixed, 185 remaining). TASK?
    # "RUF",    # ruff rules 285 errors (36 fixed, 249 remaining). TASK
    # "B",      # flake8-bugbear 97 errors (28 fixed, 69 remaining). TASK?
    "YTT",    # flake8-2020
    "LOG",    # flake8-logging
    # "PT",     # flake8-pytest-style 76 errors, 51 unsafe fixes TASK
    # "SIM",    # flake8-simplify 97 errors (13 fixed, 84 remaining). TASK
    # "TD",     # flake8-todos 26 errors TASK?
    # "PTH",    # flake8-use-pathlib 159 errors (1 fixed, 158 remaining). TASK
    "NPY",    # numpy-specific-rules-npy
    # "N",      # pep8-naming 35 errors TASK
    # "PL",     # pylint 497 errors (14 fixed, 483 remaining). TASK
    "PLE",    # pylint error
    "PLR",    # pylint refactor
    "PLW",    # pylint warning
    # "UP",     # pyupgrade 810 errors (803 fixed, 7 remaining). TASK
    "UP006", # Prevent List and Dict, use list and dict instead.
    "UP007", # Prevent Union[X, Y], use [X | Y] instead.
    "UP045", # Prevent Optional[X], use X | None instead.
    "FURB",   # refurb
    # "TRY",    # tryceratops 266 errors, 8 unsafe fixes TASK?
    "TID252", # relative-imports
]
ignore = [
    "E203", # whitespace-before-punctuation
    "E501", # line-too-long
    "D10", # missing docstrings
    "D212", # docstring multi-line-summary-first-line
    "D415", # docstring missing-terminal-punctuation
    "PLR0911", # Too many return statements
    "PLR0912", # Too many branches
    "PLR0913", # Too many arguments in function definition
    "PLR0915", # Too many statements
    # "RET504", # unnecessary-assign 
    # "RET505", # superfluous-else-return
    # "RUF001", # ambiguous-unicode-character-string
    # "B905", # zip-without-explicit-strict
 ]

[tool.ruff.lint.per-file-ignores]
"**/{tests}/*" = ["PLR2004"]  # Allow magic numbers in tests
"src/inspect_evals/osworld/container/code/*" = ["TID252"]  # Relative imports required for container code
"src/inspect_evals/gdm_self_reasoning/data/**/*" = ["ALL"]  # This is data, not our code
"src/inspect_evals/gdm_self_proliferation/data/**/*" = ["ALL"]  # This is data, not our code
"src/inspect_evals/persistbench/prompts.py" = ["W291"]  # Prompt text includes trailing-space artifacts; preserve for reproducibility

# Large suites with lots of magic numbers we grandfather in
"src/inspect_evals/agentdojo/task_suites/**/*" = ["PLR2004"]
"src/inspect_evals/agentharm/benchmark/benign_grading_functions.py" = ["PLR2004"]
"src/inspect_evals/agentharm/benchmark/harmful_grading_functions.py" = ["PLR2004"]


[tool.ruff.lint.flake8-tidy-imports]
ban-relative-imports = "all"  # See: https://docs.astral.sh/ruff/settings/#lint_flake8-tidy-imports_ban-relative-imports

[tool.ruff.lint.pydocstyle]
convention = "google"

[tool.pytest.ini_options]
minversion = "7.0"
addopts = "-rA --doctest-modules --color=yes --durations=10"
testpaths = ["tests"]
doctest_optionflags = ["NORMALIZE_WHITESPACE", "IGNORE_EXCEPTION_DETAIL"]
norecursedirs = [
    "tests/test_helpers",
    "tests/test_package",
    "tests/test_task_list",
]
asyncio_mode = "auto"
asyncio_default_fixture_loop_scope = "function"
log_level = "WARNING"
env_files = [".env"]
markers = [
    "dataset_download: tests that download datasets (enabled by default; disable with -m 'not dataset_download' or RUN_DATASET_DOWNLOAD_TESTS=0)",
    "huggingface: marks tests that depend on downloading a model from Hugging Face",
    "slow: marks tests that are slow to run; enable with --runslow or RUN_SLOW_TESTS=1",
    "k8s: marks tests requiring k8s sandbox support; enable with --runk8s or RUN_K8S_TESTS=1",
    "docker: marks tests that pull or build docker images",
    "posix_only: marks tests that require POSIX systems (Linux/macOS)",
]

[tool.mypy]
exclude = [
    "build",
    "agent_artefacts",
    "(?:^|/)_resources/",
    "src/inspect_evals/gdm_in_house_ctf/challenges",
    "src/inspect_evals/gdm_self_reasoning/data",
    "src/inspect_evals/cyberseceval_2/vulnerability_exploit/challenges",
    "src/inspect_evals/swe_bench/tests",
    "src/inspect_evals/cybench",
    "src/inspect_evals/terminal_bench_2/challenges",
    "src/inspect_evals/mlrc_bench/orig_benchmark_corrections",
]
warn_unused_ignores = true
no_implicit_reexport = true
strict_equality = true
warn_redundant_casts = true
warn_unused_configs = true

[[tool.mypy.overrides]]
module = ["inspect_evals.*"]
warn_return_any = true
disallow_untyped_defs = true
disallow_any_generics = true
disallow_subclassing_any = true
disallow_untyped_calls = true
disallow_incomplete_defs = true
check_untyped_defs = true
disallow_untyped_decorators = true
extra_checks = true
disable_error_code = "unused-ignore"

[[tool.mypy.overrides]]
module = ["datasets", "datasets.*"]    # `datasets` doesn't have type annotations
ignore_missing_imports = true

[[tool.mypy.overrides]]
module = ["bs4", "bs4.*"]    # `bs4` has type checking issues
ignore_missing_imports = true

[[tool.mypy.overrides]]
module = ["sklearn", "sklearn.*"]    # `sklearn` doesn't have complete type stubs
ignore_missing_imports = true

[[tool.mypy.overrides]]
module = ["drain3", "drain3.*"]    # `drain3` doesn't have type stubs
ignore_missing_imports = true

[[tool.mypy.overrides]]
module = ["kernelbench", "kernelbench.*"]    # `drain3` doesn't have type stubs
follow_untyped_imports = true

[tool.check-wheel-contents]
ignore = ["W002", "W009", "W004"]

[tool.scriv]
format = "md"
fragment_directory = "changelog.d"
categories = ["New Evals", "Existing Evals", "Other"]
skip_fragments = "{README,TEMPLATE}.*"
new_fragment_template = "file: TEMPLATE.md"

[project]
name = "inspect_evals"
description = "Collection of large language model evaluations"
authors = [{ name = "UK AI Security Institute" }]
readme = "README.md"
requires-python = ">=3.10"
license = "MIT"
dynamic = ["version"]
classifiers = [
    "Development Status :: 4 - Beta",
    "Environment :: Console",
    "Intended Audience :: Science/Research",
    "Intended Audience :: Developers",
    "Natural Language :: English",
    "Programming Language :: Python :: 3",
    "Topic :: Scientific/Engineering :: Artificial Intelligence",
    "Typing :: Typed",
    "Operating System :: OS Independent",
]
dependencies = [
    "backoff>=2.2.0",
    "datasets>=4.5.0",
    "huggingface_hub>=1.2.0",
    "hf_xet",
    "inspect_ai >= 0.3.158",
    "jinja2",
    "numpy>=1.26.0",
    "pillow>=11.3.0",
    "pydantic>=2.10.0",
    "pyyaml>=5.1.0",
    "requests>=2.32.0",
    "tiktoken>=0.11.0",
    "toml>=0.10.2",
]

[project.urls]
"Source Code" = "https://github.com/UKGovernmentBEIS/inspect_evals"
"Issue Tracker" = "https://github.com/UKGovernmentBEIS/inspect_evals/issues"

[project.entry-points.inspect_ai]
inspect_evals = "inspect_evals._registry"

# This used to be an extra like all other extra, but after migrating to `uv`
# We made it a proper dependency group so `uv` installs it by default
# If you want to use pip, use a newer version of pip, 25.1+, that supports
# `pip install --group dev`
[dependency-groups]
dev = [
    "inspect-scout>=0.3.0",  # llm_scanner API was added in 0.3.0
    "mypy",
    "pandas",
    "pandas-stubs",
    "plotly_express",
    "polars",
    "polyfactory",
    "pre-commit",
    "pytest",
    "pytest-custom-exit-code",
    "pytest-mock",
    "pytest-asyncio",
    "pytest-cov",
    "pytest-reportlog",
    "pytest-dotenv",
    "pytest-rerunfailures>=15.0",
    "pytest-xdist",
    "ruff==0.13.2",  # match version specified in .pre-commit-config.yaml and .github/workflows/build.yml
    "scriv",
    "types-Pillow",
    "types-PyYAML",
    "types-requests",
    # For local development with `uv` we want to have the version of inspect_ai from git
    "inspect_ai @ git+https://github.com/UKGovernmentBEIS/inspect_ai"
]

# <=py311
test_py311_or_lower = [
  "livebench",
  "mlebench ; python_version >= '3.11'",
  "nltk",
  "sentencepiece>=0.2.1",
]

# >=py312
test_py312_or_higher = [
  "cvebench ; python_version >= '3.12'",
  "livebench ; sys_platform != 'win32' or python_version < '3.13'",
  "mlebench",
  "nltk",
  "sentencepiece>=0.2.1",
]

ifeval = [
    "instruction-following-eval",
]

livebench = [
  "livebench ; sys_platform != 'win32' or python_version < '3.13'",
  "nltk",
  "sentencepiece>=0.2.1",
]

mle_bench = [
  "mlebench ; python_version >= '3.11'",
  "docker",
]

cve_bench = ["cvebench ; python_version >= '3.12'"]

[tool.uv]
# Limit to Python 3.10-3.13 (matches CI). Python 3.14 lacks wheels for torch, ray, etc.
environments = [
    "python_version >= '3.10' and python_version < '3.14'",
]

# add torch override for kernelbench and novelty_bench conflict - temp fix
# livebench pins pandas==2.2.2, re-pin it to 2.2.3 for python 3.13 support
# The changelog between 2.2.2 and 2.2.3 is minimal: https://pandas.pydata.org/docs/whatsnew/v2.2.3.html
# Remove if/when this PR gets merged: https://github.com/LiveBench/LiveBench/pull/284
override-dependencies = [
    # pydra-config (kernelbench dep) pins dill<0.4, but inspect-scout>=0.3.0 needs dill>=0.4.0.
    # pydra-config 0.16.0 was yanked, so override dill to allow both to coexist.
    "dill>=0.4.0",
    "pandas==2.2.3",
    "torch>=2.9.1",
]

# cve_bench group only needs to resolve for Python >= 3.12 (cvebench requires it)
[tool.uv.dependency-groups]
cve_bench = {requires-python = ">=3.12"}
test_py312_or_higher = {requires-python = ">=3.12"}

[tool.uv.sources]
# If developing against a local copy of inspect_ai, use this, but don't commit the change
# inspect_ai = { path = "../inspect_ai", editable = true }

"instruction-following-eval" = { git = "https://github.com/josejg/instruction_following_eval", rev = "0c495b2" }
# Pinned to the last version that doesn't have jinja (not jinja2) as a dependency
# Unpin when they merge https://github.com/LiveBench/LiveBench/pull/290 
livebench = { git = "https://github.com/LiveBench/LiveBench.git", rev = "1c4c655" }
mlebench  = { git = "https://github.com/openai/mle-bench.git",   rev = "1d391b0" }
# Contains support for K8s. To be repointed to https://github.com/uiuc-kang-lab/cve-bench if the K8s changes are accepted by CVE Bench maintainers.
cvebench = { git = "https://github.com/Scott-Simmons/cve-bench.git", rev = "92541add2ebd89e5b15ed260eb5d0e9b5102c33e"}
kernelbench = { git = "https://github.com/ScalingIntelligence/KernelBench", rev = "2f65279"}

[project.optional-dependencies]
# Per-benchmark groups
b3 = ["openai", "rouge_score", "tenacity", "click", "python-dotenv"]
agentdojo = ["pydantic[email]", "deepdiff"]
swe_bench = ["swebench>=3.0.15", "docker", "jsonlines"]
swe_lancer = ["docker", "types-docker"]
math = ["sympy", "antlr4-python3-runtime~=4.11.0"]
worldsense = ["pandas"]
mind2web = ["beautifulsoup4", "types-beautifulsoup4", "lxml", "lxml-stubs"]
sevenllm = ["jieba==0.42.1", "sentence_transformers>=5.1.1", "rouge==1.0.1", "tf-keras ; python_version < '3.13'"]
scicode = ["gdown", "h5py", "scipy", "sympy"]
ifeval = ["instruction_following_eval", "langdetect"]
ahb = ["matplotlib"]
medqa = ["bioc"]
niah = ["pandas"]

kernelbench = ["kernelbench"]
core_bench = ["scipy"]
healthbench = ["scikit-learn"]
personality = ["huggingface-hub"]
sciknoweval = ["nltk", "rouge_score", "rdkit", "rdchiral", "gdown", "gensim; python_version < '3.13'", "scipy"]
gdm_stealth = ["tabulate", "scipy", "immutabledict", "pandas", "python-dateutil"]
cybench = ["inspect-cyber==0.1.0"]
cybergym = ["inspect-cyber>=0.1.0"]
bold = ["detoxify", "vaderSentiment", "transformers>=5.0.0", "torch"]
makemesay = ["nltk"]
abstention_bench = [
    "scikit-learn",
    "hydra-core>=1.4.0.dev1",  # for compatibility with antlr4-python3-runtime>=4.11.0
    "omegaconf>=2.4.0.dev2",  # required by hydra-core>=1.4.0.dev1, must be pinned for uv lock
    "torch",
    "wget",
    "loguru",
    "gdown",
    "jsonlines",
]
vimgolf = ["vimgolf==0.5.1"]
ifevalcode = ["tree-sitter", "tree-sitter-cpp"]
gdpval = ["huggingface_hub[cli]"]
agentic_misalignment = ["bs4"]

novelty_bench = [
    "transformers>=4.57.1",
    "torch>=2.9.1",
    "accelerate>=1.11.0",
    "protobuf>=6.33.1",
    "sentencepiece>=0.2.1",
]

gdm_capabilities = [
    "google-genai>=1.56.0",
    "rich",
    "python-dateutil",
]

gdm_self_proliferation = ["rich"]

paperbench = ["drain3"]

test = [
  "inspect_evals[b3,sevenllm,stealth,core_bench,mind2web,bold,sciknoweval,agentdojo,cybench,swe_bench,swe_lancer,abstention_bench,fortress,vimgolf,novelty_bench,gdpval,paperbench,ifeval,ifevalcode,gdm_capabilities,gdm_self_proliferation,cybergym]",
  "inspect_evals[kernelbench] ; sys_platform != 'win32'",
]
doc = ["quarto-cli", "jupyter"]
dist = ["twine", "build"]
