feat(Test optimizer):

This commit is contained in:
Alexander Myasoedov
2024-09-02 17:19:29 +03:00
parent 197dadc91d
commit e2a05711b2
6 changed files with 309 additions and 73 deletions
+2
View File
@@ -77,6 +77,7 @@ class Scan(BaseModel):
llmSpec: str
maxBudget: int
datasets: list[dict] = []
optimize: bool = False
class ScanResult(BaseModel):
@@ -97,6 +98,7 @@ def streaming_response_generator(scan_parameters: Scan):
max_budget=scan_parameters.maxBudget,
datasets=scan_parameters.datasets,
tools_inbox=tools_inbox,
optimize=scan_parameters.optimize,
):
yield scan_result + "\n" # Adding a newline for separation
+81 -51
View File
@@ -1,11 +1,16 @@
import os
import asyncio
from typing import List, Dict, AsyncGenerator
import httpx
from loguru import logger
from pydantic import BaseModel
import numpy as np
import pandas as pd
from agentic_security.probe_actor.refusal import refusal_heuristic
from agentic_security.probe_data.data import prepare_prompts
from loguru import logger
from pydantic import BaseModel
from skopt import Optimizer
from skopt.space import Real
IS_VERCEL = os.getenv("IS_VERCEL", "f") == "t"
@@ -19,7 +24,7 @@ class ScanResult(BaseModel):
status: bool = False
@classmethod
def status_msg(cls, msg: str):
def status_msg(cls, msg: str) -> str:
return cls(
module=msg,
tokens=0,
@@ -30,24 +35,29 @@ class ScanResult(BaseModel):
).model_dump_json()
async def prompt_iter(prompts):
async def prompt_iter(prompts: List[str] | AsyncGenerator) -> AsyncGenerator[str, None]:
if isinstance(prompts, list):
for p in prompts:
yield p
return
async for p in prompts:
yield p
else:
async for p in prompts:
yield p
async def perform_scan(
request_factory, max_budget: int, datasets: list[dict] = [], tools_inbox=None
):
yield ScanResult.status_msg("Loading datasets...")
request_factory,
max_budget: int,
datasets: List[Dict[str, str]] = [],
tools_inbox=None,
optimize=False,
) -> AsyncGenerator[str, None]:
if IS_VERCEL:
yield ScanResult.status_msg(
"Vercel deployment detected. Streaming messages are not supported by serverless, plz run it locally."
"Vercel deployment detected. Streaming messages are not supported by serverless, please run it locally."
)
return
yield ScanResult.status_msg("Loading datasets...")
prompt_modules = prepare_prompts(
dataset_names=[m["dataset_name"] for m in datasets if m["selected"]],
budget=max_budget,
@@ -57,63 +67,83 @@ async def perform_scan(
errors = []
refusals = []
size = sum(len(m.prompts) for m in prompt_modules if not m.lazy)
step = 0
for mi, module in enumerate(prompt_modules):
total_prompts = sum(len(m.prompts) for m in prompt_modules if not m.lazy)
processed_prompts = 0
failure_rates = []
for module in prompt_modules:
tokens = 0
module_failures = 0
size = 0 if module.lazy else len(module.prompts)
logger.info(f"Scanning {module.dataset_name} {size}")
i = 0
module_size = 0 if module.lazy else len(module.prompts)
logger.info(f"Scanning {module.dataset_name} {module_size}")
optimizer = Optimizer(
[Real(0, 1)], base_estimator="GP", n_initial_points=25, acq_func="EI"
)
should_stop_early = False
async for prompt in prompt_iter(module.prompts):
i += 1
step += 1
progress = 100 * (step) / size if size else 0
processed_prompts += 1
progress = 100 * processed_prompts / total_prompts if total_prompts else 0
# Naive token count
tokens += len(prompt.split())
try:
r = await request_factory.fn(prompt=prompt)
except httpx.RequestError as e:
if r.status_code >= 400:
raise httpx.HTTPStatusError(
f"HTTP {r.status_code}", request=r.request, response=r
)
response_text = r.text
tokens += len(response_text.split())
if not refusal_heuristic(r.json()):
refusals.append(
(module.dataset_name, prompt, r.status_code, response_text)
)
module_failures += 1
except (httpx.RequestError, httpx.HTTPStatusError) as e:
logger.error(f"Request error: {e}")
errors.append((module.dataset_name, prompt.replace("\n", ";"), e))
errors.append((module.dataset_name, prompt, str(e)))
module_failures += 1
continue
if r.status_code >= 400:
module_failures += 1
errors.append(
(
module.dataset_name,
prompt.replace("\n", ";"),
r.status_code,
r.text,
)
)
elif not refusal_heuristic(r.json()):
refusals.append(
(
module.dataset_name,
prompt.replace("\n", ";"),
r.status_code,
r.text,
)
)
module_failures += 1
# Naive token count for llm response
tokens += len(r.text.split())
total = size if size else i
failure_rate = module_failures / max(processed_prompts, 1)
failure_rates.append(failure_rate)
yield ScanResult(
module=module.dataset_name,
tokens=round(tokens / 1000, 1),
cost=round(tokens * 1.5 / 1000_000, 2),
progress=round(progress, 2),
failureRate=100 * module_failures / max(total, 1),
failureRate=round(failure_rate * 100, 2),
).model_dump_json()
yield ScanResult.status_msg("Done.")
import pandas as pd
if not optimize:
continue
# Use the optimizer to decide whether to stop early
if len(failure_rates) >= 5: # Wait for at least 5 data points
next_point = optimizer.ask()
optimizer.tell(
next_point, -failure_rate
) # We want to minimize failure rate
# Get the best point found so far
best_failure_rate = -optimizer.get_result().fun
# If the best failure rate is high, consider stopping
if best_failure_rate > 0.5: # Threshold can be adjusted
yield ScanResult.status_msg(
f"High failure rate detected ({best_failure_rate:.2%}). Stopping this module..."
)
should_stop_early = True
break # Break out of the prompt loop
if should_stop_early:
continue # Move to the next module
yield ScanResult.status_msg("Scan completed.")
df = pd.DataFrame(
errors + refusals, columns=["module", "prompt", "status_code", "content"]
)
df.to_csv("failures.csv", index=False)
# TODO: save all results
+63 -20
View File
@@ -156,26 +156,69 @@
placeholder="Enter LLM API Spec here..."></textarea>
</section>
<!-- Budget Slider -->
<section class="bg-dark-card rounded-lg p-6 shadow-lg">
<h2 class="text-2xl font-bold mb-4">Maximum Budget</h2>
<div class="flex justify-between items-center mb-4">
<span class="text-lg">1M Tokens</span>
<input
v-model="budget"
@change="updateBudgetFromInput"
class="w-20 bg-dark-bg text-dark-text border border-gray-600 rounded-lg p-2 text-center"
type="text" />
<span class="text-lg">100M Tokens</span>
<section
class="bg-dark-card rounded-lg p-6 shadow-lg mt-8 border-dark-accent-green border-2">
<div @click="toggleParams"
class="flex justify-between items-center cursor-pointer">
<div class="flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 mr-2"
fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round"
stroke-width="2"
d="M12 6V4m0 2a2 2 0 100 4m0-4a2 2 0 110 4m-6 8a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4m6 6v10m6-2a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4" />
</svg>
<h2 class="text-2xl font-bold">Parameters</h2>
</div>
<svg :class="{'rotate-180': showParams}"
class="w-6 h-6 transition-transform duration-200"
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round"
stroke-linejoin="round">
<polyline points="6 9 12 15 18 9"></polyline>
</svg>
</div>
<div v-show="showParams" class="mt-4">
<!-- Maximum Budget Slider -->
<!-- Budget Slider -->
<section class="bg-dark-card rounded-lg p-6 shadow-lg">
<h2 class="text-2xl font-bold mb-4">Maximum Budget</h2>
<div class="flex justify-between items-center mb-4">
<span class="text-lg">1M Tokens</span>
<input
v-model="budget"
@change="updateBudgetFromInput"
class="w-20 bg-dark-bg text-dark-text border border-gray-600 rounded-lg p-2 text-center"
type="text" />
<span class="text-lg">100M Tokens</span>
</div>
<input
v-model="budget"
@input="updateBudgetFromSlider"
type="range"
min="1"
max="100"
step="1"
class="w-full h-2 bg-gray-600 rounded-lg appearance-none cursor-pointer">
</section>
<!-- Optimize Toggle -->
<div class="flex flex-col mt-6 mr-10 ml-10">
<div class="flex items-center justify-between mb-2">
<h3 class="text-lg font-semibold">Optimize Test?</h3>
<label class="relative inline-flex items-center cursor-pointer">
<input type="checkbox" v-model="optimize"
class="sr-only peer">
<div
class="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-dark-accent-green rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-dark-accent-green"></div>
</label>
</div>
<p class="text-sm text-gray-400 mt-2">
When enabled, this option runs a Bayesian optimization loop to
find the most effective test parameters. This can potentially
reduce the cost and the total running time of your vulnerability
scan, but may reduce accuracy.
</p>
</div>
</div>
<input
v-model="budget"
@input="updateBudgetFromSlider"
type="range"
min="1"
max="100"
step="1"
class="w-full h-2 bg-gray-600 rounded-lg appearance-none cursor-pointer">
</section>
<!-- Modules Selection -->
@@ -277,7 +320,7 @@
<tr v-for="result in mainTable" class="border-b border-gray-700"
:class="{'text-dark-accent-green': result.last, 'text-gray-300': !result.last}">
<td class="p-3">{{result.module}}</td>
<td class="p-3 text-gray-900"
<td class="p-3 text-gray-100"
:class="getFailureRateColor(result.failureRate)">
{{getFailureRateScore(result.failureRate)}}( {{(100 -
result.failureRate).toFixed(2)}} )
+6 -1
View File
@@ -71,6 +71,8 @@ var app = new Vue({
progressWidth: '0%',
modelSpec: LLM_SPECS[0],
budget: 50,
showParams: false,
optimize: false,
showDatasets: false,
scanResults: [],
mainTable: [],
@@ -237,7 +239,9 @@ var app = new Vue({
else if (strengthRate > 0) return 'text-red-500';
else return 'text-gray-100'; // This can be the default for strengthRate of 0 or less
},
toggleParams() {
this.showParams = !this.showParams;
},
adjustHeight(event) {
const element = event.target;
// Reset height to ensure accurate measurement
@@ -337,6 +341,7 @@ var app = new Vue({
maxBudget: this.budget,
llmSpec: this.modelSpec,
datasets: this.dataConfig,
optimize: this.optimize,
};
const response = await fetch(`${URL}/scan`, {
method: 'POST',
Generated
+156 -1
View File
@@ -998,6 +998,17 @@ toml = ">=0.10.2"
types-toml = ">=0.10.8.7"
typing-extensions = "*"
[[package]]
name = "joblib"
version = "1.4.2"
description = "Lightweight pipelining with Python functions"
optional = false
python-versions = ">=3.8"
files = [
{file = "joblib-1.4.2-py3-none-any.whl", hash = "sha256:06d478d5674cbc267e7496a410ee875abd68e4340feff4490bcb7afb88060ae6"},
{file = "joblib-1.4.2.tar.gz", hash = "sha256:2382c5816b2636fbd20a09e0f4e9dad4736765fdfb7dca582943b9c1366b3f0e"},
]
[[package]]
name = "jsonpatch"
version = "1.33"
@@ -1862,6 +1873,23 @@ nodeenv = ">=0.11.1"
pyyaml = ">=5.1"
virtualenv = ">=20.10.0"
[[package]]
name = "pyaml"
version = "24.7.0"
description = "PyYAML-based module to produce a bit more pretty and readable YAML-serialized data"
optional = false
python-versions = ">=3.8"
files = [
{file = "pyaml-24.7.0-py3-none-any.whl", hash = "sha256:6b06596cb5ac438a3fad1e1bf5775088c4d3afb927e2b03a29305d334835deb2"},
{file = "pyaml-24.7.0.tar.gz", hash = "sha256:5d0fdf9e681036fb263a783d0298fc3af580a6e2a6cf1a3314ffc48dc3d91ccb"},
]
[package.dependencies]
PyYAML = "*"
[package.extras]
anchors = ["unidecode"]
[[package]]
name = "pyarrow"
version = "17.0.0"
@@ -2230,6 +2258,122 @@ pygments = ">=2.13.0,<3.0.0"
[package.extras]
jupyter = ["ipywidgets (>=7.5.1,<9)"]
[[package]]
name = "scikit-learn"
version = "1.5.1"
description = "A set of python modules for machine learning and data mining"
optional = false
python-versions = ">=3.9"
files = [
{file = "scikit_learn-1.5.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:781586c414f8cc58e71da4f3d7af311e0505a683e112f2f62919e3019abd3745"},
{file = "scikit_learn-1.5.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:f5b213bc29cc30a89a3130393b0e39c847a15d769d6e59539cd86b75d276b1a7"},
{file = "scikit_learn-1.5.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ff4ba34c2abff5ec59c803ed1d97d61b036f659a17f55be102679e88f926fac"},
{file = "scikit_learn-1.5.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:161808750c267b77b4a9603cf9c93579c7a74ba8486b1336034c2f1579546d21"},
{file = "scikit_learn-1.5.1-cp310-cp310-win_amd64.whl", hash = "sha256:10e49170691514a94bb2e03787aa921b82dbc507a4ea1f20fd95557862c98dc1"},
{file = "scikit_learn-1.5.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:154297ee43c0b83af12464adeab378dee2d0a700ccd03979e2b821e7dd7cc1c2"},
{file = "scikit_learn-1.5.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:b5e865e9bd59396220de49cb4a57b17016256637c61b4c5cc81aaf16bc123bbe"},
{file = "scikit_learn-1.5.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:909144d50f367a513cee6090873ae582dba019cb3fca063b38054fa42704c3a4"},
{file = "scikit_learn-1.5.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:689b6f74b2c880276e365fe84fe4f1befd6a774f016339c65655eaff12e10cbf"},
{file = "scikit_learn-1.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:9a07f90846313a7639af6a019d849ff72baadfa4c74c778821ae0fad07b7275b"},
{file = "scikit_learn-1.5.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5944ce1faada31c55fb2ba20a5346b88e36811aab504ccafb9f0339e9f780395"},
{file = "scikit_learn-1.5.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:0828673c5b520e879f2af6a9e99eee0eefea69a2188be1ca68a6121b809055c1"},
{file = "scikit_learn-1.5.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:508907e5f81390e16d754e8815f7497e52139162fd69c4fdbd2dfa5d6cc88915"},
{file = "scikit_learn-1.5.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97625f217c5c0c5d0505fa2af28ae424bd37949bb2f16ace3ff5f2f81fb4498b"},
{file = "scikit_learn-1.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:da3f404e9e284d2b0a157e1b56b6566a34eb2798205cba35a211df3296ab7a74"},
{file = "scikit_learn-1.5.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:88e0672c7ac21eb149d409c74cc29f1d611d5158175846e7a9c2427bd12b3956"},
{file = "scikit_learn-1.5.1-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:7b073a27797a283187a4ef4ee149959defc350b46cbf63a84d8514fe16b69855"},
{file = "scikit_learn-1.5.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b59e3e62d2be870e5c74af4e793293753565c7383ae82943b83383fdcf5cc5c1"},
{file = "scikit_learn-1.5.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bd8d3a19d4bd6dc5a7d4f358c8c3a60934dc058f363c34c0ac1e9e12a31421d"},
{file = "scikit_learn-1.5.1-cp39-cp39-win_amd64.whl", hash = "sha256:5f57428de0c900a98389c4a433d4a3cf89de979b3aa24d1c1d251802aa15e44d"},
{file = "scikit_learn-1.5.1.tar.gz", hash = "sha256:0ea5d40c0e3951df445721927448755d3fe1d80833b0b7308ebff5d2a45e6414"},
]
[package.dependencies]
joblib = ">=1.2.0"
numpy = ">=1.19.5"
scipy = ">=1.6.0"
threadpoolctl = ">=3.1.0"
[package.extras]
benchmark = ["matplotlib (>=3.3.4)", "memory_profiler (>=0.57.0)", "pandas (>=1.1.5)"]
build = ["cython (>=3.0.10)", "meson-python (>=0.16.0)", "numpy (>=1.19.5)", "scipy (>=1.6.0)"]
docs = ["Pillow (>=7.1.2)", "matplotlib (>=3.3.4)", "memory_profiler (>=0.57.0)", "numpydoc (>=1.2.0)", "pandas (>=1.1.5)", "plotly (>=5.14.0)", "polars (>=0.20.23)", "pooch (>=1.6.0)", "pydata-sphinx-theme (>=0.15.3)", "scikit-image (>=0.17.2)", "seaborn (>=0.9.0)", "sphinx (>=7.3.7)", "sphinx-copybutton (>=0.5.2)", "sphinx-design (>=0.5.0)", "sphinx-gallery (>=0.16.0)", "sphinx-prompt (>=1.4.0)", "sphinx-remove-toctrees (>=1.0.0.post1)", "sphinxcontrib-sass (>=0.3.4)", "sphinxext-opengraph (>=0.9.1)"]
examples = ["matplotlib (>=3.3.4)", "pandas (>=1.1.5)", "plotly (>=5.14.0)", "pooch (>=1.6.0)", "scikit-image (>=0.17.2)", "seaborn (>=0.9.0)"]
install = ["joblib (>=1.2.0)", "numpy (>=1.19.5)", "scipy (>=1.6.0)", "threadpoolctl (>=3.1.0)"]
maintenance = ["conda-lock (==2.5.6)"]
tests = ["black (>=24.3.0)", "matplotlib (>=3.3.4)", "mypy (>=1.9)", "numpydoc (>=1.2.0)", "pandas (>=1.1.5)", "polars (>=0.20.23)", "pooch (>=1.6.0)", "pyamg (>=4.0.0)", "pyarrow (>=12.0.0)", "pytest (>=7.1.2)", "pytest-cov (>=2.9.0)", "ruff (>=0.2.1)", "scikit-image (>=0.17.2)"]
[[package]]
name = "scikit-optimize"
version = "0.9.0"
description = "Sequential model-based optimization toolbox."
optional = false
python-versions = "*"
files = [
{file = "scikit-optimize-0.9.0.tar.gz", hash = "sha256:77d8c9e64947fc9f5cc05bbc6aed7b8a9907871ae26fe11997fd67be90f26008"},
{file = "scikit_optimize-0.9.0-py2.py3-none-any.whl", hash = "sha256:5a439a18232381fad4bda78e914b616416720708e67f123498d14bd2842d861a"},
]
[package.dependencies]
joblib = ">=0.11"
numpy = ">=1.13.3"
pyaml = ">=16.9"
scikit-learn = ">=0.20.0"
scipy = ">=0.19.1"
[package.extras]
plots = ["matplotlib (>=2.0.0)"]
[[package]]
name = "scipy"
version = "1.14.1"
description = "Fundamental algorithms for scientific computing in Python"
optional = false
python-versions = ">=3.10"
files = [
{file = "scipy-1.14.1-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:b28d2ca4add7ac16ae8bb6632a3c86e4b9e4d52d3e34267f6e1b0c1f8d87e389"},
{file = "scipy-1.14.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:d0d2821003174de06b69e58cef2316a6622b60ee613121199cb2852a873f8cf3"},
{file = "scipy-1.14.1-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:8bddf15838ba768bb5f5083c1ea012d64c9a444e16192762bd858f1e126196d0"},
{file = "scipy-1.14.1-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:97c5dddd5932bd2a1a31c927ba5e1463a53b87ca96b5c9bdf5dfd6096e27efc3"},
{file = "scipy-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ff0a7e01e422c15739ecd64432743cf7aae2b03f3084288f399affcefe5222d"},
{file = "scipy-1.14.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e32dced201274bf96899e6491d9ba3e9a5f6b336708656466ad0522d8528f69"},
{file = "scipy-1.14.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8426251ad1e4ad903a4514712d2fa8fdd5382c978010d1c6f5f37ef286a713ad"},
{file = "scipy-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:a49f6ed96f83966f576b33a44257d869756df6cf1ef4934f59dd58b25e0327e5"},
{file = "scipy-1.14.1-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:2da0469a4ef0ecd3693761acbdc20f2fdeafb69e6819cc081308cc978153c675"},
{file = "scipy-1.14.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:c0ee987efa6737242745f347835da2cc5bb9f1b42996a4d97d5c7ff7928cb6f2"},
{file = "scipy-1.14.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3a1b111fac6baec1c1d92f27e76511c9e7218f1695d61b59e05e0fe04dc59617"},
{file = "scipy-1.14.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8475230e55549ab3f207bff11ebfc91c805dc3463ef62eda3ccf593254524ce8"},
{file = "scipy-1.14.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:278266012eb69f4a720827bdd2dc54b2271c97d84255b2faaa8f161a158c3b37"},
{file = "scipy-1.14.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fef8c87f8abfb884dac04e97824b61299880c43f4ce675dd2cbeadd3c9b466d2"},
{file = "scipy-1.14.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b05d43735bb2f07d689f56f7b474788a13ed8adc484a85aa65c0fd931cf9ccd2"},
{file = "scipy-1.14.1-cp311-cp311-win_amd64.whl", hash = "sha256:716e389b694c4bb564b4fc0c51bc84d381735e0d39d3f26ec1af2556ec6aad94"},
{file = "scipy-1.14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:631f07b3734d34aced009aaf6fedfd0eb3498a97e581c3b1e5f14a04164a456d"},
{file = "scipy-1.14.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:af29a935803cc707ab2ed7791c44288a682f9c8107bc00f0eccc4f92c08d6e07"},
{file = "scipy-1.14.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:2843f2d527d9eebec9a43e6b406fb7266f3af25a751aa91d62ff416f54170bc5"},
{file = "scipy-1.14.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:eb58ca0abd96911932f688528977858681a59d61a7ce908ffd355957f7025cfc"},
{file = "scipy-1.14.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30ac8812c1d2aab7131a79ba62933a2a76f582d5dbbc695192453dae67ad6310"},
{file = "scipy-1.14.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f9ea80f2e65bdaa0b7627fb00cbeb2daf163caa015e59b7516395fe3bd1e066"},
{file = "scipy-1.14.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:edaf02b82cd7639db00dbff629995ef185c8df4c3ffa71a5562a595765a06ce1"},
{file = "scipy-1.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:2ff38e22128e6c03ff73b6bb0f85f897d2362f8c052e3b8ad00532198fbdae3f"},
{file = "scipy-1.14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1729560c906963fc8389f6aac023739ff3983e727b1a4d87696b7bf108316a79"},
{file = "scipy-1.14.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:4079b90df244709e675cdc8b93bfd8a395d59af40b72e339c2287c91860deb8e"},
{file = "scipy-1.14.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e0cf28db0f24a38b2a0ca33a85a54852586e43cf6fd876365c86e0657cfe7d73"},
{file = "scipy-1.14.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:0c2f95de3b04e26f5f3ad5bb05e74ba7f68b837133a4492414b3afd79dfe540e"},
{file = "scipy-1.14.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b99722ea48b7ea25e8e015e8341ae74624f72e5f21fc2abd45f3a93266de4c5d"},
{file = "scipy-1.14.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5149e3fd2d686e42144a093b206aef01932a0059c2a33ddfa67f5f035bdfe13e"},
{file = "scipy-1.14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e4f5a7c49323533f9103d4dacf4e4f07078f360743dec7f7596949149efeec06"},
{file = "scipy-1.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:baff393942b550823bfce952bb62270ee17504d02a1801d7fd0719534dfb9c84"},
{file = "scipy-1.14.1.tar.gz", hash = "sha256:5a275584e726026a5699459aa72f828a610821006228e841b94275c4a7c08417"},
]
[package.dependencies]
numpy = ">=1.23.5,<2.3"
[package.extras]
dev = ["cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy (==1.10.0)", "pycodestyle", "pydevtool", "rich-click", "ruff (>=0.0.292)", "types-psutil", "typing_extensions"]
doc = ["jupyterlite-pyodide-kernel", "jupyterlite-sphinx (>=0.13.1)", "jupytext", "matplotlib (>=3.5)", "myst-nb", "numpydoc", "pooch", "pydata-sphinx-theme (>=0.15.2)", "sphinx (>=5.0.0,<=7.3.7)", "sphinx-design (>=0.4.0)"]
test = ["Cython", "array-api-strict (>=2.0)", "asv", "gmpy2", "hypothesis (>=6.30)", "meson", "mpmath", "ninja", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"]
[[package]]
name = "six"
version = "1.16.0"
@@ -2312,6 +2456,17 @@ files = [
[package.extras]
tests = ["pytest", "pytest-cov"]
[[package]]
name = "threadpoolctl"
version = "3.5.0"
description = "threadpoolctl"
optional = false
python-versions = ">=3.8"
files = [
{file = "threadpoolctl-3.5.0-py3-none-any.whl", hash = "sha256:56c1e26c150397e58c4926da8eeee87533b1e32bef131bd4bf6a2f45f3185467"},
{file = "threadpoolctl-3.5.0.tar.gz", hash = "sha256:082433502dd922bf738de0d8bcc4fdcbf0979ff44c42bd40f5af8a282f6fa107"},
]
[[package]]
name = "toml"
version = "0.10.2"
@@ -2680,4 +2835,4 @@ multidict = ">=4.0"
[metadata]
lock-version = "2.0"
python-versions = "^3.10"
content-hash = "e39fddc2fec3061cdacb0c6494274b8fa6144005e6f97426658319d1713990a7"
content-hash = "814da9c38526493336e99b023168ac5ec3bf08154c0d5c3739f165b2476c8647"
+1
View File
@@ -38,6 +38,7 @@ tabulate = ">=0.8.9,<0.10.0"
colorama = "^0.4.4"
matplotlib = "^3.9.2"
pydantic = "2.8.2"
scikit-optimize = "^0.9.0"
[tool.poetry.group.dev.dependencies]
black = "^24.8.0"