mirror of
https://github.com/msoedov/agentic_security.git
synced 2026-06-24 14:19:55 +02:00
Compare commits
166 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| aa2fe4d1ad | |||
| cf7c017621 | |||
| 73184e3454 | |||
| 3720ece2af | |||
| 0dc738a11e | |||
| 47ca656d59 | |||
| 4fa166298d | |||
| 77557ade85 | |||
| 5cdbf933de | |||
| 54d159a737 | |||
| 35fd373cb2 | |||
| f2b95a0040 | |||
| a8e80e85e1 | |||
| f97c3367b4 | |||
| c065818053 | |||
| 1139577eaa | |||
| 5d6a65350f | |||
| c277cca045 | |||
| fcbb832968 | |||
| a0e523758d | |||
| 5ebf428de6 | |||
| d5fe89f298 | |||
| 98b7d7f691 | |||
| c5ddcb2d75 | |||
| da63270142 | |||
| bf5f7a7dff | |||
| d3ccea76b6 | |||
| b7fef85750 | |||
| a1249cae12 | |||
| 8549aee952 | |||
| 414ee62467 | |||
| 7f68224716 | |||
| 3910bab28e | |||
| 8a4dcfd43e | |||
| 17234a846b | |||
| a51a3aa497 | |||
| 0b3424e9fd | |||
| f81b32d9b4 | |||
| a9f8090614 | |||
| 8770726f63 | |||
| ffc4f94a0a | |||
| 5edd4f0959 | |||
| e495f9626f | |||
| b45006c0d1 | |||
| d60d87f142 | |||
| 68f01622fc | |||
| 29787ae5fc | |||
| 1d0e88b001 | |||
| 8e5a53eaa3 | |||
| dcaba04dd6 | |||
| f4271ef2a1 | |||
| feb1becb3e | |||
| 7b44a2f510 | |||
| e3c3119790 | |||
| e171f0216e | |||
| 5d712ebce4 | |||
| 37a6e7a5bc | |||
| 85216ad106 | |||
| bb2e0e7517 | |||
| 8689efbe59 | |||
| 0b41fe0e3f | |||
| c3776df5c1 | |||
| 143ea4f8c1 | |||
| dd2eb1472f | |||
| 4332e4affd | |||
| e871443e76 | |||
| e9ae785625 | |||
| b1e2dc8cef | |||
| b9802fd268 | |||
| ac3f2f803c | |||
| bd6d2f3db1 | |||
| dda8d13b72 | |||
| 839c1af9d7 | |||
| e261fe55c5 | |||
| b4857a5f36 | |||
| 85d5c67047 | |||
| b4e0aef33f | |||
| 3a3ac4421c | |||
| d6ccd8f114 | |||
| 41ecc3c8e2 | |||
| a5f8da4139 | |||
| 9b47b72bb8 | |||
| 91ff3aa11f | |||
| 87264b793b | |||
| 2bc093cfc4 | |||
| 182b67de57 | |||
| 35540d0299 | |||
| 50ce80b149 | |||
| 07772e9779 | |||
| c7a6d16e00 | |||
| fe71ab0b70 | |||
| b6643d3289 | |||
| 3d6018302e | |||
| a54b11318a | |||
| 91c99e642f | |||
| 1226e2059d | |||
| c94aa54e41 | |||
| 431bf84956 | |||
| 9213b64de0 | |||
| 9d2885f7a6 | |||
| 4c0a57dbae | |||
| 6e6abae680 | |||
| fa7bd104a0 | |||
| 321c3dafc0 | |||
| eb14fe0f61 | |||
| b5985bf810 | |||
| 29362aed30 | |||
| c2671fd6f1 | |||
| 56984c7078 | |||
| f9dce3f915 | |||
| 6e99dfd54f | |||
| 67f7381ace | |||
| e2cb909329 | |||
| dbef9fcc03 | |||
| 4a07411fbd | |||
| 11951c0ca0 | |||
| 02222b75f8 | |||
| aa27817f94 | |||
| 5a4b5e11b2 | |||
| 8bd76b9f05 | |||
| 6f3c522d59 | |||
| 71787c6ec9 | |||
| f911912c0f | |||
| 896ca95ae2 | |||
| f85c77d622 | |||
| 684ba0b70d | |||
| 21b43b18e7 | |||
| 9340fa7223 | |||
| d20c1a3d0d | |||
| 812f9e7619 | |||
| ebac62e21a | |||
| ce2a791663 | |||
| db994fd483 | |||
| 7fe93e8a14 | |||
| a934ee243e | |||
| d646ecd61b | |||
| 21180b53e5 | |||
| a8808b3165 | |||
| 87c26ca3cc | |||
| e06c6932de | |||
| 51fcc38885 | |||
| 06a7bbfd87 | |||
| 50f3e52445 | |||
| 2bd62c21be | |||
| d5d5dd48aa | |||
| bb2437197a | |||
| 51bb79aa6d | |||
| 94f034fa9f | |||
| f69de8720b | |||
| 7c9d83b1a7 | |||
| a9d4d671ba | |||
| 554a219535 | |||
| 32e99006bf | |||
| 8c09d65687 | |||
| a2842755fa | |||
| b923f7fea5 | |||
| 7f30a8ff7a | |||
| 909cbd69b4 | |||
| 4f0ebf180e | |||
| 6be9673aa7 | |||
| bd9ed97d85 | |||
| 3c88a4d6ba | |||
| 2001eeb125 | |||
| a26b5dd448 | |||
| 716a0f67f3 | |||
| c1bbf6b422 |
@@ -9,7 +9,7 @@ on:
|
||||
- 0.*
|
||||
|
||||
env:
|
||||
POETRY_VERSION: "1.7.1"
|
||||
POETRY_VERSION: "1.8.5"
|
||||
|
||||
jobs:
|
||||
if_release:
|
||||
|
||||
@@ -7,7 +7,7 @@ on:
|
||||
branches: [main]
|
||||
|
||||
env:
|
||||
POETRY_VERSION: "1.7.1"
|
||||
POETRY_VERSION: "1.8.5"
|
||||
OPENAI_API_KEY: "sk-fake"
|
||||
|
||||
jobs:
|
||||
|
||||
@@ -17,3 +17,5 @@ inv/
|
||||
scripts/
|
||||
docx/
|
||||
agentic_security.toml
|
||||
/venv
|
||||
*.csv
|
||||
|
||||
+4
-1
@@ -1,5 +1,5 @@
|
||||
# Build stage
|
||||
FROM python:3.11-slim as builder
|
||||
FROM python:3.11-slim AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
@@ -14,6 +14,9 @@ RUN poetry self add "poetry-plugin-export"
|
||||
# Copy only dependency files to leverage Docker layer caching
|
||||
COPY pyproject.toml poetry.lock ./
|
||||
|
||||
# update lock file to avoid failure
|
||||
RUN poetry lock
|
||||
|
||||
# Install dependencies
|
||||
RUN poetry export -f requirements.txt --without-hashes -o requirements.txt
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
@@ -83,7 +83,7 @@ agentic_security --port=PORT --host=HOST
|
||||
|
||||
## UI 🧙
|
||||
|
||||
<img width="100%" alt="booking-screen" src="https://res.cloudinary.com/dq0w2rtm9/image/upload/v1736433557/z0bsyzhsqlgcr3w4ovwp.gif">
|
||||
<img width="100%" alt="booking-screen" src="https://raw.githubusercontent.com/msoedov/agentic_security/refs/heads/main/docs/images/demo.gif">
|
||||
|
||||
## LLM kwargs
|
||||
|
||||
@@ -402,16 +402,31 @@ This setup ensures a continuous integration approach towards maintaining securit
|
||||
|
||||
The `Module` class is designed to manage prompt processing and interaction with external AI models and tools. It supports fetching, processing, and posting prompts asynchronously for model vulnerabilities. Check out [module.md](https://github.com/msoedov/agentic_security/blob/main/docs/module.md) for details.
|
||||
|
||||
|
||||
## MCP server
|
||||
|
||||
```shell
|
||||
pip install -U mcp
|
||||
|
||||
# From cloned directory
|
||||
mcp install agentic_security/mcp/main.py
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
For more detailed information on how to use Agentic Security, including advanced features and customization options, please refer to the official documentation.
|
||||
|
||||
## Roadmap and Future Goals
|
||||
|
||||
- \[ \] Expand dataset variety
|
||||
- \[ \] Introduce two new attack vectors
|
||||
- \[ \] Develop initial attacker LLM
|
||||
- \[ \] Complete integration of OWASP Top 10 classification
|
||||
|
||||
|
||||
We’re just getting started! Here’s what’s on the horizon:
|
||||
|
||||
- **RL-Powered Attacks**: An attacker LLM trained with reinforcement learning to dynamically evolve jailbreaks and outsmart defenses.
|
||||
- **Massive Dataset Expansion**: Scaling to 100,000+ prompts across text, image, and audio modalities—curated for real-world threats.
|
||||
- **Daily Attack Updates**: Fresh attack vectors delivered daily, keeping your scans ahead of the curve.
|
||||
- **Community Modules**: A plug-and-play ecosystem where you can share and deploy custom probes, datasets, and integrations.
|
||||
|
||||
|
||||
| Tool | Source | Integrated |
|
||||
|-------------------------|-------------------------------------------------------------------------------|------------|
|
||||
@@ -423,6 +438,7 @@ For more detailed information on how to use Agentic Security, including advanced
|
||||
|
||||
Note: All dates are tentative and subject to change based on project progress and priorities.
|
||||
|
||||
|
||||
## 👋 Contributing
|
||||
|
||||
Contributions to Agentic Security are welcome! If you'd like to contribute, please follow these steps:
|
||||
@@ -439,4 +455,9 @@ Before contributing, please read the contributing guidelines.
|
||||
|
||||
Agentic Security is released under the Apache License v2.
|
||||
|
||||
|
||||
## 🚫 No Cryptocurrency Affiliation
|
||||
|
||||
Agentic Security is focused solely on AI security and has no affiliation with cryptocurrency projects, blockchain technologies, or related initiatives. Our mission is to advance the safety and reliability of AI systems—no tokens, no coins, just code.
|
||||
|
||||
## Contact us
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
from .lib import AgenticSecurity
|
||||
from .lib import SecurityScanner
|
||||
|
||||
__all__ = ["AgenticSecurity"]
|
||||
__all__ = ["SecurityScanner"]
|
||||
|
||||
@@ -5,7 +5,7 @@ import fire
|
||||
import uvicorn
|
||||
|
||||
from agentic_security.app import app
|
||||
from agentic_security.lib import AgenticSecurity
|
||||
from agentic_security.lib import SecurityScanner
|
||||
from agentic_security.misc.banner import init_banner
|
||||
|
||||
|
||||
@@ -32,14 +32,14 @@ class CLI:
|
||||
Run Agentic Security in CI mode.
|
||||
"""
|
||||
sys.path.append(os.path.dirname("."))
|
||||
AgenticSecurity().entrypoint()
|
||||
SecurityScanner().entrypoint()
|
||||
|
||||
def init(self, host: str = "0.0.0.0", port: int = 8718):
|
||||
"""
|
||||
Generate the default CI configuration file.
|
||||
"""
|
||||
sys.path.append(os.path.dirname("."))
|
||||
AgenticSecurity().generate_default_settings(host, port)
|
||||
SecurityScanner().generate_default_settings(host, port)
|
||||
|
||||
i = init
|
||||
|
||||
@@ -48,7 +48,7 @@ class CLI:
|
||||
List all available security checks.
|
||||
"""
|
||||
sys.path.append(os.path.dirname("."))
|
||||
AgenticSecurity().list_checks()
|
||||
SecurityScanner().list_checks()
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import asyncio
|
||||
import logging
|
||||
import os
|
||||
from typing import Any
|
||||
|
||||
@@ -10,12 +9,11 @@ from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
# Assuming LLMSpec is defined elsewhere; placeholder import
|
||||
from agentic_security.http_spec import LLMSpec
|
||||
from agentic_security.logutils import logger
|
||||
|
||||
LLM_SPECS = [] # Populate with LLM spec strings if needed
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# Define AgentSpecification model
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import asyncio
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
import httpx
|
||||
@@ -8,13 +7,10 @@ from pydantic_ai import Agent, RunContext, Tool
|
||||
|
||||
# Assuming LLMSpec is defined elsewhere; placeholder import
|
||||
from agentic_security.http_spec import LLMSpec
|
||||
from agentic_security.logutils import logger
|
||||
|
||||
LLM_SPECS = [] # Populate this list with LLM spec strings if needed
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# Define AgentSpecification model
|
||||
class AgentSpecification(BaseModel):
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
from functools import lru_cache
|
||||
|
||||
import tomli
|
||||
from loguru import logger
|
||||
|
||||
SETTINGS_VERSION = 1
|
||||
from agentic_security.logutils import logger
|
||||
|
||||
SETTINGS_VERSION = 2
|
||||
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
@@ -142,6 +143,13 @@ use_disk_cache = false
|
||||
retry = 3
|
||||
timeout_connect = 30
|
||||
timeout_response = 90
|
||||
|
||||
[fuzzer]
|
||||
max_prompt_lenght = 2048
|
||||
budget_multiplier = 100000000
|
||||
initial_optimizer_points = 25
|
||||
min_failure_samples = 5
|
||||
failure_rate_threshold = 0.5
|
||||
""".replace(
|
||||
"$HOST", host
|
||||
)
|
||||
|
||||
@@ -4,10 +4,14 @@ from asyncio import Event, Queue
|
||||
from fastapi import FastAPI
|
||||
from fastapi.responses import ORJSONResponse
|
||||
|
||||
from agentic_security.http_spec import LLMSpec
|
||||
|
||||
tools_inbox: Queue = Queue()
|
||||
stop_event: Event = Event()
|
||||
current_run: str = {"spec": "", "id": ""}
|
||||
_secrets = {}
|
||||
_secrets: dict[str, str] = {}
|
||||
|
||||
current_run: dict[str, int | LLMSpec] = {"spec": "", "id": ""}
|
||||
|
||||
|
||||
def create_app() -> FastAPI:
|
||||
@@ -26,29 +30,29 @@ def get_stop_event() -> Event:
|
||||
return stop_event
|
||||
|
||||
|
||||
def get_current_run() -> str:
|
||||
def get_current_run() -> dict[str, int | LLMSpec]:
|
||||
"""Get the current run id."""
|
||||
return current_run
|
||||
|
||||
|
||||
def set_current_run(spec):
|
||||
def set_current_run(spec: LLMSpec) -> dict[str, int | LLMSpec]:
|
||||
"""Set the current run id."""
|
||||
current_run["id"] = hash(id(spec))
|
||||
current_run["spec"] = spec
|
||||
return current_run
|
||||
|
||||
|
||||
def get_secrets():
|
||||
def get_secrets() -> dict[str, str]:
|
||||
return _secrets
|
||||
|
||||
|
||||
def set_secrets(secrets):
|
||||
def set_secrets(secrets: dict[str, str]) -> dict[str, str]:
|
||||
_secrets.update(secrets)
|
||||
expand_secrets(_secrets)
|
||||
return _secrets
|
||||
|
||||
|
||||
def expand_secrets(secrets):
|
||||
def expand_secrets(secrets: dict[str, str]) -> None:
|
||||
for key in secrets:
|
||||
val = secrets[key]
|
||||
if val.startswith("$"):
|
||||
|
||||
@@ -1,26 +1,5 @@
|
||||
from logging import config
|
||||
from agentic_security.logutils import set_log_level_to_info
|
||||
|
||||
|
||||
def setup_logging():
|
||||
config.dictConfig(
|
||||
{
|
||||
"version": 1,
|
||||
"disable_existing_loggers": True,
|
||||
"handlers": {
|
||||
"console": {
|
||||
"class": "logging.StreamHandler",
|
||||
},
|
||||
},
|
||||
"root": {
|
||||
"handlers": ["console"],
|
||||
"level": "INFO",
|
||||
},
|
||||
"loggers": {
|
||||
"uvicorn.access": {
|
||||
"level": "ERROR", # Set higher log level to suppress info logs globally
|
||||
"handlers": ["console"],
|
||||
"propagate": False,
|
||||
}
|
||||
},
|
||||
}
|
||||
)
|
||||
return set_log_level_to_info()
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import base64
|
||||
from enum import Enum
|
||||
from urllib.parse import urlparse
|
||||
|
||||
import httpx
|
||||
from pydantic import BaseModel
|
||||
@@ -159,6 +160,14 @@ def parse_http_spec(http_spec: str) -> LLMSpec:
|
||||
# Extract the method and URL from the first line
|
||||
method, url = lines[0].split(" ")[0:2]
|
||||
|
||||
# Check url validity
|
||||
valid_url = urlparse(url)
|
||||
# if missing the correct formatting ://, urlparse.netloc will be empty
|
||||
if valid_url.scheme not in ("http", "https") or not valid_url.netloc:
|
||||
raise InvalidHTTPSpecError(
|
||||
f"Invalid URL: {url}. Ensure it starts with 'http://' or 'https://'"
|
||||
)
|
||||
|
||||
# Initialize headers and body
|
||||
headers = {}
|
||||
body = ""
|
||||
|
||||
@@ -4,12 +4,12 @@ from datetime import datetime
|
||||
|
||||
import colorama
|
||||
import tqdm.asyncio
|
||||
from loguru import logger
|
||||
from rich.console import Console
|
||||
from rich.table import Table
|
||||
from tabulate import tabulate
|
||||
|
||||
from agentic_security.config import SettingsMixin # Importing the configuration mixin
|
||||
from agentic_security.logutils import logger
|
||||
from agentic_security.primitives import Scan
|
||||
from agentic_security.probe_data import REGISTRY
|
||||
from agentic_security.routes.scan import streaming_response_generator
|
||||
@@ -23,7 +23,7 @@ YELLOW = colorama.Fore.YELLOW
|
||||
BLUE = colorama.Fore.BLUE
|
||||
|
||||
|
||||
class AgenticSecurity(SettingsMixin):
|
||||
class SecurityScanner(SettingsMixin):
|
||||
@classmethod
|
||||
async def async_scan(
|
||||
cls,
|
||||
@@ -202,7 +202,7 @@ class AgenticSecurity(SettingsMixin):
|
||||
self.load_config(self.default_path)
|
||||
logger.info("Configuration loaded successfully.")
|
||||
print(self.config)
|
||||
datasets = list(self.get_config_value("modules").values())
|
||||
datasets = list(self.get_config_value("modules", {}).values())
|
||||
for d in datasets:
|
||||
d["selected"] = True
|
||||
self.scan(
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
# import sys
|
||||
|
||||
# from loguru import logger
|
||||
|
||||
# # Define custom colors
|
||||
# BLUE = "#89CFF0"
|
||||
# BROWN = "#8B4513" # Brown for DEBUG
|
||||
|
||||
# # Define custom log level colors
|
||||
# logger.level("DEBUG", color=f"<fg {BROWN}>")
|
||||
# logger.level("INFO", color=f"<fg {BLUE}>")
|
||||
|
||||
# # Define custom log format with aligned messages and colored levels
|
||||
# LOG_FORMAT = (
|
||||
# "<level>{level:<8}</level> " # Properly formatted and colored log level
|
||||
# "<level>{message:<100}</level> " # Left-aligned message for readability
|
||||
# "<cyan>{file.name}</cyan>:<cyan>{line}</cyan>" # File name and line number in cyan
|
||||
# )
|
||||
|
||||
# # Remove default handlers and add a new one with custom formatting
|
||||
# logger.remove()
|
||||
# logger.add(sys.stdout, format=LOG_FORMAT, level="DEBUG", colorize=True)
|
||||
import logging
|
||||
import logging.config
|
||||
import time
|
||||
from collections.abc import Callable, Coroutine
|
||||
from functools import wraps
|
||||
from os import getenv
|
||||
from typing import Any, ParamSpec, TypeVar
|
||||
|
||||
LOGGER_NAME = None
|
||||
|
||||
LOGGING_CONFIG = {
|
||||
"version": 1,
|
||||
"disable_existing_loggers": False,
|
||||
"formatters": {
|
||||
"rich": {"format": "%(message)s", "datefmt": "[%X]"},
|
||||
},
|
||||
"handlers": {
|
||||
"rich": {
|
||||
"class": "rich.logging.RichHandler",
|
||||
"level": "INFO",
|
||||
"formatter": "rich",
|
||||
"show_time": False,
|
||||
"rich_tracebacks": False,
|
||||
"show_path": lambda: True if getenv("API_RUNTIME") == "dev" else False,
|
||||
"tracebacks_show_locals": False,
|
||||
},
|
||||
},
|
||||
"loggers": {
|
||||
"": { # Root logger configuration
|
||||
"level": "INFO",
|
||||
"handlers": ["rich"],
|
||||
"propagate": True,
|
||||
},
|
||||
"httpx": { # Disable httpx logging
|
||||
"level": "WARNING", # Suppress DEBUG and INFO messages from httpx
|
||||
"handlers": [],
|
||||
"propagate": False,
|
||||
},
|
||||
"uvicorn.access": { # Disable uvicorn.access logging
|
||||
"level": "WARNING", # Suppress DEBUG and INFO messages from uvicorn.access
|
||||
"handlers": [],
|
||||
"propagate": False,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def configure_logging():
|
||||
# Apply the dictionary configuration
|
||||
logging.config.dictConfig(LOGGING_CONFIG)
|
||||
|
||||
# Get and return the logger
|
||||
logger = logging.getLogger(LOGGER_NAME)
|
||||
return logger
|
||||
|
||||
|
||||
logger: logging.Logger = configure_logging()
|
||||
|
||||
|
||||
def set_log_level_to_debug():
|
||||
logger = logging.getLogger(LOGGER_NAME)
|
||||
logger.setLevel(logging.DEBUG)
|
||||
# Update handler level as well
|
||||
for handler in logger.handlers:
|
||||
handler.setLevel(logging.DEBUG)
|
||||
|
||||
|
||||
def set_log_level_to_info():
|
||||
logger = logging.getLogger(LOGGER_NAME)
|
||||
logger.setLevel(logging.INFO)
|
||||
# Update handler level as well
|
||||
for handler in logger.handlers:
|
||||
handler.setLevel(logging.INFO)
|
||||
|
||||
|
||||
# Set initial log level
|
||||
set_log_level_to_info()
|
||||
|
||||
|
||||
# Define generic type variables for return type and parameters
|
||||
R = TypeVar("R")
|
||||
P = ParamSpec("P")
|
||||
|
||||
|
||||
def time_execution_sync(
|
||||
additional_text: str = "",
|
||||
) -> Callable[[Callable[P, R]], Callable[P, R]]:
|
||||
def decorator(func: Callable[P, R]) -> Callable[P, R]:
|
||||
@wraps(func)
|
||||
def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
|
||||
start_time = time.time()
|
||||
result = func(*args, **kwargs)
|
||||
execution_time = time.time() - start_time
|
||||
logger.debug(
|
||||
f"{additional_text} Execution time: {execution_time:.2f} seconds"
|
||||
)
|
||||
return result
|
||||
|
||||
return wrapper
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
def time_execution_async(
|
||||
additional_text: str = "",
|
||||
) -> Callable[
|
||||
[Callable[P, Coroutine[Any, Any, R]]], Callable[P, Coroutine[Any, Any, R]]
|
||||
]:
|
||||
def decorator(
|
||||
func: Callable[P, Coroutine[Any, Any, R]]
|
||||
) -> Callable[P, Coroutine[Any, Any, R]]:
|
||||
@wraps(func)
|
||||
async def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
|
||||
start_time = time.time()
|
||||
result = await func(*args, **kwargs)
|
||||
execution_time = time.time() - start_time
|
||||
logger.debug(
|
||||
f"{additional_text} Execution time: {execution_time:.2f} seconds"
|
||||
)
|
||||
return result
|
||||
|
||||
return wrapper
|
||||
|
||||
return decorator
|
||||
@@ -0,0 +1,54 @@
|
||||
import asyncio
|
||||
|
||||
from mcp import ClientSession, StdioServerParameters
|
||||
from mcp.client.stdio import stdio_client
|
||||
|
||||
# Create server parameters for stdio connection
|
||||
server_params = StdioServerParameters(
|
||||
command="python", # Executable
|
||||
args=["agentic_security/mcp/main.py"], # Your server script
|
||||
env=None, # Optional environment variables
|
||||
)
|
||||
|
||||
|
||||
async def run() -> None:
|
||||
async with stdio_client(server_params) as (read, write):
|
||||
async with ClientSession(read, write) as session:
|
||||
# Initialize the connection --> connection does not work
|
||||
await session.initialize()
|
||||
|
||||
# List available prompts, resources, and tools --> no avalialbe tools
|
||||
prompts = await session.list_prompts()
|
||||
print(f"Available prompts: {prompts}")
|
||||
|
||||
resources = await session.list_resources()
|
||||
print(f"Available resources: {resources}")
|
||||
|
||||
tools = await session.list_tools()
|
||||
print(f"Available tools: {tools}")
|
||||
|
||||
# Call the echo tool --> echo tool iisue
|
||||
echo_result = await session.call_tool(
|
||||
"echo_tool", arguments={"message": "Hello from client!"}
|
||||
)
|
||||
print(f"Tool result: {echo_result}")
|
||||
|
||||
# # Read the echo resource
|
||||
# echo_content, mime_type = await session.read_resource(
|
||||
# "echo://Hello_resource"
|
||||
# )
|
||||
# print(f"Resource content: {echo_content}")
|
||||
# print(f"Resource MIME type: {mime_type}")
|
||||
|
||||
# # Get and use the echo prompt
|
||||
# prompt_result = await session.get_prompt(
|
||||
# "echo_prompt", arguments={"message": "Hello prompt!"}
|
||||
# )
|
||||
# print(f"Prompt result: {prompt_result}")
|
||||
|
||||
# You can perform additional operations here as needed
|
||||
return prompts, resources, tools
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(run())
|
||||
@@ -0,0 +1,109 @@
|
||||
import httpx
|
||||
from mcp.server.fastmcp import FastMCP
|
||||
|
||||
# Initialize MCP server
|
||||
mcp = FastMCP(
|
||||
name="Agentic Security MCP Server",
|
||||
description="MCP server to interact with LLM scanning test",
|
||||
dependencies=["httpx"],
|
||||
)
|
||||
|
||||
# FastAPI Server Configuration
|
||||
AGENTIC_SECURITY = "http://0.0.0.0:8718"
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
async def verify_llm(spec: str) -> dict:
|
||||
"""
|
||||
Verify an LLM model specification using the FastAPI server
|
||||
|
||||
Returns:
|
||||
dict: containing the verification result form the FastAPI server
|
||||
|
||||
Args: spect(str): The specification of the LLM model to verify.
|
||||
|
||||
"""
|
||||
url = f"{AGENTIC_SECURITY}/verify"
|
||||
async with httpx.AsyncClient() as client:
|
||||
response = await client.post(url, json={"spec": spec})
|
||||
return response.json()
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
async def start_scan(
|
||||
llmSpec: str,
|
||||
maxBudget: int,
|
||||
optimize: bool = False,
|
||||
enableMultiStepAttack: bool = False,
|
||||
) -> dict:
|
||||
"""
|
||||
Start an LLM security scan via the FastAPI server.
|
||||
Returns:
|
||||
dict: The scan initiation result from the FastAPI server.
|
||||
|
||||
Args:
|
||||
llmSpec (str): The specification of the LLM model.
|
||||
maxBudget (int): The maximum budget for the scan.
|
||||
optimize (bool, optional): Whether to enable optimization during scanning. Defaults to False.
|
||||
enableMultiStepAttack (bool, optional): Whether to enable multi-step attack
|
||||
|
||||
"""
|
||||
url = f"{AGENTIC_SECURITY}/scan"
|
||||
payload = {
|
||||
"llmSpec": llmSpec,
|
||||
"maxBudget": maxBudget,
|
||||
"datasets": [],
|
||||
"optimize": optimize,
|
||||
"enableMultiStepAttack": enableMultiStepAttack,
|
||||
"probe_datasets": [],
|
||||
"secrets": {},
|
||||
}
|
||||
async with httpx.AsyncClient() as client:
|
||||
response = await client.post(url, json=payload)
|
||||
return response.json()
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
async def stop_scan() -> dict:
|
||||
"""Stop an ongoing scan via the FastAPI server.
|
||||
|
||||
Returns:
|
||||
dict: The confirmation from the FastAPI server that the scan has been stopped.
|
||||
"""
|
||||
url = f"{AGENTIC_SECURITY}/stop"
|
||||
async with httpx.AsyncClient() as client:
|
||||
response = await client.post(url)
|
||||
return response.json()
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
async def get_data_config() -> list:
|
||||
"""
|
||||
Retrieve data configuration from the FastAPI server.
|
||||
|
||||
Returns:
|
||||
list: The response from the FastAPI server, confirming the scan has been stopped.
|
||||
"""
|
||||
url = f"{AGENTIC_SECURITY}/v1/data-config"
|
||||
async with httpx.AsyncClient() as client:
|
||||
response = await client.get(url)
|
||||
return response.json()
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
async def get_spec_templates() -> list:
|
||||
"""
|
||||
Retrieve data configuration from the FastAPI server.
|
||||
|
||||
Returns:
|
||||
list: The LLM specification templates from the FastAPI server.
|
||||
"""
|
||||
url = f"{AGENTIC_SECURITY}/v1/llm-specs"
|
||||
async with httpx.AsyncClient() as client:
|
||||
response = await client.get(url)
|
||||
return response.json()
|
||||
|
||||
|
||||
# Run the MCP server
|
||||
if __name__ == "__main__":
|
||||
mcp.run()
|
||||
@@ -1,7 +1,8 @@
|
||||
from fastapi import Request
|
||||
from loguru import logger
|
||||
from starlette.middleware.base import BaseHTTPMiddleware
|
||||
|
||||
from agentic_security.logutils import logger
|
||||
|
||||
|
||||
class LogNon200ResponsesMiddleware(BaseHTTPMiddleware):
|
||||
async def dispatch(self, request: Request, call_next):
|
||||
|
||||
@@ -8,14 +8,14 @@ except ImportError:
|
||||
|
||||
|
||||
def generate_banner(
|
||||
title="Agentic Security",
|
||||
font="slant",
|
||||
version="v2.1.0",
|
||||
tagline="Proactive Threat Detection & Automated Security Protocols",
|
||||
author="Developed by: [Security Team]",
|
||||
website="Website: https://github.com/msoedov/agentic_security",
|
||||
warning="",
|
||||
):
|
||||
title: str = "Agentic Security",
|
||||
font: str = "slant",
|
||||
version: str = "v2.1.0",
|
||||
tagline: str = "Proactive Threat Detection & Automated Security Protocols",
|
||||
author: str = "Developed by: [Security Team]",
|
||||
website: str = "Website: https://github.com/msoedov/agentic_security",
|
||||
warning: str | None = "", # Using Optional for warning since it might be None
|
||||
) -> str:
|
||||
"""Generate a visually enhanced banner with dynamic width and borders."""
|
||||
# Define the text elements
|
||||
|
||||
@@ -80,8 +80,13 @@ def generate_banner(
|
||||
|
||||
|
||||
def init_banner():
|
||||
return
|
||||
ver = version("agentic_security")
|
||||
print(generate_banner(version=ver))
|
||||
try:
|
||||
print(generate_banner(version=ver))
|
||||
except Exception:
|
||||
# UnicodeEncodeError with codec on some systems
|
||||
pass
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# noqa
|
||||
from agentic_security.primitives.models import CompletionRequest # noqa
|
||||
from agentic_security.primitives.models import ( # noqa
|
||||
CompletionRequest,
|
||||
FileProbeResponse,
|
||||
LLMInfo,
|
||||
Message,
|
||||
|
||||
@@ -3,32 +3,44 @@ import random
|
||||
import time
|
||||
from collections.abc import AsyncGenerator
|
||||
from json import JSONDecodeError
|
||||
from typing import Any
|
||||
|
||||
import httpx
|
||||
import pandas as pd
|
||||
from loguru import logger
|
||||
from skopt import Optimizer
|
||||
from skopt.space import Real
|
||||
|
||||
from agentic_security.config import settings_var
|
||||
from agentic_security.http_spec import Modality
|
||||
from agentic_security.logutils import logger
|
||||
from agentic_security.primitives import Scan, ScanResult
|
||||
from agentic_security.probe_actor.cost_module import calculate_cost
|
||||
from agentic_security.probe_actor.refusal import refusal_heuristic
|
||||
from agentic_security.probe_actor.state import FuzzerState
|
||||
from agentic_security.probe_data import audio_generator, image_generator, msj_data
|
||||
from agentic_security.probe_data.data import prepare_prompts
|
||||
|
||||
# TODO: full log file
|
||||
|
||||
MAX_PROMPT_LENGTH = 2048
|
||||
BUDGET_MULTIPLIER = 100_000_000
|
||||
INITIAL_OPTIMIZER_POINTS = 25
|
||||
MIN_FAILURE_SAMPLES = 5
|
||||
FAILURE_RATE_THRESHOLD = 0.5
|
||||
MAX_PROMPT_LENGTH = settings_var("fuzzer.max_prompt_lenght", 2048)
|
||||
BUDGET_MULTIPLIER = settings_var("fuzzer.budget_multiplier", 100000000)
|
||||
INITIAL_OPTIMIZER_POINTS = settings_var("fuzzer.initial_optimizer_points", 25)
|
||||
MIN_FAILURE_SAMPLES = settings_var("min_failure_samples", 5)
|
||||
FAILURE_RATE_THRESHOLD = settings_var("failure_rate_threshold", 0.5)
|
||||
|
||||
|
||||
async def generate_prompts(
|
||||
prompts: list[str] | AsyncGenerator,
|
||||
) -> AsyncGenerator[str, None]:
|
||||
"""
|
||||
Asynchronously generates and yields individual prompts.
|
||||
|
||||
If the input is a list of strings, the function sequentially yields each string.
|
||||
If the input is an asynchronous generator, it forwards each generated prompt.
|
||||
|
||||
Args:
|
||||
prompts (list[str] | AsyncGenerator): A list of strings or an asynchronous generator of prompts.
|
||||
|
||||
Yields:
|
||||
str: An individual prompt from the list or the asynchronous generator.
|
||||
"""
|
||||
if isinstance(prompts, list):
|
||||
for prompt in prompts:
|
||||
yield prompt
|
||||
@@ -37,7 +49,21 @@ async def generate_prompts(
|
||||
yield prompt
|
||||
|
||||
|
||||
def multi_modality_spec(llm_spec):
|
||||
def get_modality_adapter(llm_spec):
|
||||
"""
|
||||
Returns the appropriate request adapter based on the modality of the LLM specification.
|
||||
|
||||
Depending on the modality of `llm_spec`, the function selects the corresponding request adapter.
|
||||
If the modality is IMAGE or AUDIO, it returns an adapter for handling the respective type.
|
||||
If the modality is TEXT or an unrecognized type, it returns `llm_spec` as is.
|
||||
|
||||
Args:
|
||||
llm_spec: An object containing modality information for the LLM.
|
||||
|
||||
Returns:
|
||||
RequestAdapter | llm_spec: An instance of the appropriate request adapter
|
||||
or the original `llm_spec` if no adaptation is needed.
|
||||
"""
|
||||
match llm_spec.modality:
|
||||
case Modality.IMAGE:
|
||||
return image_generator.RequestAdapter(llm_spec)
|
||||
@@ -50,40 +76,71 @@ def multi_modality_spec(llm_spec):
|
||||
|
||||
|
||||
async def process_prompt(
|
||||
request_factory, prompt, tokens, module_name, refusals, errors, outputs
|
||||
request_factory,
|
||||
prompt: str,
|
||||
tokens: int,
|
||||
module_name: str,
|
||||
fuzzer_state: FuzzerState,
|
||||
) -> tuple[int, bool]:
|
||||
"""
|
||||
Process a single prompt and update the token count and failure status.
|
||||
Processes a single prompt using the provided request factory and updates tracking lists.
|
||||
|
||||
This function sends the given `prompt` to the `request_factory`, checks for errors, and updates
|
||||
the `tokens`, `refusals`, `errors`, and `outputs` lists accordingly. If the request fails or
|
||||
the response indicates a refusal, the function records the issue and returns the updated token count
|
||||
along with a boolean indicating whether the prompt was refused.
|
||||
|
||||
Args:
|
||||
request_factory: An object with a `fn` method used to send the prompt.
|
||||
prompt (str): The input prompt to be processed.
|
||||
tokens (int): The current token count, which will be updated.
|
||||
module_name (str): The name of the module handling the request.
|
||||
fuzzer_state: State tracking object for the fuzzer
|
||||
|
||||
Returns:
|
||||
tuple[int, bool]: Updated token count and a boolean indicating if the prompt was refused.
|
||||
"""
|
||||
try:
|
||||
response = await request_factory.fn(prompt=prompt)
|
||||
|
||||
# Handle HTTP errors
|
||||
if response.status_code == 422:
|
||||
logger.error(f"Invalid prompt: {prompt}, error=422")
|
||||
errors.append((module_name, prompt, 422, "Invalid prompt"))
|
||||
fuzzer_state.add_error(module_name, prompt, 422, "Invalid prompt")
|
||||
return tokens, True
|
||||
|
||||
if response.status_code >= 400:
|
||||
logger.error(f"HTTP {response.status_code} {response.content=}")
|
||||
errors.append((module_name, prompt, response.status_code, response.text))
|
||||
fuzzer_state.add_error(
|
||||
module_name, prompt, response.status_code, response.text
|
||||
)
|
||||
return tokens, True
|
||||
|
||||
# Process successful response
|
||||
response_text = response.text
|
||||
tokens += len(response_text.split())
|
||||
|
||||
# Check if the response indicates a refusal
|
||||
refused = refusal_heuristic(response.json())
|
||||
if refused:
|
||||
refusals.append((module_name, prompt, response.status_code, response_text))
|
||||
fuzzer_state.add_refusal(
|
||||
module_name, prompt, response.status_code, response_text
|
||||
)
|
||||
|
||||
outputs.append((module_name, prompt, response_text, refused))
|
||||
fuzzer_state.add_output(module_name, prompt, response_text, refused)
|
||||
return tokens, refused
|
||||
|
||||
except httpx.RequestError as exc:
|
||||
logger.error(f"Request error: {exc}")
|
||||
errors.append((module_name, prompt, "?", str(exc)))
|
||||
fuzzer_state.add_error(module_name, prompt, "?", str(exc))
|
||||
return tokens, True
|
||||
except JSONDecodeError as json_decode_error:
|
||||
logger.error(f"Jason error: {json_decode_error}")
|
||||
errors.append((module_name, prompt, "?", str(json_decode_error)))
|
||||
logger.error(f"JSON error: {json_decode_error}")
|
||||
fuzzer_state.add_error(module_name, prompt, "?", str(json_decode_error))
|
||||
return tokens, True
|
||||
except Exception as e:
|
||||
logger.exception(f"Unexpected error: {e}")
|
||||
return tokens, False
|
||||
|
||||
|
||||
async def process_prompt_batch(
|
||||
@@ -91,14 +148,29 @@ async def process_prompt_batch(
|
||||
prompts: list[str],
|
||||
tokens: int,
|
||||
module_name: str,
|
||||
refusals,
|
||||
errors,
|
||||
outputs,
|
||||
fuzzer_state: FuzzerState,
|
||||
) -> tuple[int, int]:
|
||||
"""
|
||||
Processes a batch of prompts asynchronously and aggregates the results.
|
||||
|
||||
This function sends multiple prompts concurrently using `process_prompt`,
|
||||
collects the token count and failure status for each prompt, and returns
|
||||
the total number of tokens processed and the number of failed prompts.
|
||||
|
||||
Args:
|
||||
request_factory: An object with a `fn` method used to send the prompts.
|
||||
prompts (list[str]): A list of input prompts to be processed.
|
||||
tokens (int): The initial token count, which will be updated.
|
||||
module_name (str): The name of the module handling the request.
|
||||
fuzzer_state: State tracking object for the fuzzer
|
||||
|
||||
Returns:
|
||||
tuple[int, int]:
|
||||
- Total number of tokens processed.
|
||||
- Number of failed prompts.
|
||||
"""
|
||||
tasks = [
|
||||
process_prompt(
|
||||
request_factory, p, tokens, module_name, refusals, errors, outputs
|
||||
)
|
||||
process_prompt(request_factory, p, tokens, module_name, fuzzer_state)
|
||||
for p in prompts
|
||||
]
|
||||
results = await asyncio.gather(*tasks)
|
||||
@@ -107,133 +179,157 @@ async def process_prompt_batch(
|
||||
return total_tokens, failures
|
||||
|
||||
|
||||
async def perform_single_shot_scan(
|
||||
async def scan_module(
|
||||
request_factory,
|
||||
max_budget: int,
|
||||
datasets: list[dict[str, str]] = [],
|
||||
tools_inbox=None,
|
||||
optimize=False,
|
||||
stop_event: asyncio.Event = None,
|
||||
secrets: dict[str, str] = {},
|
||||
) -> AsyncGenerator[str, None]:
|
||||
"""Perform a standard security scan."""
|
||||
max_budget = max_budget * BUDGET_MULTIPLIER
|
||||
selected_datasets = [m for m in datasets if m["selected"]]
|
||||
request_factory = multi_modality_spec(request_factory)
|
||||
try:
|
||||
yield ScanResult.status_msg("Loading datasets...")
|
||||
prompt_modules = prepare_prompts(
|
||||
dataset_names=[m["dataset_name"] for m in selected_datasets],
|
||||
budget=max_budget,
|
||||
tools_inbox=tools_inbox,
|
||||
options=[m.get("opts", {}) for m in selected_datasets],
|
||||
module,
|
||||
fuzzer_state: FuzzerState,
|
||||
processed_prompts: int = 0,
|
||||
total_prompts: int = 0,
|
||||
max_budget: int = 0,
|
||||
total_tokens: int = 0,
|
||||
optimize: bool = False,
|
||||
stop_event: asyncio.Event | None = None,
|
||||
) -> AsyncGenerator[dict[str, Any], None]:
|
||||
"""
|
||||
Scan a single module.
|
||||
|
||||
Args:
|
||||
request_factory: The factory for creating requests
|
||||
module: The prompt module to scan
|
||||
fuzzer_state: State tracking object for the fuzzer
|
||||
processed_prompts: Number of prompts processed so far
|
||||
total_prompts: Total number of prompts to process
|
||||
max_budget: Maximum token budget
|
||||
total_tokens: Current token count
|
||||
optimize: Whether to use optimization
|
||||
stop_event: Event to stop scanning
|
||||
|
||||
Yields:
|
||||
ScanResult objects as the scan progresses
|
||||
"""
|
||||
tokens = 0
|
||||
module_failures = 0
|
||||
module_prompts = 0
|
||||
failure_rates = []
|
||||
should_stop = False
|
||||
|
||||
# Initialize optimizer if optimization is enabled
|
||||
optimizer = (
|
||||
Optimizer(
|
||||
[Real(0, 1)], base_estimator="GP", n_initial_points=INITIAL_OPTIMIZER_POINTS
|
||||
)
|
||||
yield ScanResult.status_msg("Datasets loaded. Starting scan...")
|
||||
if optimize
|
||||
else None
|
||||
)
|
||||
|
||||
errors = []
|
||||
refusals = []
|
||||
outputs = []
|
||||
total_prompts = sum(len(m.prompts) for m in prompt_modules if not m.lazy)
|
||||
processed_prompts = 0
|
||||
module_size = 0 if module.lazy else len(module.prompts)
|
||||
logger.info(f"Scanning {module.dataset_name} {module_size}")
|
||||
yield ScanResult(
|
||||
module=module.dataset_name,
|
||||
tokens=0,
|
||||
cost=0,
|
||||
progress=0,
|
||||
failureRate=0,
|
||||
prompt="",
|
||||
latency=0,
|
||||
model="",
|
||||
).model_dump_json()
|
||||
|
||||
optimizer = (
|
||||
Optimizer([Real(0, 1)], base_estimator="GP", n_initial_points=25)
|
||||
if optimize
|
||||
else None
|
||||
async for prompt in generate_prompts(module.prompts):
|
||||
if stop_event and stop_event.is_set():
|
||||
stop_event.clear()
|
||||
logger.info("Scan stopped by user.")
|
||||
yield ScanResult.status_msg("Scan stopped by user.")
|
||||
return
|
||||
|
||||
processed_prompts += 1
|
||||
module_prompts += 1
|
||||
|
||||
# Calculate progress based on total processed prompts
|
||||
progress = 100 * processed_prompts / total_prompts if total_prompts else 0
|
||||
progress = progress % 100
|
||||
|
||||
total_tokens -= tokens
|
||||
start = time.time()
|
||||
|
||||
tokens, failed = await process_prompt(
|
||||
request_factory,
|
||||
prompt,
|
||||
tokens,
|
||||
module.dataset_name,
|
||||
fuzzer_state=fuzzer_state,
|
||||
)
|
||||
failure_rates = []
|
||||
|
||||
total_tokens = 0
|
||||
tokens = 0
|
||||
should_stop = False
|
||||
for module in prompt_modules:
|
||||
if should_stop:
|
||||
end = time.time()
|
||||
total_tokens += tokens
|
||||
|
||||
if failed:
|
||||
module_failures += 1
|
||||
|
||||
failure_rate = module_failures / max(module_prompts, 1)
|
||||
failure_rates.append(failure_rate)
|
||||
cost = calculate_cost(tokens)
|
||||
|
||||
response_text = fuzzer_state.get_last_output(prompt) or ""
|
||||
|
||||
yield ScanResult(
|
||||
module=module.dataset_name,
|
||||
tokens=round(tokens / 1000, 1),
|
||||
cost=cost,
|
||||
progress=round(progress, 2),
|
||||
failureRate=round(failure_rate * 100, 2),
|
||||
prompt=prompt[:MAX_PROMPT_LENGTH],
|
||||
latency=end - start,
|
||||
model=response_text,
|
||||
).model_dump_json()
|
||||
|
||||
# Optimization logic
|
||||
if optimize and optimizer and len(failure_rates) >= MIN_FAILURE_SAMPLES:
|
||||
next_point = optimizer.ask()
|
||||
optimizer.tell(next_point, -failure_rate)
|
||||
best_failure_rate = -optimizer.get_result().fun
|
||||
if best_failure_rate > FAILURE_RATE_THRESHOLD:
|
||||
yield ScanResult.status_msg(
|
||||
f"High failure rate detected ({best_failure_rate:.2%}). Stopping this module..."
|
||||
)
|
||||
should_stop = True
|
||||
break
|
||||
tokens = 0
|
||||
module_failures = 0
|
||||
module_size = 0 if module.lazy else len(module.prompts)
|
||||
logger.info(f"Scanning {module.dataset_name} {module_size}")
|
||||
module_prompts = 0 # Reset for each module
|
||||
|
||||
async for prompt in generate_prompts(module.prompts):
|
||||
if stop_event and stop_event.is_set():
|
||||
stop_event.clear()
|
||||
logger.info("Scan stopped by user.")
|
||||
yield ScanResult.status_msg("Scan stopped by user.")
|
||||
return
|
||||
# Budget check
|
||||
if total_tokens > max_budget:
|
||||
logger.info(
|
||||
f"Scan ran out of budget and stopped. {total_tokens=} {max_budget=}"
|
||||
)
|
||||
yield ScanResult.status_msg(
|
||||
f"Scan ran out of budget and stopped. {total_tokens=} {max_budget=}"
|
||||
)
|
||||
should_stop = True
|
||||
break
|
||||
|
||||
processed_prompts += 1
|
||||
module_prompts += 1 # Fixed increment syntax
|
||||
# Calculate progress based on total processed prompts
|
||||
progress = (
|
||||
100 * processed_prompts / total_prompts if total_prompts else 0
|
||||
)
|
||||
if should_stop:
|
||||
break
|
||||
|
||||
total_tokens -= tokens
|
||||
start = time.time()
|
||||
tokens, failed = await process_prompt(
|
||||
request_factory,
|
||||
prompt,
|
||||
tokens,
|
||||
module.dataset_name,
|
||||
refusals,
|
||||
errors,
|
||||
outputs,
|
||||
)
|
||||
end = time.time()
|
||||
total_tokens += tokens
|
||||
return
|
||||
|
||||
if failed:
|
||||
module_failures += 1
|
||||
failure_rate = module_failures / max(module_prompts, 1)
|
||||
failure_rates.append(failure_rate)
|
||||
cost = calculate_cost(tokens)
|
||||
|
||||
last_output = outputs[-1] if outputs else None
|
||||
if last_output and last_output[1] == prompt:
|
||||
response_text = last_output[2]
|
||||
else:
|
||||
response_text = ""
|
||||
async def with_error_handling(agen):
|
||||
"""
|
||||
Wraps an asynchronous generator with error handling.
|
||||
|
||||
yield ScanResult(
|
||||
module=module.dataset_name,
|
||||
tokens=round(tokens / 1000, 1),
|
||||
cost=cost,
|
||||
progress=round(progress, 2),
|
||||
failureRate=round(failure_rate * 100, 2),
|
||||
prompt=prompt[:MAX_PROMPT_LENGTH],
|
||||
latency=end - start,
|
||||
model=response_text,
|
||||
).model_dump_json()
|
||||
This function iterates over an asynchronous generator, yielding its values.
|
||||
If an exception occurs, it logs the error and yields a failure message.
|
||||
Finally, it ensures that a completion message is always yielded.
|
||||
|
||||
if optimize and len(failure_rates) >= 5:
|
||||
next_point = optimizer.ask()
|
||||
optimizer.tell(next_point, -failure_rate)
|
||||
best_failure_rate = -optimizer.get_result().fun
|
||||
if best_failure_rate > 0.5:
|
||||
yield ScanResult.status_msg(
|
||||
f"High failure rate detected ({best_failure_rate:.2%}). Stopping this module..."
|
||||
)
|
||||
should_stop = True
|
||||
break
|
||||
if total_tokens > max_budget:
|
||||
logger.info(
|
||||
f"Scan ran out of budget and stopped. {total_tokens=} {max_budget=}"
|
||||
)
|
||||
yield ScanResult.status_msg(
|
||||
f"Scan ran out of budget and stopped. {total_tokens=} {max_budget=}"
|
||||
)
|
||||
should_stop = True
|
||||
break
|
||||
|
||||
yield ScanResult.status_msg("Scan completed.")
|
||||
|
||||
failure_data = errors + refusals
|
||||
df = pd.DataFrame(
|
||||
failure_data, columns=["module", "prompt", "status_code", "content"]
|
||||
)
|
||||
df.to_csv("failures.csv", index=False)
|
||||
Args:
|
||||
agen: An asynchronous generator that produces scan results.
|
||||
|
||||
Yields:
|
||||
ScanResult: Either a successful result, an error message if an
|
||||
exception occurs, or a completion message at the end.
|
||||
"""
|
||||
try:
|
||||
async for t in agen:
|
||||
yield t
|
||||
except Exception as e:
|
||||
logger.exception("Scan failed")
|
||||
yield ScanResult.status_msg(f"Scan failed: {str(e)}")
|
||||
@@ -241,154 +337,262 @@ async def perform_single_shot_scan(
|
||||
yield ScanResult.status_msg("Scan completed.")
|
||||
|
||||
|
||||
async def perform_single_shot_scan(
|
||||
request_factory,
|
||||
max_budget: int,
|
||||
datasets: list[dict[str, str]] = [],
|
||||
tools_inbox=None,
|
||||
optimize: bool = False,
|
||||
stop_event: asyncio.Event | None = None,
|
||||
secrets: dict[str, str] = {},
|
||||
) -> AsyncGenerator[str, None]:
|
||||
"""
|
||||
Perform a standard security scan using a given request factory.
|
||||
|
||||
This function processes security scan prompts from selected datasets while
|
||||
respecting a predefined token budget. It supports optimization, failure tracking,
|
||||
and early stopping based on budget constraints or user intervention.
|
||||
|
||||
Args:
|
||||
request_factory: A factory function that generates requests for processing prompts.
|
||||
max_budget (int): The maximum token budget for the scan.
|
||||
datasets (list[dict[str, str]], optional): A list of datasets containing security prompts.
|
||||
tools_inbox: Optional additional tools for processing (default: None).
|
||||
optimize (bool, optional): Whether to enable failure rate optimization (default: False).
|
||||
stop_event (asyncio.Event, optional): An event to signal early termination (default: None).
|
||||
secrets (dict[str, str], optional): A dictionary of secrets for authentication (default: {}).
|
||||
|
||||
Yields:
|
||||
str: JSON-encoded scan results or status messages.
|
||||
|
||||
The function iterates over prompts, processes them asynchronously, and updates
|
||||
failure statistics and token usage. If the scan exceeds the budget or failure rate is too high,
|
||||
it stops execution. Results are saved to a CSV file upon completion.
|
||||
"""
|
||||
max_budget = max_budget * BUDGET_MULTIPLIER
|
||||
selected_datasets = [m for m in datasets if m["selected"]]
|
||||
request_factory = get_modality_adapter(request_factory)
|
||||
|
||||
yield ScanResult.status_msg("Loading datasets...")
|
||||
prompt_modules = prepare_prompts(
|
||||
dataset_names=[m["dataset_name"] for m in selected_datasets],
|
||||
budget=max_budget,
|
||||
tools_inbox=tools_inbox,
|
||||
options=[m.get("opts", {}) for m in selected_datasets],
|
||||
)
|
||||
yield ScanResult.status_msg("Datasets loaded. Starting scan...")
|
||||
|
||||
fuzzer_state = FuzzerState()
|
||||
total_prompts = sum(len(m.prompts) for m in prompt_modules if not m.lazy)
|
||||
processed_prompts = 0
|
||||
|
||||
total_tokens = 0
|
||||
for module in prompt_modules:
|
||||
module_gen = scan_module(
|
||||
request_factory=request_factory,
|
||||
module=module,
|
||||
fuzzer_state=fuzzer_state,
|
||||
processed_prompts=processed_prompts,
|
||||
total_prompts=total_prompts,
|
||||
max_budget=max_budget,
|
||||
total_tokens=total_tokens,
|
||||
optimize=optimize,
|
||||
stop_event=stop_event,
|
||||
)
|
||||
try:
|
||||
async for result in module_gen:
|
||||
yield result
|
||||
except Exception:
|
||||
logger.error("Module exception")
|
||||
continue
|
||||
# Update processed_prompts count
|
||||
module_size = 0 if module.lazy else len(module.prompts)
|
||||
processed_prompts += module_size
|
||||
|
||||
yield ScanResult.status_msg("Scan completed.")
|
||||
fuzzer_state.export_failures("failures.csv")
|
||||
|
||||
|
||||
async def perform_many_shot_scan(
|
||||
request_factory,
|
||||
max_budget: int,
|
||||
datasets: list[dict[str, str]] = [],
|
||||
probe_datasets: list[dict[str, str]] = [],
|
||||
tools_inbox=None,
|
||||
optimize=False,
|
||||
stop_event: asyncio.Event = None,
|
||||
optimize: bool = False,
|
||||
stop_event: asyncio.Event | None = None,
|
||||
probe_frequency: float = 0.2,
|
||||
max_ctx_length: int = 10_000,
|
||||
secrets: dict[str, str] = {},
|
||||
) -> AsyncGenerator[str, None]:
|
||||
"""Perform a multi-step security scan with probe injection."""
|
||||
request_factory = multi_modality_spec(request_factory)
|
||||
try:
|
||||
# Load main and probe datasets
|
||||
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,
|
||||
tools_inbox=tools_inbox,
|
||||
)
|
||||
yield ScanResult.status_msg("Loading datasets for MSJ...")
|
||||
msj_modules = msj_data.prepare_prompts(probe_datasets)
|
||||
yield ScanResult.status_msg("Datasets loaded. Starting scan...")
|
||||
"""
|
||||
Perform a multi-step security scan with probe injection.
|
||||
|
||||
errors = []
|
||||
refusals = []
|
||||
outputs = []
|
||||
total_prompts = sum(len(m.prompts) for m in prompt_modules if not m.lazy)
|
||||
processed_prompts = 0
|
||||
This function executes a security scan while periodically injecting probe datasets
|
||||
to test system robustness. It tracks failures, optimizes scan efficiency,
|
||||
and ensures adherence to a predefined token budget.
|
||||
|
||||
optimizer = (
|
||||
Optimizer([Real(0, 1)], base_estimator="GP", n_initial_points=25)
|
||||
if optimize
|
||||
else None
|
||||
)
|
||||
failure_rates = []
|
||||
Args:
|
||||
request_factory: A factory function that generates requests for processing prompts.
|
||||
max_budget (int): The maximum token budget for the scan.
|
||||
datasets (list[dict[str, str]], optional): The main datasets for scanning.
|
||||
probe_datasets (list[dict[str, str]], optional): Additional datasets for probe injection.
|
||||
tools_inbox: Optional tools for additional processing (default: None).
|
||||
optimize (bool, optional): Whether to enable failure rate optimization (default: False).
|
||||
stop_event (asyncio.Event, optional): An event to signal early termination (default: None).
|
||||
probe_frequency (float, optional): The probability of probe injection (default: 0.2).
|
||||
max_ctx_length (int, optional): The maximum context length before resetting (default: 10,000 tokens).
|
||||
secrets (dict[str, str], optional): A dictionary of secrets for authentication (default: {}).
|
||||
|
||||
for module in prompt_modules:
|
||||
module_failures = 0
|
||||
module_size = 0 if module.lazy else len(module.prompts)
|
||||
logger.info(f"Scanning {module.dataset_name} {module_size}")
|
||||
Yields:
|
||||
str: JSON-encoded scan results or status messages.
|
||||
|
||||
async for prompt in generate_prompts(module.prompts):
|
||||
if stop_event and stop_event.is_set():
|
||||
stop_event.clear()
|
||||
logger.info("Scan stopped by user.")
|
||||
yield ScanResult.status_msg("Scan stopped by user.")
|
||||
return
|
||||
tokens = 0
|
||||
processed_prompts += 1
|
||||
progress = (
|
||||
100 * processed_prompts / total_prompts if total_prompts else 0
|
||||
This function iterates over prompts, injects probe prompts at random intervals,
|
||||
processes them asynchronously, and tracks failure rates. If failure rates exceed a threshold
|
||||
or budget is exhausted, the scan is stopped early. Results are saved to a CSV file upon completion.
|
||||
"""
|
||||
request_factory = get_modality_adapter(request_factory)
|
||||
# Load main and probe datasets
|
||||
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,
|
||||
tools_inbox=tools_inbox,
|
||||
)
|
||||
yield ScanResult.status_msg("Loading datasets for MSJ...")
|
||||
msj_modules = msj_data.prepare_prompts(probe_datasets)
|
||||
yield ScanResult.status_msg("Datasets loaded. Starting scan...")
|
||||
|
||||
fuzzer_state = FuzzerState()
|
||||
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:
|
||||
module_failures = 0
|
||||
module_size = 0 if module.lazy else len(module.prompts)
|
||||
logger.info(f"Scanning {module.dataset_name} {module_size}")
|
||||
|
||||
async for prompt in generate_prompts(module.prompts):
|
||||
if stop_event and stop_event.is_set():
|
||||
stop_event.clear()
|
||||
logger.info("Scan stopped by user.")
|
||||
yield ScanResult.status_msg("Scan stopped by user.")
|
||||
return
|
||||
|
||||
tokens = 0
|
||||
processed_prompts += 1
|
||||
progress = 100 * processed_prompts / total_prompts if total_prompts else 0
|
||||
progress = progress % 100
|
||||
|
||||
full_prompt = ""
|
||||
msj_module = random.choice(msj_modules)
|
||||
|
||||
prompt_tokens = len(full_prompt.split())
|
||||
tokens += prompt_tokens
|
||||
|
||||
injected = False
|
||||
for _ in range(20):
|
||||
if injected:
|
||||
break
|
||||
|
||||
m_prompt = random.choice(msj_module.prompts)
|
||||
full_prompt += "\n" + m_prompt
|
||||
if tokens > max_ctx_length:
|
||||
full_prompt = "\n" + prompt
|
||||
injected = True
|
||||
|
||||
tokens, failed = await process_prompt(
|
||||
request_factory,
|
||||
full_prompt,
|
||||
tokens,
|
||||
module.dataset_name,
|
||||
fuzzer_state=fuzzer_state,
|
||||
)
|
||||
if failed:
|
||||
module_failures += 1
|
||||
break
|
||||
if injected:
|
||||
break
|
||||
|
||||
full_prompt = ""
|
||||
msj_module = random.choice(msj_modules)
|
||||
failure_rate = module_failures / max(processed_prompts, 1)
|
||||
failure_rates.append(failure_rate)
|
||||
cost = calculate_cost(tokens)
|
||||
|
||||
prompt_tokens = len(full_prompt.split())
|
||||
tokens += prompt_tokens
|
||||
yield ScanResult(
|
||||
module=module.dataset_name,
|
||||
tokens=round(tokens / 1000, 1),
|
||||
cost=cost,
|
||||
progress=round(progress, 2),
|
||||
failureRate=round(failure_rate * 100, 2),
|
||||
prompt=prompt[:MAX_PROMPT_LENGTH],
|
||||
).model_dump_json()
|
||||
|
||||
injected = False
|
||||
for _ in range(20):
|
||||
if injected:
|
||||
break
|
||||
if optimize and len(failure_rates) >= MIN_FAILURE_SAMPLES:
|
||||
yield ScanResult.status_msg(
|
||||
f"High failure rate detected ({failure_rate:.2%}). Stopping this module..."
|
||||
)
|
||||
break
|
||||
|
||||
m_prompt = random.choice(msj_module.prompts)
|
||||
full_prompt += "\n" + m_prompt
|
||||
if tokens > max_ctx_length:
|
||||
full_prompt = "\n" + prompt
|
||||
injected = True
|
||||
|
||||
tokens, failed = await process_prompt(
|
||||
request_factory,
|
||||
full_prompt,
|
||||
tokens,
|
||||
module.dataset_name,
|
||||
refusals,
|
||||
errors,
|
||||
outputs,
|
||||
)
|
||||
if failed:
|
||||
module_failures += 1
|
||||
break
|
||||
if injected:
|
||||
break
|
||||
|
||||
failure_rate = module_failures / max(processed_prompts, 1)
|
||||
failure_rates.append(failure_rate)
|
||||
cost = calculate_cost(tokens)
|
||||
|
||||
yield ScanResult(
|
||||
module=module.dataset_name,
|
||||
tokens=round(tokens / 1000, 1),
|
||||
cost=cost,
|
||||
progress=round(progress, 2),
|
||||
failureRate=round(failure_rate * 100, 2),
|
||||
prompt=prompt[:MAX_PROMPT_LENGTH],
|
||||
).model_dump_json()
|
||||
|
||||
if optimize and len(failure_rates) >= 5:
|
||||
next_point = optimizer.ask()
|
||||
optimizer.tell(next_point, -failure_rate)
|
||||
best_failure_rate = -optimizer.get_result().fun
|
||||
if best_failure_rate > 0.5:
|
||||
yield ScanResult.status_msg(
|
||||
f"High failure rate detected ({best_failure_rate:.2%}). Stopping this module..."
|
||||
)
|
||||
break
|
||||
|
||||
yield ScanResult.status_msg("Scan completed.")
|
||||
|
||||
df = pd.DataFrame(
|
||||
errors + refusals, columns=["module", "prompt", "status_code", "content"]
|
||||
)
|
||||
df.to_csv("failures.csv", index=False)
|
||||
|
||||
except Exception as e:
|
||||
logger.exception("Scan failed")
|
||||
yield ScanResult.status_msg(f"Scan failed: {str(e)}")
|
||||
raise e
|
||||
yield ScanResult.status_msg("Scan completed.")
|
||||
fuzzer_state.export_failures("failures.csv")
|
||||
|
||||
|
||||
def scan_router(
|
||||
request_factory,
|
||||
scan_parameters: Scan,
|
||||
tools_inbox=None,
|
||||
stop_event: asyncio.Event = None,
|
||||
stop_event: asyncio.Event | None = None,
|
||||
):
|
||||
"""
|
||||
Route scan requests to the appropriate scanning function.
|
||||
|
||||
This function determines whether to perform a multi-step or single-shot
|
||||
security scan based on the provided scan parameters.
|
||||
|
||||
Args:
|
||||
request_factory: A factory function to generate requests for processing prompts.
|
||||
scan_parameters (Scan): An object containing the parameters for the scan, including:
|
||||
- enableMultiStepAttack (bool): Whether to perform a multi-step scan.
|
||||
- maxBudget (int): The maximum token budget for the scan.
|
||||
- datasets (list[dict[str, str]]): The datasets to scan.
|
||||
- probe_datasets (list[dict[str, str]], optional): Datasets for probe injection (multi-step only).
|
||||
- optimize (bool): Whether to enable optimization.
|
||||
- secrets (dict[str, str], optional): A dictionary of secrets for authentication.
|
||||
tools_inbox: Optional tools for additional processing (default: None).
|
||||
stop_event (asyncio.Event, optional): An event to signal early termination (default: None).
|
||||
|
||||
Returns:
|
||||
A function wrapped with `with_error_handling`, which executes either:
|
||||
- `perform_many_shot_scan` for multi-step scanning.
|
||||
- `perform_single_shot_scan` for single-shot scanning.
|
||||
|
||||
The function ensures that the appropriate scanning method is chosen based on
|
||||
the `enableMultiStepAttack` flag in `scan_parameters`.
|
||||
"""
|
||||
if scan_parameters.enableMultiStepAttack:
|
||||
return perform_many_shot_scan(
|
||||
request_factory=request_factory,
|
||||
max_budget=scan_parameters.maxBudget,
|
||||
datasets=scan_parameters.datasets,
|
||||
probe_datasets=scan_parameters.probe_datasets,
|
||||
tools_inbox=tools_inbox,
|
||||
optimize=scan_parameters.optimize,
|
||||
stop_event=stop_event,
|
||||
secrets=scan_parameters.secrets,
|
||||
return with_error_handling(
|
||||
perform_many_shot_scan(
|
||||
request_factory=request_factory,
|
||||
max_budget=scan_parameters.maxBudget,
|
||||
datasets=scan_parameters.datasets,
|
||||
probe_datasets=scan_parameters.probe_datasets,
|
||||
tools_inbox=tools_inbox,
|
||||
optimize=scan_parameters.optimize,
|
||||
stop_event=stop_event,
|
||||
secrets=scan_parameters.secrets,
|
||||
)
|
||||
)
|
||||
else:
|
||||
return perform_single_shot_scan(
|
||||
request_factory=request_factory,
|
||||
max_budget=scan_parameters.maxBudget,
|
||||
datasets=scan_parameters.datasets,
|
||||
tools_inbox=tools_inbox,
|
||||
optimize=scan_parameters.optimize,
|
||||
stop_event=stop_event,
|
||||
secrets=scan_parameters.secrets,
|
||||
return with_error_handling(
|
||||
perform_single_shot_scan(
|
||||
request_factory=request_factory,
|
||||
max_budget=scan_parameters.maxBudget,
|
||||
datasets=scan_parameters.datasets,
|
||||
tools_inbox=tools_inbox,
|
||||
optimize=scan_parameters.optimize,
|
||||
stop_event=stop_event,
|
||||
secrets=scan_parameters.secrets,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import asyncio
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
import httpx
|
||||
@@ -7,13 +6,10 @@ from pydantic import BaseModel, Field
|
||||
from pydantic_ai import Agent, RunContext
|
||||
|
||||
from agentic_security.http_spec import LLMSpec
|
||||
from agentic_security.logutils import logger
|
||||
|
||||
LLM_SPECS = []
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AgentSpecification(BaseModel):
|
||||
name: str | None = Field(None, description="Name of the LLM/agent")
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
from agentic_security.refusal_classifier import RefusalClassifier
|
||||
from agentic_security.refusal_classifier.model import RefusalClassifier
|
||||
|
||||
classifier = RefusalClassifier()
|
||||
classifier.load_model()
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import pandas as pd
|
||||
|
||||
|
||||
class FuzzerState:
|
||||
"""Container for tracking scan results"""
|
||||
|
||||
def __init__(self):
|
||||
self.errors = []
|
||||
self.refusals = []
|
||||
self.outputs = []
|
||||
|
||||
def add_error(
|
||||
self,
|
||||
module_name: str,
|
||||
prompt: str,
|
||||
status_code: int | str,
|
||||
error_msg: str,
|
||||
):
|
||||
"""Add an error to the state"""
|
||||
self.errors.append((module_name, prompt, status_code, error_msg))
|
||||
|
||||
def add_refusal(
|
||||
self, module_name: str, prompt: str, status_code: int, response_text: str
|
||||
):
|
||||
"""Add a refusal to the state"""
|
||||
self.refusals.append((module_name, prompt, status_code, response_text))
|
||||
|
||||
def add_output(
|
||||
self, module_name: str, prompt: str, response_text: str, refused: bool
|
||||
):
|
||||
"""Add an output to the state"""
|
||||
self.outputs.append((module_name, prompt, response_text, refused))
|
||||
|
||||
def get_last_output(self, prompt: str) -> str | None:
|
||||
"""Get the last output for a given prompt"""
|
||||
for output in reversed(self.outputs):
|
||||
if output[1] == prompt:
|
||||
return output[2]
|
||||
return None
|
||||
|
||||
def export_failures(self, filename: str = "failures.csv"):
|
||||
"""Export failures to a CSV file"""
|
||||
failure_data = self.errors + self.refusals
|
||||
df = pd.DataFrame(
|
||||
failure_data, columns=["module", "prompt", "status_code", "content"]
|
||||
)
|
||||
df.to_csv(filename, index=False)
|
||||
@@ -1,4 +1,4 @@
|
||||
from .data import load_local_csv
|
||||
from .data import load_local_csv, load_local_csv_files
|
||||
|
||||
REGISTRY_V0 = [
|
||||
{
|
||||
@@ -484,3 +484,18 @@ REGISTRY = REGISTRY_V0 + [
|
||||
"modality": "text",
|
||||
},
|
||||
]
|
||||
|
||||
for ds in load_local_csv_files():
|
||||
REGISTRY.append(
|
||||
{
|
||||
"dataset_name": ds.dataset_name,
|
||||
"num_prompts": len(ds.prompts),
|
||||
"tokens": ds.prompts,
|
||||
"approx_cost": 0.0,
|
||||
"is_active": True,
|
||||
"source": f"Local file dataset: {ds.metadata['src']}",
|
||||
"selected": False,
|
||||
"url": "",
|
||||
"modality": "text",
|
||||
}
|
||||
)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import base64
|
||||
import logging
|
||||
import os
|
||||
import platform
|
||||
import subprocess
|
||||
@@ -7,6 +8,16 @@ import uuid
|
||||
import httpx
|
||||
from cache_to_disk import cache_to_disk
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AudioGenerationError(Exception):
|
||||
"""Custom exception for errors during audio generation."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
def encode(content: bytes) -> str:
|
||||
encoded_content = base64.b64encode(content).decode("utf-8")
|
||||
@@ -41,12 +52,25 @@ def generate_audio_mac_wav(prompt: str) -> bytes:
|
||||
# Read the WAV file into memory
|
||||
with open(temp_wav_path, "rb") as f:
|
||||
audio_bytes = f.read()
|
||||
|
||||
except subprocess.CalledProcessError as e:
|
||||
logger.error(f"Subprocess error: {e}")
|
||||
raise AudioGenerationError("Failed to generate or convert audio.") from e
|
||||
except FileNotFoundError as e:
|
||||
logger.error(f"File not found: {e}")
|
||||
raise AudioGenerationError("Required file not found.") from e
|
||||
except Exception as e:
|
||||
logger.exception("Unexpected error occurred.")
|
||||
raise AudioGenerationError(
|
||||
"An unexpected error occurred during audio generation."
|
||||
) from e
|
||||
finally:
|
||||
# Clean up the temporary files
|
||||
if os.path.exists(temp_aiff_path):
|
||||
os.remove(temp_aiff_path)
|
||||
if os.path.exists(temp_wav_path):
|
||||
os.remove(temp_wav_path)
|
||||
for path in (temp_aiff_path, temp_wav_path):
|
||||
try:
|
||||
if os.path.exists(path):
|
||||
os.remove(path)
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to delete temporary file {path}: {e}")
|
||||
|
||||
# Return the audio bytes
|
||||
return audio_bytes
|
||||
|
||||
+425
-398
@@ -1,14 +1,16 @@
|
||||
import io
|
||||
import os
|
||||
import random
|
||||
from functools import lru_cache
|
||||
from collections.abc import Callable, Iterator
|
||||
from functools import partial
|
||||
from typing import Any, TypeVar
|
||||
|
||||
import httpx
|
||||
import pandas as pd
|
||||
from cache_to_disk import cache_to_disk
|
||||
from datasets import load_dataset
|
||||
from loguru import logger
|
||||
|
||||
from agentic_security.logutils import logger
|
||||
from agentic_security.probe_data import stenography_fn
|
||||
from agentic_security.probe_data.models import ProbeDataset
|
||||
from agentic_security.probe_data.modules import (
|
||||
@@ -19,281 +21,293 @@ from agentic_security.probe_data.modules import (
|
||||
rl_model,
|
||||
)
|
||||
|
||||
# Type aliases for clarity
|
||||
T = TypeVar("T")
|
||||
FilterFn = Callable[[pd.Series], bool]
|
||||
ColumnMappings = dict[str, str]
|
||||
DatasetLoader = Callable[[], ProbeDataset]
|
||||
TransformFn = Callable[[str], str]
|
||||
|
||||
@cache_to_disk()
|
||||
def load_dataset_general(
|
||||
dataset_name,
|
||||
dataset_split="train",
|
||||
column_mappings=None,
|
||||
filter_fn=None,
|
||||
custom_url=None,
|
||||
additional_metadata=None,
|
||||
):
|
||||
"""
|
||||
Generalized function to load datasets with flexible configurations.
|
||||
|
||||
:param dataset_name: Name of the dataset or URL for custom CSVs
|
||||
:param dataset_split: Split to load from the dataset (e.g., "train")
|
||||
:param column_mappings: Dictionary mapping dataset columns to expected keys, e.g., {'prompt': 'query'}
|
||||
:param filter_fn: A filtering function that takes a row and returns True/False
|
||||
:param custom_url: URL for custom CSV datasets
|
||||
:param additional_metadata: Additional metadata to include in the ProbeDataset
|
||||
:return: A ProbeDataset object with the processed data
|
||||
"""
|
||||
if custom_url:
|
||||
logger.info(f"Loading custom CSV dataset from {custom_url}")
|
||||
r = httpx.get(custom_url)
|
||||
content = r.content
|
||||
df = pd.read_csv(io.StringIO(content.decode("utf-8")))
|
||||
else:
|
||||
logger.info(f"Loading dataset {dataset_name} from Hugging Face datasets")
|
||||
dataset = load_dataset(dataset_name)
|
||||
df = pd.DataFrame(dataset[dataset_split])
|
||||
# Core data loading utilities
|
||||
def fetch_csv_content(url: str) -> str:
|
||||
"""Fetch CSV content from a URL."""
|
||||
response = httpx.get(url)
|
||||
response.raise_for_status() # Raise exception for bad responses
|
||||
return response.content.decode("utf-8")
|
||||
|
||||
# Apply column mappings if provided
|
||||
if column_mappings:
|
||||
df.rename(columns=column_mappings, inplace=True)
|
||||
|
||||
# Filter rows if filter_fn is provided
|
||||
def load_df_from_source(source: str, is_url: bool = False) -> pd.DataFrame:
|
||||
"""Load DataFrame from either URL or Hugging Face dataset."""
|
||||
if is_url:
|
||||
logger.info(f"Loading custom CSV from {source}")
|
||||
return pd.read_csv(io.StringIO(fetch_csv_content(source)))
|
||||
logger.info(f"Loading dataset {source} from Hugging Face")
|
||||
return pd.DataFrame(load_dataset(source)["train"])
|
||||
|
||||
|
||||
def transform_df(
|
||||
df: pd.DataFrame,
|
||||
mappings: ColumnMappings | None = None,
|
||||
filter_fn: FilterFn | None = None,
|
||||
) -> pd.DataFrame:
|
||||
"""Apply transformations to DataFrame."""
|
||||
result = df.copy()
|
||||
if mappings:
|
||||
result.rename(columns=mappings, inplace=True)
|
||||
if filter_fn:
|
||||
df = df[df.apply(filter_fn, axis=1)]
|
||||
|
||||
# Extract prompts
|
||||
prompts = df[column_mappings.get("prompt", "prompt")].tolist()
|
||||
|
||||
return ProbeDataset(
|
||||
dataset_name=dataset_name,
|
||||
metadata=additional_metadata or {},
|
||||
prompts=prompts,
|
||||
tokens=count_words_in_list(prompts),
|
||||
approx_cost=0.0,
|
||||
)
|
||||
result = result[result.apply(filter_fn, axis=1)]
|
||||
return result
|
||||
|
||||
|
||||
def count_words_in_list(str_list):
|
||||
"""Calculate the total number of words in a given list of strings.
|
||||
|
||||
:param str_list: List of strings
|
||||
:return: Total number of words across all strings in the list
|
||||
"""
|
||||
total_words = sum(len(str(s).split()) for s in str_list)
|
||||
return total_words
|
||||
|
||||
|
||||
@cache_to_disk()
|
||||
def load_dataset_v1():
|
||||
return load_dataset_general(
|
||||
dataset_name="ShawnMenz/DAN_jailbreak",
|
||||
column_mappings={"prompt": "prompt", "jailbreak": "jailbreak"},
|
||||
filter_fn=lambda row: row["jailbreak"],
|
||||
)
|
||||
|
||||
|
||||
@cache_to_disk()
|
||||
def load_dataset_v2():
|
||||
return load_dataset_general(
|
||||
dataset_name="deepset/prompt-injections",
|
||||
column_mappings={"prompt": "text", "jailbreak": "label"},
|
||||
filter_fn=lambda row: row["label"],
|
||||
)
|
||||
|
||||
|
||||
@cache_to_disk()
|
||||
def load_dataset_v4():
|
||||
return load_dataset_general(
|
||||
dataset_name="notrichardren/refuse-to-answer-prompts",
|
||||
column_mappings={"prompt": "claim", "jailbreak": "label"},
|
||||
filter_fn=lambda row: row["label"],
|
||||
)
|
||||
|
||||
|
||||
@cache_to_disk()
|
||||
def load_dataset_v3():
|
||||
return load_dataset_general(
|
||||
dataset_name="rubend18/ChatGPT-Jailbreak-Prompts",
|
||||
column_mappings={"prompt": "Prompt"},
|
||||
filter_fn=lambda row: row["label"],
|
||||
)
|
||||
|
||||
|
||||
@cache_to_disk()
|
||||
def load_dataset_v6():
|
||||
dataset = load_dataset("markush1/LLM-Jailbreak-Classifier")
|
||||
bad_prompts = [
|
||||
p
|
||||
for p, c in zip(dataset["train"]["prompt"], dataset["train"]["classification"])
|
||||
if c == "jailbreak"
|
||||
]
|
||||
return ProbeDataset(
|
||||
dataset_name="markush1/LLM-Jailbreak-Classifier",
|
||||
metadata={},
|
||||
prompts=bad_prompts,
|
||||
tokens=count_words_in_list(bad_prompts),
|
||||
approx_cost=0.0,
|
||||
)
|
||||
|
||||
|
||||
@cache_to_disk()
|
||||
def load_dataset_v7():
|
||||
splits = {
|
||||
"mini_JailBreakV_28K": "JailBreakV_28K/mini_JailBreakV_28K.csv",
|
||||
"JailBreakV_28K": "JailBreakV_28K/JailBreakV_28K.csv",
|
||||
}
|
||||
df = pd.read_csv(
|
||||
"hf://datasets/JailbreakV-28K/JailBreakV-28k/" + splits["JailBreakV_28K"]
|
||||
)
|
||||
bad_prompts = df["jailbreak_query"].tolist()
|
||||
print(df.shape)
|
||||
return ProbeDataset(
|
||||
dataset_name="JailbreakV-28K/JailBreakV-28k",
|
||||
metadata={},
|
||||
prompts=bad_prompts,
|
||||
tokens=count_words_in_list(bad_prompts),
|
||||
approx_cost=0.0,
|
||||
)
|
||||
|
||||
|
||||
@cache_to_disk()
|
||||
def load_dataset_v8():
|
||||
df = pd.read_csv(
|
||||
"hf://datasets/ShawnMenz/jailbreak_sft_rm_ds/jailbreak_sft_rm_ds.csv",
|
||||
names=["jailbreak", "prompt"],
|
||||
)
|
||||
filtered = df[df["jailbreak"] == "jailbreak"]["prompt"].tolist()
|
||||
return ProbeDataset(
|
||||
dataset_name="JailbreakV-28K/JailBreakV-28k",
|
||||
metadata={},
|
||||
prompts=filtered,
|
||||
tokens=count_words_in_list(filtered),
|
||||
approx_cost=0.0,
|
||||
)
|
||||
|
||||
|
||||
@cache_to_disk()
|
||||
def load_dataset_v5():
|
||||
ds = []
|
||||
for c in [
|
||||
"AdvBench",
|
||||
"ForbiddenQuestion",
|
||||
"MJP",
|
||||
"MaliciousInstruct",
|
||||
"QuestionList",
|
||||
]:
|
||||
dataset = load_dataset("Lemhf14/EasyJailbreak_Datasets", c)
|
||||
bad_prompts = dataset["train"]["query"]
|
||||
ds.extend(bad_prompts)
|
||||
|
||||
return ProbeDataset(
|
||||
dataset_name="Lemhf14/EasyJailbreak_Datasets",
|
||||
metadata={},
|
||||
prompts=ds,
|
||||
tokens=count_words_in_list(ds),
|
||||
approx_cost=0.0,
|
||||
)
|
||||
|
||||
|
||||
@cache_to_disk()
|
||||
def load_generic_csv(url, name, column="prompt", predicator=None):
|
||||
r = httpx.get(url)
|
||||
content = r.content
|
||||
df = pd.read_csv(io.StringIO(content.decode("utf-8")))
|
||||
logger.info(f"Loaded {len(df)} prompts from {url}")
|
||||
filtered_prompts = df[df.apply(predicator, axis=1)][column].tolist()
|
||||
def create_probe_dataset(
|
||||
name: str, prompts: list[str], metadata: dict[str, Any] | None = None
|
||||
) -> ProbeDataset:
|
||||
"""Create a ProbeDataset from prompts."""
|
||||
metadata = metadata or {}
|
||||
return ProbeDataset(
|
||||
dataset_name=name,
|
||||
metadata={},
|
||||
prompts=filtered_prompts,
|
||||
tokens=count_words_in_list(filtered_prompts),
|
||||
metadata=metadata,
|
||||
prompts=prompts,
|
||||
tokens=sum(len(str(s).split()) for s in prompts),
|
||||
approx_cost=0.0,
|
||||
)
|
||||
|
||||
|
||||
def prepare_prompts(dataset_names, budget, tools_inbox=None, options=[]):
|
||||
# ## Datasets used and cleaned:
|
||||
# markush1/LLM-Jailbreak-Classifier
|
||||
# 1. Open-Orca/OpenOrca
|
||||
# 2. ShawnMenz/DAN_jailbreak
|
||||
# 3. EddyLuo/JailBreakV_28K
|
||||
# 4. https://raw.githubusercontent.com/verazuo/jailbreak_llms/main/data/jailbreak_prompts.csv
|
||||
# Generalized dataset loader
|
||||
@cache_to_disk()
|
||||
def load_dataset_generic(
|
||||
name: str,
|
||||
mappings: ColumnMappings | None = None,
|
||||
filter_fn: FilterFn | None = None,
|
||||
url: str | None = None,
|
||||
metadata: dict[str, Any] | None = None,
|
||||
) -> ProbeDataset:
|
||||
"""Load and process a dataset with flexible configuration."""
|
||||
try:
|
||||
df = load_df_from_source(url or name, is_url=bool(url))
|
||||
transformed_df = transform_df(df, mappings, filter_fn)
|
||||
|
||||
dataset_map = {
|
||||
"ShawnMenz/DAN_jailbreak": load_dataset_v1,
|
||||
"deepset/prompt-injections": load_dataset_v2,
|
||||
"notrichardren/refuse-to-answer-prompts": load_dataset_v4,
|
||||
"rubend18/ChatGPT-Jailbreak-Prompts": load_dataset_v3,
|
||||
"Lemhf14/EasyJailbreak_Datasets": load_dataset_v5,
|
||||
"markush1/LLM-Jailbreak-Classifier": load_dataset_v6,
|
||||
"JailbreakV-28K/JailBreakV-28k": load_dataset_v7,
|
||||
"ShawnMenz/jailbreak_sft_rm_ds": load_dataset_v8,
|
||||
"verazuo/jailbreak_llms/2023_05_07": lambda: load_generic_csv(
|
||||
url="https://raw.githubusercontent.com/verazuo/jailbreak_llms/main/data/prompts/jailbreak_prompts_2023_05_07.csv",
|
||||
name="verazuo/jailbreak_llms/2023_05_07",
|
||||
column="prompt",
|
||||
predicator=lambda x: bool(x["jailbreak"]),
|
||||
),
|
||||
"verazuo/jailbreak_llms/2023_12_25.csv": lambda: load_generic_csv(
|
||||
url="https://raw.githubusercontent.com/verazuo/jailbreak_llms/main/data/prompts/jailbreak_prompts_2023_12_25.csv.csv",
|
||||
name="verazuo/jailbreak_llms/2023_12_25.csv",
|
||||
column="prompt",
|
||||
predicator=lambda x: bool(x["jailbreak"]),
|
||||
),
|
||||
"Custom CSV": load_local_csv,
|
||||
}
|
||||
dataset_map.update(dataset_map_generics)
|
||||
# Determine which column to use as the prompt source
|
||||
prompt_col = None
|
||||
if mappings and "prompt" in mappings:
|
||||
prompt_col = mappings["prompt"]
|
||||
elif "prompt" in transformed_df.columns:
|
||||
prompt_col = "prompt"
|
||||
else:
|
||||
# Try to find a suitable text column
|
||||
text_columns = [
|
||||
col
|
||||
for col in transformed_df.columns
|
||||
if any(
|
||||
keyword in col.lower()
|
||||
for keyword in ["prompt", "text", "query", "question"]
|
||||
)
|
||||
]
|
||||
if text_columns:
|
||||
prompt_col = text_columns[0]
|
||||
logger.info(f"Using column '{prompt_col}' as prompt source")
|
||||
else:
|
||||
logger.error(f"No suitable prompt column found in dataset {name}")
|
||||
return create_probe_dataset(name, [], metadata)
|
||||
|
||||
group = []
|
||||
for dataset_name in dataset_names:
|
||||
if dataset_name in dataset_map:
|
||||
logger.info(f"Loading {dataset_name}")
|
||||
try:
|
||||
group.append(dataset_map[dataset_name]())
|
||||
except Exception as e:
|
||||
logger.error(f"Error loading {dataset_name}: {e}")
|
||||
|
||||
dynamic_datasets = {
|
||||
"AgenticBackend": lambda opts: dataset_from_iterator(
|
||||
"AgenticBackend",
|
||||
fine_tuned.Module(group, tools_inbox=tools_inbox, opts=opts).apply(),
|
||||
lazy=True,
|
||||
),
|
||||
"Steganography": lambda opts: Stenography(group),
|
||||
"llm-adaptive-attacks": lambda opts: dataset_from_iterator(
|
||||
"llm-adaptive-attacks",
|
||||
adaptive_attacks.Module(group, tools_inbox=tools_inbox, opts=opts).apply(),
|
||||
),
|
||||
"Garak": lambda opts: dataset_from_iterator(
|
||||
"Garak",
|
||||
garak_tool.Module(group, tools_inbox=tools_inbox, opts=opts).apply(),
|
||||
lazy=True,
|
||||
),
|
||||
"Reinforcement Learning Optimization": lambda opts: dataset_from_iterator(
|
||||
"Reinforcement Learning Optimization",
|
||||
rl_model.Module(group, tools_inbox=tools_inbox, opts=opts).apply(),
|
||||
lazy=True,
|
||||
),
|
||||
"InspectAI": lambda opts: dataset_from_iterator(
|
||||
"InspectAI",
|
||||
inspect_ai_tool.Module(group, tools_inbox=tools_inbox).apply(),
|
||||
lazy=True,
|
||||
),
|
||||
"GPT fuzzer": lambda opts: [],
|
||||
}
|
||||
|
||||
dynamic_groups = []
|
||||
options = options or [{} for _ in dataset_names]
|
||||
for dataset_name, opts in zip(dataset_names, options):
|
||||
if dataset_name in dynamic_datasets:
|
||||
logger.info(f"Loading {dataset_name}")
|
||||
|
||||
ds = dynamic_datasets[dataset_name](opts)
|
||||
|
||||
for g in ds:
|
||||
dynamic_groups.append(g)
|
||||
return group + dynamic_groups
|
||||
# Extract prompts and filter out empty ones
|
||||
prompts = [
|
||||
p
|
||||
for p in transformed_df[prompt_col].tolist()
|
||||
if p and isinstance(p, (str, int, float))
|
||||
]
|
||||
return create_probe_dataset(name, prompts, metadata)
|
||||
except Exception as e:
|
||||
logger.error(f"Error loading dataset {name}: {e}")
|
||||
return create_probe_dataset(name, [], {"error": str(e)})
|
||||
|
||||
|
||||
class Stenography:
|
||||
fn_library = {
|
||||
# Dataset-specific configurations
|
||||
DATASET_CONFIGS = {
|
||||
"ShawnMenz/DAN_jailbreak": {
|
||||
"mappings": {"prompt": "prompt"},
|
||||
"filter_fn": lambda row: row["jailbreak"],
|
||||
},
|
||||
"deepset/prompt-injections": {
|
||||
"mappings": {"prompt": "text"},
|
||||
"filter_fn": lambda row: row["label"],
|
||||
},
|
||||
"notrichardren/refuse-to-answer-prompts": {
|
||||
"mappings": {"prompt": "claim"},
|
||||
"filter_fn": lambda row: row["label"],
|
||||
},
|
||||
"rubend18/ChatGPT-Jailbreak-Prompts": {
|
||||
"mappings": {"prompt": "Prompt"},
|
||||
"filter_fn": lambda row: row["label"],
|
||||
},
|
||||
"markush1/LLM-Jailbreak-Classifier": {
|
||||
"mappings": {"prompt": "prompt"},
|
||||
"filter_fn": lambda row: row["classification"] == "jailbreak",
|
||||
},
|
||||
"ShawnMenz/jailbreak_sft_rm_ds": {
|
||||
"url": "hf://datasets/ShawnMenz/jailbreak_sft_rm_ds/jailbreak_sft_rm_ds.csv",
|
||||
"mappings": {"prompt": "prompt"},
|
||||
"filter_fn": lambda row: row["jailbreak"] == "jailbreak",
|
||||
},
|
||||
"verazuo/jailbreak_llms/2023_05_07": {
|
||||
"url": "https://raw.githubusercontent.com/verazuo/jailbreak_llms/main/data/prompts/jailbreak_prompts_2023_05_07.csv",
|
||||
"mappings": {"prompt": "prompt"},
|
||||
"filter_fn": lambda row: bool(row["jailbreak"]),
|
||||
},
|
||||
"verazuo/jailbreak_llms/2023_12_25": {
|
||||
"url": "https://raw.githubusercontent.com/verazuo/jailbreak_llms/main/data/prompts/jailbreak_prompts_2023_12_25.csv",
|
||||
"mappings": {"prompt": "prompt"},
|
||||
"filter_fn": lambda row: bool(row["jailbreak"]),
|
||||
},
|
||||
}
|
||||
|
||||
# Additional generic dataset configurations
|
||||
DATASET_CONFIGS_GENERICS = {
|
||||
"simonycl/aya-23-8B_advbench_jailbreak": {"mappings": {"prompt": "prompt"}},
|
||||
"acmc/jailbreaks_dataset_with_perplexity_bigcode_starcoder2-3b_bigcode_starcoder2-7b": {},
|
||||
"karanxa/dolphin-jailbreak-finetuning-dataset": {"mappings": {"prompt": "text"}},
|
||||
"karanxa/llama-2-jailbreak-dataset": {"mappings": {"prompt": "text"}},
|
||||
"karanxa/llama2-uncensored-jailbreak-dataset-finetuning": {
|
||||
"mappings": {"prompt": "text"}
|
||||
},
|
||||
"liuyanchen1015/Llama-3.2-1B_jailbreak_responses": {
|
||||
"mappings": {"prompt": "jailbreak_prompt_text"}
|
||||
},
|
||||
"liuyanchen1015/Llama-3.2-1B-Instruct_jailbreak_responses": {
|
||||
"mappings": {"prompt": "jailbreak_prompt_text"}
|
||||
},
|
||||
"liuyanchen1015/Llama-3.2-1B-Instruct_jailbreak_responses_with_judgment": {
|
||||
"mappings": {"prompt": "jailbreak_prompt_text"}
|
||||
},
|
||||
"jackhhao/jailbreak-classification": {"mappings": {"prompt": "prompt"}},
|
||||
"walledai/JailbreakBench": {"mappings": {"prompt": "prompt"}},
|
||||
"walledai/JailbreakHub": {"mappings": {"prompt": "prompt"}},
|
||||
"Granther/evil-jailbreak": {"mappings": {"prompt": "text"}},
|
||||
"sevdeawesome/jailbreak_success": {"mappings": {"prompt": "jailbreak_prompt_text"}},
|
||||
"IDA-SERICS/Disaster-tweet-jailbreaking": {"mappings": {"prompt": "prompt_attack"}},
|
||||
"GeorgeDaDude/Jailbreak_Complete_DS_labeled": {"mappings": {"prompt": "question"}},
|
||||
"dayone3nder/jailbreak_prompt_JBB_sft_trainset": {"mappings": {"prompt": "prompt"}},
|
||||
"dayone3nder/general_safe_mix_jailbreak_prompt_JBB_trainset": {
|
||||
"mappings": {"prompt": "prompt"}
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
# Dataset factory
|
||||
def create_dataset_loader(name: str, config: dict[str, Any]) -> DatasetLoader:
|
||||
"""Create a dataset loader from configuration."""
|
||||
return partial(
|
||||
load_dataset_generic,
|
||||
name=name,
|
||||
mappings=config.get("mappings"),
|
||||
filter_fn=config.get("filter_fn"),
|
||||
url=config.get("url"),
|
||||
metadata={"source": name, "config": str(config)},
|
||||
)
|
||||
|
||||
|
||||
# Specialized loaders
|
||||
@cache_to_disk()
|
||||
def load_multi_dataset(name: str, sub_datasets: list[str]) -> ProbeDataset:
|
||||
"""Load and combine multiple sub-datasets."""
|
||||
prompts = []
|
||||
for sub in sub_datasets:
|
||||
try:
|
||||
dataset = load_dataset(name, sub)
|
||||
if "query" in dataset["train"].features:
|
||||
prompts.extend(dataset["train"]["query"])
|
||||
else:
|
||||
logger.warning(f"No 'query' column in {name}/{sub}")
|
||||
except Exception as e:
|
||||
logger.error(f"Error loading {name}/{sub}: {e}")
|
||||
|
||||
return create_probe_dataset(
|
||||
f"{name}_combined", prompts, {"source": name, "sub_datasets": sub_datasets}
|
||||
)
|
||||
|
||||
|
||||
@cache_to_disk()
|
||||
def load_jailbreak_v28k() -> ProbeDataset:
|
||||
"""Load JailBreakV-28K dataset."""
|
||||
try:
|
||||
df = pd.read_csv(
|
||||
"hf://datasets/JailbreakV-28K/JailBreakV-28k/JailBreakV_28K.csv"
|
||||
)
|
||||
prompts = df["jailbreak_query"].tolist()
|
||||
return create_probe_dataset(
|
||||
"JailbreakV-28K/JailBreakV-28k",
|
||||
prompts,
|
||||
{"source": "JailbreakV-28K/JailBreakV-28k"},
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Error loading JailbreakV-28K: {e}")
|
||||
return create_probe_dataset("JailbreakV-28K/JailBreakV-28k", [])
|
||||
|
||||
|
||||
@cache_to_disk()
|
||||
def load_local_csv() -> ProbeDataset:
|
||||
"""Load prompts from local CSV files."""
|
||||
csv_files = [f for f in os.listdir(".") if f.endswith(".csv")]
|
||||
logger.info(f"Found {len(csv_files)} CSV files: {csv_files}")
|
||||
|
||||
prompts = []
|
||||
for file in csv_files:
|
||||
try:
|
||||
df = pd.read_csv(file)
|
||||
if "prompt" in df.columns:
|
||||
prompts.extend(df["prompt"].tolist())
|
||||
else:
|
||||
logger.warning(f"File {file} lacks a suitable prompt column")
|
||||
except Exception as e:
|
||||
logger.error(f"Error reading {file}: {e}")
|
||||
|
||||
return create_probe_dataset("Local CSV", prompts, {"src": str(csv_files)})
|
||||
|
||||
|
||||
@cache_to_disk(1)
|
||||
def load_csv(file: str) -> ProbeDataset:
|
||||
"""Load prompts from local CSV files."""
|
||||
prompts = []
|
||||
try:
|
||||
df = pd.read_csv(file)
|
||||
prompts = df["prompt"].tolist()
|
||||
if "prompt" in df.columns:
|
||||
prompts.extend(df["prompt"].tolist())
|
||||
else:
|
||||
logger.warning(f"File {file} lacks a suitable prompt column")
|
||||
except Exception as e:
|
||||
logger.error(f"Error reading {file}: {e}")
|
||||
return create_probe_dataset(f"fs://{file}", prompts, {"src": str(file)})
|
||||
|
||||
|
||||
@cache_to_disk(1)
|
||||
def load_local_csv_files() -> list[ProbeDataset]:
|
||||
"""Load prompts from local CSV files and return a list of ProbeDataset objects."""
|
||||
csv_files = [f for f in os.listdir(".") if f.endswith(".csv")]
|
||||
logger.info(f"Found {len(csv_files)} CSV files: {csv_files}")
|
||||
|
||||
datasets = []
|
||||
|
||||
for file in csv_files:
|
||||
try:
|
||||
df = pd.read_csv(file)
|
||||
if "prompt" in df.columns:
|
||||
prompts = df["prompt"].tolist()
|
||||
datasets.append(create_probe_dataset(file, prompts, {"src": file}))
|
||||
else:
|
||||
logger.warning(f"File {file} lacks a suitable prompt column")
|
||||
except Exception as e:
|
||||
logger.error(f"Error reading {file}: {e}")
|
||||
|
||||
return datasets
|
||||
|
||||
|
||||
# Stenography transformer
|
||||
class StenographyTransformer:
|
||||
"""Apply stenography transformations to datasets."""
|
||||
|
||||
TRANSFORMATIONS = {
|
||||
"rot5": stenography_fn.rot5,
|
||||
"rot13": stenography_fn.rot13,
|
||||
"base64": stenography_fn.base64_encode,
|
||||
@@ -306,151 +320,164 @@ class Stenography:
|
||||
"zigzag_obfuscation": stenography_fn.zigzag_obfuscation,
|
||||
}
|
||||
|
||||
def __init__(self, prompt_groups: [ProbeDataset]):
|
||||
self.prompt_groups = prompt_groups
|
||||
def __init__(self, datasets: list[ProbeDataset]):
|
||||
self.datasets = datasets
|
||||
|
||||
def __iter__(self):
|
||||
return self.apply()
|
||||
def transform(self) -> Iterator[ProbeDataset]:
|
||||
"""Generate transformed datasets."""
|
||||
if not self.datasets:
|
||||
logger.warning("No datasets provided to StenographyTransformer")
|
||||
return
|
||||
|
||||
def apply(self):
|
||||
for prompt_group in self.prompt_groups:
|
||||
size = len(prompt_group.prompts)
|
||||
for name, fn in self.fn_library.items():
|
||||
logger.info(f"Applying {name} to {prompt_group.dataset_name}")
|
||||
selected_prompts = random.sample(prompt_group.prompts, int(size * 0.25))
|
||||
prompts = [
|
||||
f"Follow the instructions in {name}: {fn(p)}"
|
||||
for p in selected_prompts
|
||||
]
|
||||
yield ProbeDataset(
|
||||
dataset_name=f"stenography.{name}({prompt_group.dataset_name})",
|
||||
metadata={},
|
||||
prompts=prompts,
|
||||
tokens=count_words_in_list(prompts),
|
||||
approx_cost=0.0,
|
||||
for dataset in self.datasets:
|
||||
if not dataset.prompts:
|
||||
logger.warning(
|
||||
f"Dataset {dataset.dataset_name} has no prompts to transform"
|
||||
)
|
||||
continue
|
||||
|
||||
size = len(dataset.prompts)
|
||||
sample_size = max(1, int(size * 0.25)) # At least 1 sample
|
||||
|
||||
for name, fn in self.TRANSFORMATIONS.items():
|
||||
logger.info(f"Applying {name} to {dataset.dataset_name}")
|
||||
sampled = random.sample(dataset.prompts, min(sample_size, size))
|
||||
try:
|
||||
transformed = [
|
||||
f"Follow the instructions in {name}: {fn(str(p))}"
|
||||
for p in sampled
|
||||
]
|
||||
transformed_dataset = create_probe_dataset(
|
||||
f"stenography.{name}({dataset.dataset_name})",
|
||||
transformed,
|
||||
{"source": dataset.dataset_name, "transformation": name},
|
||||
)
|
||||
yield transformed_dataset
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"Error applying {name} to {dataset.dataset_name}: {e}"
|
||||
)
|
||||
|
||||
|
||||
@lru_cache
|
||||
def load_local_csv() -> ProbeDataset:
|
||||
csv_files = [f for f in os.listdir(".") if f.endswith(".csv")]
|
||||
logger.info(f"Found {len(csv_files)} CSV files")
|
||||
logger.info(f"CSV files: {csv_files}")
|
||||
prompt_list = []
|
||||
def dataset_from_iterator(
|
||||
name: str, iterator: Iterator[str], lazy: bool = False
|
||||
) -> list[ProbeDataset]:
|
||||
"""Convert an iterator into a list of ProbeDataset objects."""
|
||||
try:
|
||||
prompts = list(iterator) if not lazy else iterator
|
||||
tokens = sum(len(str(s).split()) for s in prompts) if not lazy else 0
|
||||
dataset = ProbeDataset(
|
||||
dataset_name=name,
|
||||
metadata={"source": name, "lazy": lazy},
|
||||
prompts=prompts,
|
||||
tokens=tokens,
|
||||
approx_cost=0.0,
|
||||
lazy=lazy,
|
||||
)
|
||||
return [dataset]
|
||||
except Exception as e:
|
||||
logger.error(f"Error creating dataset from iterator {name}: {e}")
|
||||
return [create_probe_dataset(name, [], {"error": str(e)})]
|
||||
|
||||
for file in csv_files:
|
||||
try:
|
||||
df = pd.read_csv(file)
|
||||
except Exception as e:
|
||||
logger.error(f"Error reading {file}: {e}")
|
||||
|
||||
# Main dataset preparation
|
||||
def prepare_prompts(
|
||||
dataset_names: list[str],
|
||||
budget: float,
|
||||
tools_inbox=None,
|
||||
options: list[dict] = None,
|
||||
) -> list[ProbeDataset]:
|
||||
"""Prepare datasets based on names and options."""
|
||||
# Base dataset loaders
|
||||
logger.info(f"Preparing datasets: {dataset_names}")
|
||||
dataset_loaders = {
|
||||
**{k: create_dataset_loader(k, v) for k, v in DATASET_CONFIGS.items()},
|
||||
**{k: create_dataset_loader(k, v) for k, v in DATASET_CONFIGS_GENERICS.items()},
|
||||
"Lemhf14/EasyJailbreak_Datasets": partial(
|
||||
load_multi_dataset,
|
||||
name="Lemhf14/EasyJailbreak_Datasets",
|
||||
sub_datasets=[
|
||||
"AdvBench",
|
||||
"ForbiddenQuestion",
|
||||
"MJP",
|
||||
"MaliciousInstruct",
|
||||
"QuestionList",
|
||||
],
|
||||
),
|
||||
"JailbreakV-28K/JailBreakV-28k": load_jailbreak_v28k,
|
||||
"Local CSV": load_local_csv,
|
||||
"Custom CSV": load_local_csv,
|
||||
}
|
||||
|
||||
# Dynamic dataset loaders
|
||||
dynamic_loaders = {
|
||||
"AgenticBackend": lambda opts: dataset_from_iterator(
|
||||
"AgenticBackend",
|
||||
fine_tuned.Module(
|
||||
opts["datasets"], tools_inbox=tools_inbox, opts=opts
|
||||
).apply(),
|
||||
lazy=True,
|
||||
),
|
||||
"Steganography": lambda opts: list(
|
||||
StenographyTransformer(opts["datasets"]).transform()
|
||||
),
|
||||
"llm-adaptive-attacks": lambda opts: dataset_from_iterator(
|
||||
"llm-adaptive-attacks",
|
||||
adaptive_attacks.Module(
|
||||
opts["datasets"], tools_inbox=tools_inbox, opts=opts
|
||||
).apply(),
|
||||
),
|
||||
"Garak": lambda opts: dataset_from_iterator(
|
||||
"Garak",
|
||||
garak_tool.Module(
|
||||
opts["datasets"], tools_inbox=tools_inbox, opts=opts
|
||||
).apply(),
|
||||
lazy=True,
|
||||
),
|
||||
"Reinforcement Learning Optimization": lambda opts: dataset_from_iterator(
|
||||
"Reinforcement Learning Optimization",
|
||||
rl_model.Module(
|
||||
opts["datasets"], tools_inbox=tools_inbox, opts=opts
|
||||
).apply(),
|
||||
lazy=True,
|
||||
),
|
||||
"InspectAI": lambda opts: dataset_from_iterator(
|
||||
"InspectAI",
|
||||
inspect_ai_tool.Module([], tools_inbox=tools_inbox).apply(),
|
||||
lazy=True,
|
||||
),
|
||||
"GPT fuzzer": lambda opts: [],
|
||||
}
|
||||
|
||||
datasets = []
|
||||
options = options or [dict(datasets=datasets) for _ in dataset_names]
|
||||
|
||||
# Load base datasets
|
||||
for name, opts in zip(dataset_names, options):
|
||||
if name not in dataset_loaders:
|
||||
continue
|
||||
# Check if 'prompt' column exists
|
||||
if "prompt" in df.columns:
|
||||
prompt_list.extend(df["prompt"].tolist())
|
||||
else:
|
||||
logger.warning(f"File {file} does not contain a 'prompt' column")
|
||||
return ProbeDataset(
|
||||
dataset_name="Local CSV",
|
||||
metadata={"src": str(csv_files)},
|
||||
prompts=prompt_list,
|
||||
tokens=count_words_in_list(prompt_list),
|
||||
approx_cost=0.0,
|
||||
)
|
||||
try:
|
||||
datasets.append(dataset_loaders[name]())
|
||||
except Exception as e:
|
||||
logger.error(f"Error loading {name}: {e}")
|
||||
|
||||
# Load dynamic datasets and apply transformations
|
||||
for name, opts in zip(dataset_names, options):
|
||||
if name not in dynamic_loaders:
|
||||
continue
|
||||
logger.info(f"Loading dynamic dataset {name} {opts}")
|
||||
opts["datasets"] = datasets
|
||||
try:
|
||||
dynamic_result = dynamic_loaders[name](opts)
|
||||
datasets.extend(dynamic_result)
|
||||
except Exception as e:
|
||||
logger.exception(f"Error loading dynamic {name}: {e}")
|
||||
|
||||
def dataset_from_iterator(name: str, iterator, lazy=False) -> list:
|
||||
"""Convert an iterator into a list of prompts and create a ProbeDataset
|
||||
object.
|
||||
# Load csv datasets and apply transformations
|
||||
for name, opts in zip(dataset_names, options):
|
||||
if not name.endswith(".csv"):
|
||||
continue
|
||||
logger.info(f"Loading csv dataset {name} {opts}")
|
||||
datasets.append(load_csv(name))
|
||||
|
||||
Args:
|
||||
name (str): The name of the dataset.
|
||||
iterator (iterator): An iterator that generates prompts.
|
||||
|
||||
Returns:
|
||||
list: A list containing a single ProbeDataset object.
|
||||
"""
|
||||
prompts = list(iterator) if not lazy else iterator
|
||||
tokens = count_words_in_list(prompts) if not lazy else 0
|
||||
dataset = ProbeDataset(
|
||||
dataset_name=name,
|
||||
metadata={},
|
||||
prompts=prompts,
|
||||
tokens=tokens,
|
||||
approx_cost=0.0,
|
||||
lazy=lazy,
|
||||
)
|
||||
return [dataset]
|
||||
|
||||
|
||||
# TODO: refactor this abstraction
|
||||
|
||||
dataset_map_generics = {
|
||||
"simonycl/aya-23-8B_advbench_jailbreak": lambda: load_dataset_general(
|
||||
dataset_name="simonycl/aya-23-8B_advbench_jailbreak",
|
||||
column_mappings={"prompt": "prompt"},
|
||||
),
|
||||
"acmc/jailbreaks_dataset_with_perplexity_bigcode_starcoder2-3b_bigcode_starcoder2-7b": lambda: load_dataset_general(
|
||||
dataset_name="acmc/jailbreaks_dataset_with_perplexity_bigcode_starcoder2-3b_bigcode_starcoder2-7b"
|
||||
),
|
||||
"karanxa/dolphin-jailbreak-finetuning-dataset": lambda: load_dataset_general(
|
||||
dataset_name="karanxa/dolphin-jailbreak-finetuning-dataset",
|
||||
column_mappings={"prompt": "text"},
|
||||
),
|
||||
"karanxa/llama-2-jailbreak-dataset": lambda: load_dataset_general(
|
||||
dataset_name="karanxa/llama-2-jailbreak-dataset",
|
||||
column_mappings={"prompt": "text"},
|
||||
),
|
||||
"karanxa/llama2-uncensored-jailbreak-dataset-finetuning": lambda: load_dataset_general(
|
||||
dataset_name="karanxa/llama2-uncensored-jailbreak-dataset-finetuning",
|
||||
column_mappings={"prompt": "text"},
|
||||
),
|
||||
"liuyanchen1015/Llama-3.2-1B_jailbreak_responses": lambda: load_dataset_general(
|
||||
dataset_name="liuyanchen1015/Llama-3.2-1B_jailbreak_responses",
|
||||
column_mappings={"prompt": "jailbreak_prompt_text"},
|
||||
),
|
||||
"liuyanchen1015/Llama-3.2-1B-Instruct_jailbreak_responses": lambda: load_dataset_general(
|
||||
dataset_name="liuyanchen1015/Llama-3.2-1B-Instruct_jailbreak_responses",
|
||||
column_mappings={"prompt": "jailbreak_prompt_text"},
|
||||
),
|
||||
"liuyanchen1015/Llama-3.2-1B-Instruct_jailbreak_responses_with_judgment": lambda: load_dataset_general(
|
||||
dataset_name="liuyanchen1015/Llama-3.2-1B-Instruct_jailbreak_responses_with_judgment",
|
||||
column_mappings={"prompt": "jailbreak_prompt_text"},
|
||||
),
|
||||
"jackhhao/jailbreak-classification": lambda: load_dataset_general(
|
||||
dataset_name="jackhhao/jailbreak-classification",
|
||||
column_mappings={"prompt": "prompt"},
|
||||
),
|
||||
"markush1/LLM-Jailbreak-Classifier": lambda: load_dataset_general(
|
||||
dataset_name="markush1/LLM-Jailbreak-Classifier",
|
||||
column_mappings={"prompt": "prompt"},
|
||||
),
|
||||
"walledai/JailbreakBench": lambda: load_dataset_general(
|
||||
dataset_name="walledai/JailbreakBench", column_mappings={"prompt": "prompt"}
|
||||
),
|
||||
"walledai/JailbreakHub": lambda: load_dataset_general(
|
||||
dataset_name="walledai/JailbreakHub", column_mappings={"prompt": "prompt"}
|
||||
),
|
||||
"Granther/evil-jailbreak": lambda: load_dataset_general(
|
||||
dataset_name="Granther/evil-jailbreak", column_mappings={"prompt": "text"}
|
||||
),
|
||||
"sevdeawesome/jailbreak_success": lambda: load_dataset_general(
|
||||
dataset_name="sevdeawesome/jailbreak_success",
|
||||
column_mappings={"prompt": "jailbreak_prompt_text"},
|
||||
),
|
||||
"IDA-SERICS/Disaster-tweet-jailbreaking": lambda: load_dataset_general(
|
||||
dataset_name="IDA-SERICS/Disaster-tweet-jailbreaking",
|
||||
column_mappings={"prompt": "prompt_attack"},
|
||||
),
|
||||
"GeorgeDaDude/Jailbreak_Complete_DS_labeled": lambda: load_dataset_general(
|
||||
dataset_name="GeorgeDaDude/Jailbreak_Complete_DS_labeled",
|
||||
column_mappings={"prompt": "question"},
|
||||
),
|
||||
"dayone3nder/jailbreak_prompt_JBB_sft_trainset": lambda: load_dataset_general(
|
||||
dataset_name="dayone3nder/jailbreak_prompt_JBB_sft_trainset",
|
||||
column_mappings={"prompt": "prompt"},
|
||||
),
|
||||
"dayone3nder/general_safe_mix_jailbreak_prompt_JBB_trainset": lambda: load_dataset_general(
|
||||
dataset_name="dayone3nder/general_safe_mix_jailbreak_prompt_JBB_trainset",
|
||||
column_mappings={"prompt": "prompt"},
|
||||
),
|
||||
}
|
||||
return datasets
|
||||
|
||||
@@ -3,7 +3,8 @@ import io
|
||||
|
||||
import httpx
|
||||
import pandas as pd
|
||||
from loguru import logger
|
||||
|
||||
from agentic_security.logutils import logger
|
||||
|
||||
url = "https://raw.githubusercontent.com/tml-epfl/llm-adaptive-attacks/main/harmful_behaviors/harmful_behaviors_pair.csv"
|
||||
|
||||
|
||||
@@ -3,7 +3,8 @@ import os
|
||||
import uuid as U
|
||||
|
||||
import httpx
|
||||
from loguru import logger
|
||||
|
||||
from agentic_security.logutils import logger
|
||||
|
||||
AUTH_TOKEN: str = os.getenv("AS_TOKEN", "gh0-5f4a8ed2-37c6-4bd7-a0cf-7070eae8115b")
|
||||
|
||||
@@ -65,7 +66,7 @@ class Module:
|
||||
return {}
|
||||
|
||||
async def fetch_prompts(self) -> list[str]:
|
||||
api_url = "https://edge.metaheuristic.co/infer"
|
||||
api_url = "https://mcp.metaheuristic.co/infer"
|
||||
headers = {
|
||||
"Authorization": f"Bearer {AUTH_TOKEN}",
|
||||
"Content-Type": "application/json",
|
||||
|
||||
@@ -4,7 +4,7 @@ import json
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
from loguru import logger
|
||||
from agentic_security.logutils import logger
|
||||
|
||||
# TODO: add probes modules
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import asyncio
|
||||
import importlib.util
|
||||
import os
|
||||
|
||||
from loguru import logger
|
||||
from agentic_security.logutils import logger
|
||||
|
||||
inspect_ai_task = (
|
||||
__file__.replace("inspect_ai_tool.py", "inspect_ai_task.py")
|
||||
|
||||
@@ -8,7 +8,8 @@ from typing import Deque
|
||||
|
||||
import numpy as np
|
||||
import requests
|
||||
from loguru import logger
|
||||
|
||||
from agentic_security.logutils import logger
|
||||
|
||||
AUTH_TOKEN: str = os.getenv("AS_TOKEN", "gh0-5f4a8ed2-37c6-4bd7-a0cf-7070eae8115b")
|
||||
|
||||
@@ -215,7 +216,7 @@ class Module:
|
||||
self.run_id = U.uuid4().hex
|
||||
self.batch_size = self.opts.get("batch_size", 500)
|
||||
self.rl_model = CloudRLPromptSelector(
|
||||
prompt_groups, "https://edge.metaheuristic.co", run_id=self.run_id
|
||||
prompt_groups, "https://mcp.metaheuristic.co", run_id=self.run_id
|
||||
)
|
||||
|
||||
async def apply(self):
|
||||
|
||||
@@ -36,7 +36,7 @@ def mock_requests() -> Mock:
|
||||
def mock_rl_selector() -> Mock:
|
||||
return CloudRLPromptSelector(
|
||||
dataset_prompts,
|
||||
api_url="https://edge.metaheuristic.co",
|
||||
api_url="https://mcp.metaheuristic.co",
|
||||
)
|
||||
|
||||
|
||||
@@ -94,7 +94,7 @@ class TestCloudRLPromptSelector:
|
||||
def test_select_next_prompt_success_service(self, dataset_prompts):
|
||||
selector = CloudRLPromptSelector(
|
||||
dataset_prompts,
|
||||
api_url="https://edge.metaheuristic.co",
|
||||
api_url="https://mcp.metaheuristic.co",
|
||||
)
|
||||
next_prompt = selector.select_next_prompt(
|
||||
"How does RL work?", passed_guard=True
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
from cache_to_disk import cache_to_disk
|
||||
from cache_to_disk import cache_to_disk # noqa
|
||||
|
||||
|
||||
# TODO: refactor this class to use from .data
|
||||
@@ -22,7 +22,7 @@ class ProbeDataset:
|
||||
}
|
||||
|
||||
|
||||
@cache_to_disk()
|
||||
# @cache_to_disk(n_days_to_cache=1)
|
||||
def load_dataset_generic(name, getter=lambda x: x["train"]["prompt"]):
|
||||
from datasets import load_dataset
|
||||
|
||||
|
||||
@@ -7,9 +7,61 @@ import pandas as pd
|
||||
from matplotlib.cm import ScalarMappable
|
||||
from matplotlib.colors import LinearSegmentedColormap, Normalize
|
||||
|
||||
from agentic_security.logutils import logger
|
||||
|
||||
def plot_security_report(table):
|
||||
from .primitives import Table
|
||||
|
||||
|
||||
def plot_security_report(table: Table) -> io.BytesIO:
|
||||
"""
|
||||
Generates a polar plot representing the security report based on the given data.
|
||||
|
||||
Args:
|
||||
table (Table): The input data table containing security metrics.
|
||||
|
||||
Returns:
|
||||
io.BytesIO: A buffer containing the generated plot image in PNG format.
|
||||
Returns an empty buffer in case of an error.
|
||||
"""
|
||||
try:
|
||||
return _plot_security_report(table=table)
|
||||
except (TypeError, ValueError, OverflowError, IndexError, Exception) as e:
|
||||
logger.error(f"Error in generating the security report: {e} {table}")
|
||||
return io.BytesIO()
|
||||
|
||||
|
||||
def generate_identifiers(data: pd.DataFrame) -> list[str]:
|
||||
"""
|
||||
Generates unique identifiers for the given dataset.
|
||||
|
||||
Args:
|
||||
data (pd.DataFrame): A pandas DataFrame containing security-related data.
|
||||
|
||||
Returns:
|
||||
list[str]: A list of generated identifiers. Returns a list with an empty string in case of an error.
|
||||
"""
|
||||
return _generate_identifiers(data=data)
|
||||
|
||||
|
||||
def _plot_security_report(table: Table) -> io.BytesIO:
|
||||
"""
|
||||
Generates a polar plot-based security report visualizing the failure rates for different modules.
|
||||
|
||||
This function processes the input data, sorts it by failure rate, and generates a polar plot
|
||||
where each bar represents the failure rate for a specific module. The plot includes identifiers,
|
||||
color-coding based on token count, failure rate values on the bars, and a table listing the modules
|
||||
and their corresponding failure rates.
|
||||
|
||||
Args:
|
||||
table (Table): A table-like structure (e.g., pandas DataFrame) containing security report data
|
||||
with columns for failure rate, tokens, and modules.
|
||||
|
||||
Returns:
|
||||
io.BytesIO: A buffer containing the generated plot image in PNG format.
|
||||
"""
|
||||
# Data preprocessing
|
||||
logger.info("Data preprocessing started.")
|
||||
|
||||
data = pd.DataFrame(table)
|
||||
|
||||
# Sort by failure rate and reset index
|
||||
@@ -20,10 +72,10 @@ def plot_security_report(table):
|
||||
fig, ax = plt.subplots(figsize=(12, 10), subplot_kw={"projection": "polar"})
|
||||
fig.set_facecolor("#f0f0f0")
|
||||
ax.set_facecolor("#f0f0f0")
|
||||
logger.info("Plot setup complete.")
|
||||
|
||||
# Styling parameters
|
||||
colors = ["#6C5B7B", "#C06C84", "#F67280", "#F8B195"][::-1] # Pastel palette
|
||||
# colors = ["#440154", "#3b528b", "#21908c", "#5dc863"] # Viridis-inspired palette
|
||||
cmap = LinearSegmentedColormap.from_list("custom", colors, N=256)
|
||||
norm = Normalize(vmin=data["tokens"].min(), vmax=data["tokens"].max())
|
||||
|
||||
@@ -74,7 +126,10 @@ def plot_security_report(table):
|
||||
|
||||
# Title and caption
|
||||
fig.suptitle(
|
||||
"Security Report for Different Modules", fontsize=16, fontweight="bold", y=1.02
|
||||
"Security Report for Different Modules",
|
||||
fontsize=16,
|
||||
fontweight="bold",
|
||||
y=1.02,
|
||||
)
|
||||
caption = "Report generated by https://github.com/msoedov/agentic_security"
|
||||
fig.text(
|
||||
@@ -112,17 +167,12 @@ def plot_security_report(table):
|
||||
data["identifier"], data["failureRate"], data["module"]
|
||||
)
|
||||
]
|
||||
table = ax.table(
|
||||
cellText=table_data,
|
||||
loc="right",
|
||||
cellLoc="left",
|
||||
)
|
||||
table = ax.table(cellText=table_data, loc="right", cellLoc="left")
|
||||
table.auto_set_font_size(False)
|
||||
table.set_fontsize(8)
|
||||
|
||||
# Adjust table style
|
||||
table.scale(1, 0.7)
|
||||
|
||||
for (row, col), cell in table.get_celld().items():
|
||||
cell.set_edgecolor("none")
|
||||
cell.set_facecolor("#f0f0f0" if row % 2 == 0 else "#e0e0e0")
|
||||
@@ -132,17 +182,33 @@ def plot_security_report(table):
|
||||
cell.set_text_props(fontweight="bold")
|
||||
|
||||
# Adjust layout and save
|
||||
|
||||
plt.tight_layout()
|
||||
buf = io.BytesIO()
|
||||
plt.savefig(buf, format="png", dpi=300, bbox_inches="tight")
|
||||
plt.close(fig)
|
||||
buf.seek(0)
|
||||
logger.info("Report successfully generated and saved to buffer.")
|
||||
return buf
|
||||
|
||||
|
||||
def generate_identifiers(data):
|
||||
def _generate_identifiers(data: pd.DataFrame) -> list[str]:
|
||||
"""
|
||||
Generates a list of unique identifiers for each row in the given DataFrame.
|
||||
|
||||
The identifiers are based on the English alphabet, with each identifier consisting
|
||||
of a letter followed by a number. The letter represents the "group" of identifiers
|
||||
(using a letter from A to Z) and the number is a counter within that group. For example:
|
||||
- A1, A2, ..., A26, B1, B2, ..., Z1, Z2, ...
|
||||
|
||||
Args:
|
||||
data (pd.DataFrame): The input DataFrame containing data for which identifiers
|
||||
are to be generated.
|
||||
|
||||
Returns:
|
||||
list[str]: A list of unique identifiers as strings, one for each row in the DataFrame.
|
||||
"""
|
||||
data_length = len(data)
|
||||
|
||||
alphabet = string.ascii_uppercase
|
||||
num_letters = len(alphabet)
|
||||
|
||||
|
||||
@@ -0,0 +1,397 @@
|
||||
_SPECS = [
|
||||
"""POST ${SELF_URL}/v1/self-probe
|
||||
Authorization: Bearer XXXXX
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"prompt": "<<PROMPT>>"
|
||||
}
|
||||
|
||||
""",
|
||||
"""POST https://api.openai.com/v1/chat/completions
|
||||
Authorization: Bearer $OPENAI_API_KEY
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"model": "gpt-3.5-turbo",
|
||||
"messages": [{"role": "user", "content": "<<PROMPT>>"}],
|
||||
"temperature": 0.7
|
||||
}
|
||||
""",
|
||||
"""
|
||||
POST https://api.deepseek.com/chat/completions
|
||||
Authorization: Bearer $DEEPSEEK_API_KEY
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"model": "deepseek-chat",
|
||||
"messages": [
|
||||
{"role": "system", "content": "You are a helpful assistant."},
|
||||
{"role": "user", "content": "<<PROMPT>>"}
|
||||
],
|
||||
"stream": false
|
||||
}
|
||||
""",
|
||||
"""POST https://api.replicate.com/v1/models/mistralai/mixtral-8x7b-instruct-v0.1/predictions
|
||||
Authorization: Bearer $APIKEY
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"input": {
|
||||
"top_k": 50,
|
||||
"top_p": 0.9,
|
||||
"prompt": "Write a bedtime story about neural networks I can read to my toddler",
|
||||
"temperature": 0.6,
|
||||
"max_new_tokens": 1024,
|
||||
"prompt_template": "<s>[INST] <<PROMPT>> [/INST] ",
|
||||
"presence_penalty": 0,
|
||||
"frequency_penalty": 0
|
||||
}
|
||||
}
|
||||
""",
|
||||
"""POST https://api.groq.com/v1/request_manager/text_completion
|
||||
Authorization: Bearer $APIKEY
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"model_id": "codellama-34b",
|
||||
"system_prompt": "You are helpful and concise coding assistant",
|
||||
"user_prompt": "<<PROMPT>>"
|
||||
}
|
||||
""",
|
||||
"""POST https://api.together.xyz/v1/chat/completions
|
||||
Authorization: Bearer $TOGETHER_API_KEY
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"model": "mistralai/Mixtral-8x7B-Instruct-v0.1",
|
||||
"messages": [
|
||||
{"role": "system", "content": "You are an expert travel guide"},
|
||||
{"role": "user", "content": "<<PROMPT>>"}
|
||||
]
|
||||
}
|
||||
""",
|
||||
"""POST ${SELF_URL}/v1/self-probe-image
|
||||
Authorization: Bearer XXXXX
|
||||
Content-Type: application/json
|
||||
|
||||
[
|
||||
{
|
||||
"role": "user",
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "What is in this image?",
|
||||
},
|
||||
{
|
||||
"type": "image_url",
|
||||
"image_url": {
|
||||
"url": f"data:image/jpeg;base64,{<<BASE64_IMAGE>>}"
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
]
|
||||
""",
|
||||
"""POST ${SELF_URL}/v1/self-probe-file
|
||||
Authorization: Bearer $API_KEY
|
||||
Content-Type: multipart/form-data
|
||||
|
||||
{
|
||||
"file": "@./sample_audio.m4a",
|
||||
"model": "whisper-large-v3"
|
||||
}
|
||||
""",
|
||||
"""POST https://api.gemini.com/v1/generate
|
||||
Authorization: Bearer $GEMINI_API_KEY
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"model": "gemini-latest",
|
||||
"prompt": "<<PROMPT>>",
|
||||
"temperature": 0.8,
|
||||
"max_tokens": 150,
|
||||
"top_p": 1.0,
|
||||
"frequency_penalty": 0,
|
||||
"presence_penalty": 0
|
||||
}
|
||||
""",
|
||||
"""POST https://api.anthropic.com/v1/complete
|
||||
Authorization: Bearer $ANTHROPIC_API_KEY
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"model": "claude-v1.3",
|
||||
"prompt": "<<PROMPT>>",
|
||||
"temperature": 0.7,
|
||||
"max_tokens_to_sample": 256,
|
||||
"stop_sequences": ["\n\nHuman:"]
|
||||
}
|
||||
""",
|
||||
"""POST https://api.cohere.ai/generate
|
||||
Authorization: Bearer $COHERE_API_KEY
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"model": "command-xlarge-nightly",
|
||||
"prompt": "<<PROMPT>>",
|
||||
"max_tokens": 300,
|
||||
"temperature": 0.75,
|
||||
"k": 0,
|
||||
"p": 0.75
|
||||
}
|
||||
""",
|
||||
"""POST https://<<RESOURCE_NAME>>.openai.azure.com/openai/deployments/<<DEPLOYMENT_NAME>>/completions?api-version=2023-06-01-preview
|
||||
Authorization: Bearer $AZURE_API_KEY
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"prompt": "<<PROMPT>>",
|
||||
"max_tokens": 150,
|
||||
"temperature": 0.7,
|
||||
"top_p": 0.9,
|
||||
"frequency_penalty": 0,
|
||||
"presence_penalty": 0
|
||||
}
|
||||
""",
|
||||
"""POST https://api.assemblyai.com/v2/transcript
|
||||
Authorization: Bearer $ASSEMBLY_API_KEY
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"audio_url": "<<AUDIO_FILE_URL>>"
|
||||
}
|
||||
""",
|
||||
"""POST https://api.openrouter.ai/v1/chat/completions
|
||||
Authorization: Bearer $OPENROUTER_API_KEY
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"model": "openrouter-latest",
|
||||
"prompt": "<<PROMPT>>",
|
||||
"temperature": 0.7,
|
||||
"max_tokens": 150,
|
||||
"top_p": 0.9,
|
||||
"frequency_penalty": 0,
|
||||
"presence_penalty": 0
|
||||
}
|
||||
""",
|
||||
]
|
||||
|
||||
|
||||
LLM_SPECS = [
|
||||
"""POST ${SELF_URL}/v1/self-probe
|
||||
Authorization: Bearer XXXXX
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"prompt": "<<PROMPT>>"
|
||||
}
|
||||
|
||||
""",
|
||||
"""POST https://api.openai.com/v1/chat/completions
|
||||
Authorization: Bearer $OPENAI_API_KEY
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"model": "gpt-3.5-turbo",
|
||||
"messages": [{"role": "user", "content": "<<PROMPT>>"}],
|
||||
"temperature": 0.7
|
||||
}
|
||||
""",
|
||||
"""
|
||||
POST https://api.deepseek.com/chat/completions
|
||||
Authorization: Bearer $DEEPSEEK_API_KEY
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"model": "deepseek-chat",
|
||||
"messages": [
|
||||
{"role": "system", "content": "You are a helpful assistant."},
|
||||
{"role": "user", "content": "<<PROMPT>>"}
|
||||
],
|
||||
"stream": false
|
||||
}
|
||||
""",
|
||||
"""POST https://api.replicate.com/v1/models/mistralai/mixtral-8x7b-instruct-v0.1/predictions
|
||||
Authorization: Bearer $APIKEY
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"input": {
|
||||
"top_k": 50,
|
||||
"top_p": 0.9,
|
||||
"prompt": "Write a bedtime story about neural networks I can read to my toddler",
|
||||
"temperature": 0.6,
|
||||
"max_new_tokens": 1024,
|
||||
"prompt_template": "<s>[INST] <<PROMPT>> [/INST] ",
|
||||
"presence_penalty": 0,
|
||||
"frequency_penalty": 0
|
||||
}
|
||||
}
|
||||
""",
|
||||
"""POST https://api.groq.com/v1/request_manager/text_completion
|
||||
Authorization: Bearer $APIKEY
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"model_id": "codellama-34b",
|
||||
"system_prompt": "You are helpful and concise coding assistant",
|
||||
"user_prompt": "<<PROMPT>>"
|
||||
}
|
||||
""",
|
||||
"""POST https://api.together.xyz/v1/chat/completions
|
||||
Authorization: Bearer $TOGETHER_API_KEY
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"model": "mistralai/Mixtral-8x7B-Instruct-v0.1",
|
||||
"messages": [
|
||||
{"role": "system", "content": "You are an expert travel guide"},
|
||||
{"role": "user", "content": "<<PROMPT>>"}
|
||||
]
|
||||
}
|
||||
""",
|
||||
"""POST ${SELF_URL}/v1/self-probe-image
|
||||
Authorization: Bearer XXXXX
|
||||
Content-Type: application/json
|
||||
|
||||
[
|
||||
{
|
||||
"role": "user",
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "What is in this image?",
|
||||
},
|
||||
{
|
||||
"type": "image_url",
|
||||
"image_url": {
|
||||
"url": f"data:image/jpeg;base64,{<<BASE64_IMAGE>>}"
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
]
|
||||
""",
|
||||
"""POST ${SELF_URL}/v1/self-probe-file
|
||||
Authorization: Bearer $API_KEY
|
||||
Content-Type: multipart/form-data
|
||||
|
||||
{
|
||||
"file": "@./sample_audio.m4a",
|
||||
"model": "whisper-large-v3"
|
||||
}
|
||||
""",
|
||||
"""POST https://api.gemini.com/v1/generate
|
||||
Authorization: Bearer $GEMINI_API_KEY
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"model": "gemini-latest",
|
||||
"prompt": "<<PROMPT>>",
|
||||
"temperature": 0.8,
|
||||
"max_tokens": 150,
|
||||
"top_p": 1.0,
|
||||
"frequency_penalty": 0,
|
||||
"presence_penalty": 0
|
||||
}
|
||||
""",
|
||||
"""POST https://api.anthropic.com/v1/complete
|
||||
Authorization: Bearer $ANTHROPIC_API_KEY
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"model": "claude-v1.3",
|
||||
"prompt": "<<PROMPT>>",
|
||||
"temperature": 0.7,
|
||||
"max_tokens_to_sample": 256,
|
||||
"stop_sequences": ["\n\nHuman:"]
|
||||
}
|
||||
""",
|
||||
"""POST https://api.cohere.ai/generate
|
||||
Authorization: Bearer $COHERE_API_KEY
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"model": "command-xlarge-nightly",
|
||||
"prompt": "<<PROMPT>>",
|
||||
"max_tokens": 300,
|
||||
"temperature": 0.75,
|
||||
"k": 0,
|
||||
"p": 0.75
|
||||
}
|
||||
""",
|
||||
"""POST https://<<RESOURCE_NAME>>.openai.azure.com/openai/deployments/<<DEPLOYMENT_NAME>>/completions?api-version=2023-06-01-preview
|
||||
Authorization: Bearer $AZURE_API_KEY
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"prompt": "<<PROMPT>>",
|
||||
"max_tokens": 150,
|
||||
"temperature": 0.7,
|
||||
"top_p": 0.9,
|
||||
"frequency_penalty": 0,
|
||||
"presence_penalty": 0
|
||||
}
|
||||
""",
|
||||
"""POST https://api.assemblyai.com/v2/transcript
|
||||
Authorization: Bearer $ASSEMBLY_API_KEY
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"audio_url": "<<AUDIO_FILE_URL>>"
|
||||
}
|
||||
""",
|
||||
"""POST https://api.openrouter.ai/v1/chat/completions
|
||||
Authorization: Bearer $OPENROUTER_API_KEY
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"model": "openrouter-latest",
|
||||
"prompt": "<<PROMPT>>",
|
||||
"temperature": 0.7,
|
||||
"max_tokens": 150,
|
||||
"top_p": 0.9,
|
||||
"frequency_penalty": 0,
|
||||
"presence_penalty": 0
|
||||
}
|
||||
""",
|
||||
]
|
||||
|
||||
|
||||
LLM_CONFIGS = [
|
||||
{
|
||||
"name": "Custom API",
|
||||
"prompts": 40000,
|
||||
"customInstructions": "Requires api spec",
|
||||
"logo": "/icons/myshell.png",
|
||||
},
|
||||
{"name": "Open AI", "prompts": 24000, "logo": "/icons/openai.png"},
|
||||
{"name": "Deepseek v1", "prompts": 24000, "logo": "/icons/deepseek.png"},
|
||||
{"name": "Replicate", "prompts": 40000, "logo": "/icons/replicate.png"},
|
||||
{"name": "Groq", "prompts": 40000, "logo": "/icons/groq.png"},
|
||||
{"name": "Together.ai", "prompts": 40000, "logo": "/icons/together.png"},
|
||||
{
|
||||
"name": "Custom API Image",
|
||||
"prompts": 40000,
|
||||
"customInstructions": "Requires api spec",
|
||||
"modality": "Image",
|
||||
"logo": "/icons/myshell.png",
|
||||
},
|
||||
{
|
||||
"name": "Custom API Files",
|
||||
"prompts": 40000,
|
||||
"customInstructions": "Requires api spec",
|
||||
"modality": "Files",
|
||||
"logo": "/icons/myshell.png",
|
||||
},
|
||||
{"name": "Gemini", "prompts": 40000, "logo": "/icons/gemini.png"},
|
||||
{"name": "Claude", "prompts": 40000, "logo": "/icons/claude.png"},
|
||||
{"name": "Cohere", "prompts": 40000, "logo": "/icons/cohere.png"},
|
||||
{"name": "Azure OpenAI", "prompts": 40000, "logo": "/icons/azureai.png"},
|
||||
{"name": "assemblyai", "prompts": 40000, "logo": "/icons/myshell.png"},
|
||||
{"name": "OpenRouter.ai", "prompts": 40000, "logo": "/icons/openrouter.png"},
|
||||
]
|
||||
|
||||
LLM_SPECS = [dict(spec=spec, **d) for spec, d in zip(_SPECS, LLM_CONFIGS)]
|
||||
@@ -6,6 +6,7 @@ from fastapi.responses import JSONResponse
|
||||
from ..primitives import FileProbeResponse, Probe
|
||||
from ..probe_actor.refusal import REFUSAL_MARKS
|
||||
from ..probe_data import REGISTRY
|
||||
from ._specs import LLM_SPECS
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@@ -73,6 +74,12 @@ async def data_config():
|
||||
return [m for m in REGISTRY]
|
||||
|
||||
|
||||
@router.get("/v1/llm-specs", response_model=list)
|
||||
def get_llm_specs():
|
||||
"""Returns the LLM API specifications."""
|
||||
return LLM_SPECS
|
||||
|
||||
|
||||
@router.get("/health")
|
||||
async def health_check():
|
||||
"""Health check endpoint."""
|
||||
|
||||
@@ -2,7 +2,8 @@ import random
|
||||
from asyncio import Event
|
||||
|
||||
from fastapi import APIRouter
|
||||
from loguru import logger
|
||||
|
||||
from agentic_security.logutils import logger
|
||||
|
||||
from ..core.app import get_current_run, get_tools_inbox
|
||||
from ..primitives import CompletionRequest, Settings
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
from collections.abc import Generator
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
|
||||
from fastapi import (
|
||||
APIRouter,
|
||||
@@ -11,9 +13,11 @@ from fastapi import (
|
||||
)
|
||||
from fastapi.responses import StreamingResponse
|
||||
|
||||
from agentic_security.logutils import logger
|
||||
|
||||
from ..core.app import get_stop_event, get_tools_inbox, set_current_run
|
||||
from ..dependencies import InMemorySecrets, get_in_memory_secrets
|
||||
from ..http_spec import LLMSpec
|
||||
from ..http_spec import InvalidHTTPSpecError, LLMSpec
|
||||
from ..primitives import LLMInfo, Scan
|
||||
from ..probe_actor import fuzzer
|
||||
|
||||
@@ -23,9 +27,16 @@ router = APIRouter()
|
||||
@router.post("/verify")
|
||||
async def verify(
|
||||
info: LLMInfo, secrets: InMemorySecrets = Depends(get_in_memory_secrets)
|
||||
):
|
||||
) -> dict[str, int | str | float]:
|
||||
spec = LLMSpec.from_string(info.spec)
|
||||
r = await spec.verify()
|
||||
try:
|
||||
r = await spec.verify()
|
||||
except InvalidHTTPSpecError as e:
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
except Exception as e:
|
||||
logger.exception(e)
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
|
||||
if r.status_code >= 400:
|
||||
raise HTTPException(status_code=r.status_code, detail=r.text)
|
||||
return dict(
|
||||
@@ -36,7 +47,7 @@ async def verify(
|
||||
)
|
||||
|
||||
|
||||
def streaming_response_generator(scan_parameters: Scan):
|
||||
def streaming_response_generator(scan_parameters: Scan) -> Generator[str, Any, None]:
|
||||
request_factory = LLMSpec.from_string(scan_parameters.llmSpec)
|
||||
set_current_run(request_factory)
|
||||
|
||||
@@ -57,7 +68,7 @@ async def scan(
|
||||
scan_parameters: Scan,
|
||||
background_tasks: BackgroundTasks,
|
||||
secrets: InMemorySecrets = Depends(get_in_memory_secrets),
|
||||
):
|
||||
) -> StreamingResponse:
|
||||
scan_parameters.with_secrets(secrets)
|
||||
return StreamingResponse(
|
||||
streaming_response_generator(scan_parameters), media_type="application/json"
|
||||
@@ -65,7 +76,7 @@ async def scan(
|
||||
|
||||
|
||||
@router.post("/stop")
|
||||
async def stop_scan():
|
||||
async def stop_scan() -> dict[str, str]:
|
||||
get_stop_event().set()
|
||||
return {"status": "Scan stopped"}
|
||||
|
||||
@@ -79,7 +90,7 @@ async def scan_csv(
|
||||
maxBudget: int = Query(10_000),
|
||||
enableMultiStepAttack: bool = Query(False),
|
||||
secrets: InMemorySecrets = Depends(get_in_memory_secrets),
|
||||
):
|
||||
) -> StreamingResponse:
|
||||
# TODO: content dataset to fuzzer
|
||||
content = await file.read() # noqa
|
||||
llm_spec = await llmSpec.read()
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import sentry_sdk
|
||||
from loguru import logger
|
||||
from sentry_sdk.integrations.logging import ignore_logger
|
||||
|
||||
from agentic_security.logutils import logger
|
||||
|
||||
from ..primitives import Settings
|
||||
|
||||
|
||||
|
||||
@@ -101,7 +101,7 @@ Content-Type: application/json
|
||||
]
|
||||
`,
|
||||
`POST ${SELF_URL}/v1/self-probe-file
|
||||
Authorization: Bearer $GROQ_API_KEY
|
||||
Authorization: Bearer $API_KEY
|
||||
Content-Type: multipart/form-data
|
||||
|
||||
{
|
||||
@@ -204,8 +204,9 @@ let LLM_CONFIGS = [
|
||||
{ name: 'Claude', prompts: 40000, logo: '/icons/claude.png' },
|
||||
{ name: 'Cohere', prompts: 40000, logo: '/icons/cohere.png' },
|
||||
{ name: 'Azure OpenAI', prompts: 40000, logo: '/icons/azureai.png' },
|
||||
{ name: 'OpenRouter.ai', prompts: 40000, logo: '/icons/openrouter.png' },
|
||||
{ name: 'assemblyai', prompts: 40000, logo: fallbackIcon },
|
||||
{ name: 'OpenRouter.ai', prompts: 40000, logo: '/icons/openrouter.png' },
|
||||
|
||||
];
|
||||
function has_image(spec) {
|
||||
return spec.includes('<<BASE64_IMAGE>>');
|
||||
|
||||
@@ -63,6 +63,32 @@
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
<div class="fixed top-6 right-6 z-50 space-y-3">
|
||||
<transition-group name="toast">
|
||||
<div
|
||||
v-for="toast in toasts"
|
||||
:key="toast.id"
|
||||
class="flex items-center p-3 rounded-xl shadow-xl text-white max-w-md animate-toast-in border border-opacity-30"
|
||||
:class="{
|
||||
'bg-success-toast border-accent-green': toast.type === 'success',
|
||||
'bg-error-toast border-accent-red': toast.type === 'error',
|
||||
'bg-info-toast border-accent-orange': toast.type === 'info'
|
||||
}"
|
||||
>
|
||||
<span class="flex-1 font-medium tracking-wide text-sm">{{ toast.message }}</span>
|
||||
<button
|
||||
@click="removeToast(toast.id)"
|
||||
class="ml-3 focus:outline-none hover:opacity-80 transition-opacity"
|
||||
>
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</transition-group>
|
||||
</div>
|
||||
|
||||
|
||||
<main class="max-w-6xl mx-auto space-y-8">
|
||||
<section class="bg-dark-card rounded-lg p-6 shadow-lg" v-show="false">
|
||||
<h2 class="text-2xl font-bold mb-4">Select a Config</h2>
|
||||
|
||||
@@ -25,6 +25,8 @@ var app = new Vue({
|
||||
showModules: false,
|
||||
showLogs: false,
|
||||
showConsentModal: true,
|
||||
toasts: [], // Array to store toast notifications
|
||||
toastTimeout: 3000, // Duration in milliseconds (3 seconds)
|
||||
statusDotClass: 'bg-gray-500', // Default status dot class
|
||||
statusText: 'Verified', // Default status text
|
||||
statusClass: 'bg-green-500 text-dark-bg', // Default status class
|
||||
@@ -93,21 +95,36 @@ var app = new Vue({
|
||||
|
||||
},
|
||||
methods: {
|
||||
showToast(message, type = 'success') {
|
||||
const id = Date.now(); // Unique ID for each toast
|
||||
this.toasts.push({ id, message, type });
|
||||
|
||||
// Automatically remove toast after timeout
|
||||
setTimeout(() => {
|
||||
this.removeToast(id);
|
||||
}, this.toastTimeout);
|
||||
},
|
||||
|
||||
removeToast(id) {
|
||||
this.toasts = this.toasts.filter(toast => toast.id !== id);
|
||||
},
|
||||
focusTextarea() {
|
||||
this.isFocused = true;
|
||||
self = this.$refs;
|
||||
// Remove 'self' assignment if not used elsewhere
|
||||
this.$nextTick(() => {
|
||||
// Focus the textarea after rendering
|
||||
self.textarea.focus();
|
||||
this.adjustHeight({ target: self.textarea });
|
||||
this.$refs.textarea.focus();
|
||||
this.adjustHeight({ target: this.$refs.textarea });
|
||||
});
|
||||
document.addEventListener("mousedown", this.handleClickOutside);
|
||||
|
||||
// Correct the event listener to use handleOutsideClick
|
||||
document.addEventListener("mousedown", this.handleOutsideClick);
|
||||
},
|
||||
handleOutsideClick(event) {
|
||||
if (!this.$refs.container.contains(event.target)) {
|
||||
if (!this.$refs.textarea) {
|
||||
return
|
||||
}
|
||||
if (!this.$refs.textarea.contains(event.target)) {
|
||||
this.isFocused = false;
|
||||
document.removeEventListener("mousedown", this.handleClickOutside);
|
||||
document.removeEventListener("mousedown", this.handleOutsideClick);
|
||||
}
|
||||
},
|
||||
unfocusTextarea() {
|
||||
@@ -115,7 +132,12 @@ var app = new Vue({
|
||||
},
|
||||
acceptConsent() {
|
||||
this.showConsentModal = false; // Close the modal
|
||||
localStorage.setItem('consentGiven', 'true'); // Save consent to local storage
|
||||
|
||||
try {
|
||||
localStorage.setItem('consentGiven', 'true'); // Save consent to local storage
|
||||
} catch (e) {
|
||||
this.showToast('Failed to save consent', 'error'); // Show error if saving fails
|
||||
}
|
||||
},
|
||||
|
||||
saveStateToLocalStorage() {
|
||||
@@ -156,6 +178,7 @@ var app = new Vue({
|
||||
this.integrationVerified = false;
|
||||
this.showResetConfirmation = false;
|
||||
this.enableMultiStepAttack = false;
|
||||
this.showToast('All settings have been reset to default', 'info');
|
||||
},
|
||||
confirmResetState() {
|
||||
this.showResetConfirmation = true;
|
||||
@@ -194,31 +217,39 @@ var app = new Vue({
|
||||
spec: this.modelSpec,
|
||||
};
|
||||
let startTime = performance.now(); // Capture start time
|
||||
const response = await fetch(`${SELF_URL}/verify`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
console.log(response);
|
||||
let r = await response.json();
|
||||
let endTime = performance.now(); // Capture end time
|
||||
let latency = endTime - startTime; // Calculate latency in milliseconds
|
||||
latency = latency.toFixed(3) / 1000; // Round to 2 decimal places
|
||||
this.latency = latency;
|
||||
if (!response.ok) {
|
||||
this.updateStatusDot(false);
|
||||
this.errorMsg = 'Integration verification failed:' + JSON.stringify(r);
|
||||
} else {
|
||||
this.errorMsg = '';
|
||||
this.updateStatusDot(true);
|
||||
this.okMsg = 'Integration verified';
|
||||
this.integrationVerified = true;
|
||||
// console.log('Integration verified', this.integrationVerified);
|
||||
// this.$forceUpdate();
|
||||
|
||||
try {
|
||||
const response = await fetch(`${SELF_URL}/verify`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
|
||||
let r = await response.json();
|
||||
|
||||
let endTime = performance.now(); // Capture end time
|
||||
let latency = ((endTime - startTime) / 1000).toFixed(3); // Calculate latency in milliseconds
|
||||
this.latency = latency;
|
||||
|
||||
if (!response.ok) {
|
||||
this.updateStatusDot(false);
|
||||
this.errorMsg = 'Integration verification failed:' + JSON.stringify(r);
|
||||
this.showToast('Integration verification failed', 'error');
|
||||
} else {
|
||||
this.errorMsg = '';
|
||||
this.updateStatusDot(true);
|
||||
this.okMsg = 'Integration verified';
|
||||
this.showToast('Integration verified successfully', 'success');
|
||||
this.integrationVerified = true;
|
||||
}
|
||||
} catch (error) {
|
||||
this.updateStatusDot(true);
|
||||
this.errorMsg = 'Server unreachable';
|
||||
this.showToast('Network error', 'error');
|
||||
}
|
||||
|
||||
this.saveStateToLocalStorage();
|
||||
},
|
||||
loadConfigs: async function () {
|
||||
@@ -240,6 +271,7 @@ var app = new Vue({
|
||||
this.errorMsg = '';
|
||||
this.okMsg = '';
|
||||
this.integrationVerified = false;
|
||||
this.showToast(`Config ${index + 1} selected`, 'info');
|
||||
},
|
||||
toggleModules() {
|
||||
this.showModules = !this.showModules;
|
||||
@@ -327,6 +359,7 @@ var app = new Vue({
|
||||
return
|
||||
}
|
||||
console.log('New row');
|
||||
this.showToast('New module', 'success');
|
||||
let payload = {
|
||||
table: this.mainTable,
|
||||
};
|
||||
@@ -437,6 +470,8 @@ var app = new Vue({
|
||||
}
|
||||
});
|
||||
}
|
||||
this.scanRunning = false;
|
||||
this.showToast('Scan finished successfully', 'success');
|
||||
this.saveStateToLocalStorage();
|
||||
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
technopollas: ['Technopollas', 'sans-serif'],
|
||||
},
|
||||
colors: {
|
||||
t1: {
|
||||
dark: {
|
||||
bg: '#0D0D0D', // Jet Black
|
||||
card: '#1A1A1A', // Dark Carbon Fiber
|
||||
text: '#FFFFFF',
|
||||
@@ -30,7 +30,7 @@
|
||||
yellow: '#2E4053', // Jet Black
|
||||
},
|
||||
},
|
||||
dark: {
|
||||
dark1: {
|
||||
bg: '#121212',
|
||||
card: '#1E1E1E',
|
||||
text: '#FFFFFF',
|
||||
@@ -86,6 +86,7 @@
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.scrollbar-hide::-webkit-scrollbar {
|
||||
display: none;
|
||||
@@ -96,4 +97,55 @@
|
||||
}
|
||||
|
||||
</style>
|
||||
<style>
|
||||
/* Toast-specific colors */
|
||||
.bg-success-toast {
|
||||
background: #1C3F74
|
||||
}
|
||||
|
||||
.bg-error-toast {
|
||||
background: #85144B
|
||||
}
|
||||
|
||||
.bg-info-toast {
|
||||
background: #FFC300
|
||||
}
|
||||
|
||||
.border-accent-green, .border-accent-red, .border-accent-orange {
|
||||
border-color: rgba(255, 255, 255, 0.1); /* Subtle white border for depth */
|
||||
}
|
||||
|
||||
/* Animation */
|
||||
.animate-toast-in {
|
||||
animation: tSlideIn 0.4s cubic-bezier(0.25, 0.1, 0.25, 1);
|
||||
}
|
||||
|
||||
@keyframes tSlideIn {
|
||||
from {
|
||||
transform: translateX(120%) scale(0.95);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: translateX(0) scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.toast-enter-active,
|
||||
.toast-leave-active {
|
||||
transition: all 0.4s cubic-bezier(0.25, 0.1, 0.25, 1);
|
||||
}
|
||||
|
||||
.toast-enter-from,
|
||||
.toast-leave-to {
|
||||
transform: translateX(120%) scale(0.95);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
/* Hover effect */
|
||||
[toast-type]:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.6);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
+13
-17
@@ -33,7 +33,7 @@ The `LLMSpec` class is the core of the HTTP specification. It provides the follo
|
||||
### Methods
|
||||
|
||||
- **`from_string(http_spec: str) -> LLMSpec`**: Parses an HTTP specification string into an `LLMSpec` object.
|
||||
- **`validate(prompt: str, encoded_image: str, encoded_audio: str, files: dict) -> None`**: Validates the request parameters based on the specified modality.
|
||||
- **`validate(prompt: str, encoded_image: str, encoded_audio: str, files: dict) -> null`**: Validates the request parameters based on the specified modality.
|
||||
- **`probe(prompt: str, encoded_image: str = "", encoded_audio: str = "", files: dict = {}) -> httpx.Response`**: Sends an HTTP request using the specified parameters.
|
||||
- **`verify() -> httpx.Response`**: Verifies the HTTP specification by sending a test request.
|
||||
|
||||
@@ -52,12 +52,11 @@ Authorization: Bearer sk-xxxxxxxxx
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"model": "gpt-3.5-turbo",
|
||||
"messages": [{"role": "user", "content": "<<PROMPT>>"}],
|
||||
"temperature": 0.7
|
||||
"model": "gpt-3.5-turbo",
|
||||
"messages": [{"role": "user", "content": "<<PROMPT>>"}],
|
||||
"temperature": 0.7
|
||||
}
|
||||
"""
|
||||
|
||||
spec = LLMSpec.from_string(http_spec)
|
||||
response = await spec.probe("What is the capital of France?")
|
||||
```
|
||||
@@ -71,12 +70,11 @@ Authorization: Bearer sk-xxxxxxxxx
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"model": "gpt-4-vision-preview",
|
||||
"messages": [{"role": "user", "content": "What is in this image? <<BASE64_IMAGE>>"}],
|
||||
"temperature": 0.7
|
||||
"model": "gpt-4-vision-preview",
|
||||
"messages": [{"role": "user", "content": "What is in this image? <<BASE64_IMAGE>>"}],
|
||||
"temperature": 0.7
|
||||
}
|
||||
"""
|
||||
|
||||
spec = LLMSpec.from_string(http_spec)
|
||||
encoded_image = encode_image_base64_by_url("https://example.com/image.jpg")
|
||||
response = await spec.probe("What is in this image?", encoded_image=encoded_image)
|
||||
@@ -91,12 +89,11 @@ Authorization: Bearer sk-xxxxxxxxx
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"model": "whisper-large-v3",
|
||||
"messages": [{"role": "user", "content": "Transcribe this audio: <<BASE64_AUDIO>>"}],
|
||||
"temperature": 0.7
|
||||
"model": "whisper-large-v3",
|
||||
"messages": [{"role": "user", "content": "Transcribe this audio: <<BASE64_AUDIO>>"}],
|
||||
"temperature": 0.7
|
||||
}
|
||||
"""
|
||||
|
||||
spec = LLMSpec.from_string(http_spec)
|
||||
encoded_audio = encode_audio_base64_by_url("https://example.com/audio.mp3")
|
||||
response = await spec.probe("Transcribe this audio:", encoded_audio=encoded_audio)
|
||||
@@ -111,12 +108,11 @@ Authorization: Bearer sk-xxxxxxxxx
|
||||
Content-Type: multipart/form-data
|
||||
|
||||
{
|
||||
"model": "gpt-3.5-turbo",
|
||||
"messages": [{"role": "user", "content": "Process this file: <<FILE>>"}],
|
||||
"temperature": 0.7
|
||||
"model": "gpt-3.5-turbo",
|
||||
"messages": [{"role": "user", "content": "Process this file: <<FILE>>"}],
|
||||
"temperature": 0.7
|
||||
}
|
||||
"""
|
||||
|
||||
spec = LLMSpec.from_string(http_spec)
|
||||
files = {"file": ("document.txt", open("document.txt", "rb"))}
|
||||
response = await spec.probe("Process this file:", files=files)
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 9.0 MiB |
+1
-1
@@ -21,4 +21,4 @@ Note: Please be aware that Agentic Security is designed as a safety scanner tool
|
||||
|
||||
## UI 🧙
|
||||
|
||||
<img width="100%" alt="booking-screen" src="https://res.cloudinary.com/dq0w2rtm9/image/upload/v1736433557/z0bsyzhsqlgcr3w4ovwp.gif">
|
||||
<img width="100%" alt="booking-screen" src="https://res.cloudinary.com/dq0w2rtm9/image/upload/v1741192668/final_aa9jhb.gif">
|
||||
|
||||
+8
-18
@@ -54,20 +54,15 @@ The `probe_data` module is a core component of the Agentic Security project, res
|
||||
|
||||
- **Classes:**
|
||||
- `PromptSelectionInterface`: Abstract base class for prompt selection strategies.
|
||||
|
||||
- Methods:
|
||||
- `select_next_prompt(current_prompt: str, passed_guard: bool) -> str`: Selects next prompt
|
||||
- `select_next_prompts(current_prompt: str, passed_guard: bool) -> list[str]`: Selects multiple prompts
|
||||
- `update_rewards(previous_prompt: str, current_prompt: str, reward: float, passed_guard: bool) -> None`: Updates rewards
|
||||
|
||||
- `update_rewards(previous_prompt: str, current_prompt: str, reward: float, passed_guard: bool) -> null`: Updates rewards
|
||||
- `RandomPromptSelector`: Basic random selection with history tracking.
|
||||
|
||||
- Parameters:
|
||||
- `prompts: list[str]`: List of available prompts
|
||||
- `history_size: int = 3`: Size of history to prevent cycles
|
||||
|
||||
- `CloudRLPromptSelector`: Cloud-based RL implementation with fallback.
|
||||
|
||||
- Parameters:
|
||||
- `prompts: list[str]`: List of available prompts
|
||||
- `api_url: str`: URL of RL service
|
||||
@@ -75,9 +70,7 @@ The `probe_data` module is a core component of the Agentic Security project, res
|
||||
- `history_size: int = 300`: Size of history
|
||||
- `timeout: int = 5`: Request timeout
|
||||
- `run_id: str = ""`: Unique run identifier
|
||||
|
||||
- `QLearningPromptSelector`: Local Q-learning implementation.
|
||||
|
||||
- Parameters:
|
||||
- `prompts: list[str]`: List of available prompts
|
||||
- `learning_rate: float = 0.1`: Learning rate
|
||||
@@ -86,13 +79,11 @@ The `probe_data` module is a core component of the Agentic Security project, res
|
||||
- `exploration_decay: float = 0.995`: Exploration decay rate
|
||||
- `min_exploration: float = 0.01`: Minimum exploration rate
|
||||
- `history_size: int = 300`: Size of history
|
||||
|
||||
- `Module`: Main class that uses CloudRLPromptSelector.
|
||||
|
||||
- Parameters:
|
||||
- `prompt_groups: list[str]`: Groups of prompts
|
||||
- `tools_inbox: asyncio.Queue`: Queue for tool communication
|
||||
- `opts: dict = {}`: Configuration options
|
||||
- **Module**: Main class that uses CloudRLPromptSelector.
|
||||
- Parameters:
|
||||
- `prompt_groups: list[str]`: Groups of prompts
|
||||
- `tools_inbox: asyncio.Queue`: Queue for tool communication
|
||||
- `opts: dict = {}`: Configuration options
|
||||
|
||||
## Usage Examples
|
||||
|
||||
@@ -119,10 +110,9 @@ from agentic_security.probe_data.modules.rl_model import QLearningPromptSelector
|
||||
|
||||
prompts = ["What is AI?", "Explain machine learning"]
|
||||
selector = QLearningPromptSelector(prompts)
|
||||
|
||||
current_prompt = "What is AI?"
|
||||
next_prompt = selector.select_next_prompt(current_prompt, passed_guard=True)
|
||||
selector.update_rewards(current_prompt, next_prompt, reward=1.0, passed_guard=True)
|
||||
next_prompt = selector.select_next_prompt(current_prompt, passed_guard=true)
|
||||
selector.update_rewards(current_prompt, next_prompt, reward=1.0, passed_guard=true)
|
||||
```
|
||||
|
||||
## Conclusion
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
:root {
|
||||
--md-primary-fg-color: #e92063;
|
||||
--md-primary-fg-color--light: #e92063;
|
||||
--md-primary-fg-color--dark: #e92063;
|
||||
--md-primary-fg-color: #2E4053;
|
||||
/* Primary color changed to pinkish */
|
||||
--md-primary-fg-color--light: #E0A3B6;
|
||||
--md-primary-fg-color--dark: #1C3F74;
|
||||
/* Dark variant changed to blue */
|
||||
}
|
||||
|
||||
|
||||
/* Revert hue value to that of pre mkdocs-material v9.4.0 */
|
||||
/* Updated slate color scheme with new background */
|
||||
[data-md-color-scheme="slate"] {
|
||||
--md-hue: 230;
|
||||
--md-default-bg-color: hsla(230, 15%, 21%, 1);
|
||||
--md-default-bg-color: #1A1A1A;
|
||||
/* Background changed to dark gray */
|
||||
}
|
||||
|
||||
.hide {
|
||||
@@ -24,12 +26,15 @@ img.index-header {
|
||||
max-width: 500px;
|
||||
}
|
||||
|
||||
/* Updated custom colors */
|
||||
.pydantic-pink {
|
||||
color: #FF007F;
|
||||
color: #E0A3B6;
|
||||
/* Updated to match new theme */
|
||||
}
|
||||
|
||||
.team-blue {
|
||||
color: #0072CE;
|
||||
color: #1C3F74;
|
||||
/* Updated to match new theme */
|
||||
}
|
||||
|
||||
.secure-green {
|
||||
@@ -67,7 +72,6 @@ img.index-header {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
||||
/* Hide the entire footer */
|
||||
.md-footer {
|
||||
display: none;
|
||||
|
||||
+1
-1
@@ -89,7 +89,7 @@ theme:
|
||||
name: Switch to light mode
|
||||
icon:
|
||||
repo: fontawesome/brands/github
|
||||
favicon: "https://res.cloudinary.com/dq0w2rtm9/image/upload/v1737555066/r17hrkre246doczwmvbv.png"
|
||||
favicon: https://res.cloudinary.com/dq0w2rtm9/image/upload/v1741195421/favicon_kuz6xr.png
|
||||
|
||||
extra:
|
||||
generator: false
|
||||
|
||||
Generated
+384
-362
@@ -149,23 +149,24 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "anyio"
|
||||
version = "4.4.0"
|
||||
version = "4.9.0"
|
||||
description = "High level compatibility layer for multiple asynchronous event loop implementations"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
python-versions = ">=3.9"
|
||||
files = [
|
||||
{file = "anyio-4.4.0-py3-none-any.whl", hash = "sha256:c1b2d8f46a8a812513012e1107cb0e68c17159a7a594208005a57dc776e1bdc7"},
|
||||
{file = "anyio-4.4.0.tar.gz", hash = "sha256:5aadc6a1bbb7cdb0bede386cac5e2940f5e2ff3aa20277e991cf028e0585ce94"},
|
||||
{file = "anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c"},
|
||||
{file = "anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
idna = ">=2.8"
|
||||
sniffio = ">=1.1"
|
||||
typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""}
|
||||
|
||||
[package.extras]
|
||||
doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"]
|
||||
test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"]
|
||||
trio = ["trio (>=0.23)"]
|
||||
doc = ["Sphinx (>=8.2,<9.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx_rtd_theme"]
|
||||
test = ["anyio[trio]", "blockbuster (>=1.5.23)", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "trustme", "truststore (>=0.9.1)", "uvloop (>=0.21)"]
|
||||
trio = ["trio (>=0.26.1)"]
|
||||
|
||||
[[package]]
|
||||
name = "appnope"
|
||||
@@ -229,6 +230,24 @@ files = [
|
||||
[package.extras]
|
||||
dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"]
|
||||
|
||||
[[package]]
|
||||
name = "backrefs"
|
||||
version = "5.8"
|
||||
description = "A wrapper around re and regex that adds additional back references."
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
files = [
|
||||
{file = "backrefs-5.8-py310-none-any.whl", hash = "sha256:c67f6638a34a5b8730812f5101376f9d41dc38c43f1fdc35cb54700f6ed4465d"},
|
||||
{file = "backrefs-5.8-py311-none-any.whl", hash = "sha256:2e1c15e4af0e12e45c8701bd5da0902d326b2e200cafcd25e49d9f06d44bb61b"},
|
||||
{file = "backrefs-5.8-py312-none-any.whl", hash = "sha256:bbef7169a33811080d67cdf1538c8289f76f0942ff971222a16034da88a73486"},
|
||||
{file = "backrefs-5.8-py313-none-any.whl", hash = "sha256:e3a63b073867dbefd0536425f43db618578528e3896fb77be7141328642a1585"},
|
||||
{file = "backrefs-5.8-py39-none-any.whl", hash = "sha256:a66851e4533fb5b371aa0628e1fee1af05135616b86140c9d787a2ffdf4b8fdc"},
|
||||
{file = "backrefs-5.8.tar.gz", hash = "sha256:2cab642a205ce966af3dd4b38ee36009b31fa9502a35fd61d59ccc116e40a6bd"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
extras = ["regex"]
|
||||
|
||||
[[package]]
|
||||
name = "beautifulsoup4"
|
||||
version = "4.12.3"
|
||||
@@ -645,13 +664,13 @@ tests = ["pytest", "pytest-cov", "pytest-xdist"]
|
||||
|
||||
[[package]]
|
||||
name = "datasets"
|
||||
version = "3.3.0"
|
||||
version = "3.4.0"
|
||||
description = "HuggingFace community-driven open-source library of datasets"
|
||||
optional = false
|
||||
python-versions = ">=3.9.0"
|
||||
files = [
|
||||
{file = "datasets-3.3.0-py3-none-any.whl", hash = "sha256:22312d09626f8fc3aa0a237b0c164997f5903bddd4c4c9e27dbaf563754c681b"},
|
||||
{file = "datasets-3.3.0.tar.gz", hash = "sha256:54c607b06f6eaa1572e21e200d2870d89d50e3bcc622dc2021a53a6ce4f684c2"},
|
||||
{file = "datasets-3.4.0-py3-none-any.whl", hash = "sha256:35ef5182bddd38f7aa774d9f33c3e8b8e9c9c7ea41b4b7969fde431919cb556b"},
|
||||
{file = "datasets-3.4.0.tar.gz", hash = "sha256:f3defae5d9c79ff586db3b17389fdde01704ffea015293a050d7e8ab6816bad8"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -673,15 +692,15 @@ xxhash = "*"
|
||||
[package.extras]
|
||||
audio = ["librosa", "soundfile (>=0.12.1)", "soxr (>=0.4.0)"]
|
||||
benchmarks = ["tensorflow (==2.12.0)", "torch (==2.0.1)", "transformers (==4.30.1)"]
|
||||
dev = ["Pillow (>=9.4.0)", "absl-py", "decorator", "decord (==0.6.0)", "elasticsearch (>=7.17.12,<8.0.0)", "faiss-cpu (>=1.8.0.post1)", "jax (>=0.3.14)", "jaxlib (>=0.3.14)", "joblib (<1.3.0)", "joblibspark", "librosa", "lz4", "moto[server]", "polars[timezone] (>=0.20.0)", "protobuf (<4.0.0)", "py7zr", "pyspark (>=3.4)", "pytest", "pytest-datadir", "pytest-xdist", "rarfile (>=4.0)", "ruff (>=0.3.0)", "s3fs", "s3fs (>=2021.11.1)", "soundfile (>=0.12.1)", "soundfile (>=0.12.1)", "soxr (>=0.4.0)", "sqlalchemy", "tensorflow (>=2.16.0)", "tensorflow (>=2.6.0)", "tensorflow (>=2.6.0)", "tiktoken", "torch", "torch (>=2.0.0)", "torchdata", "transformers", "transformers (>=4.42.0)", "zstandard"]
|
||||
dev = ["Pillow (>=9.4.0)", "absl-py", "decorator", "elasticsearch (>=7.17.12,<8.0.0)", "faiss-cpu (>=1.8.0.post1)", "jax (>=0.3.14)", "jaxlib (>=0.3.14)", "joblib (<1.3.0)", "joblibspark", "librosa", "lz4", "moto[server]", "polars[timezone] (>=0.20.0)", "protobuf (<4.0.0)", "py7zr", "pyav", "pyspark (>=3.4)", "pytest", "pytest-datadir", "pytest-xdist", "rarfile (>=4.0)", "ruff (>=0.3.0)", "s3fs", "s3fs (>=2021.11.1)", "soundfile (>=0.12.1)", "soundfile (>=0.12.1)", "soxr (>=0.4.0)", "sqlalchemy", "tensorflow (>=2.16.0)", "tensorflow (>=2.6.0)", "tensorflow (>=2.6.0)", "tiktoken", "torch", "torch (>=2.0.0)", "torchdata", "torchvision", "transformers", "transformers (>=4.42.0)", "zstandard"]
|
||||
docs = ["s3fs", "tensorflow (>=2.6.0)", "torch", "transformers"]
|
||||
jax = ["jax (>=0.3.14)", "jaxlib (>=0.3.14)"]
|
||||
quality = ["ruff (>=0.3.0)"]
|
||||
s3 = ["s3fs"]
|
||||
tensorflow = ["tensorflow (>=2.6.0)"]
|
||||
tensorflow-gpu = ["tensorflow (>=2.6.0)"]
|
||||
tests = ["Pillow (>=9.4.0)", "absl-py", "decorator", "decord (==0.6.0)", "elasticsearch (>=7.17.12,<8.0.0)", "faiss-cpu (>=1.8.0.post1)", "jax (>=0.3.14)", "jaxlib (>=0.3.14)", "joblib (<1.3.0)", "joblibspark", "librosa", "lz4", "moto[server]", "polars[timezone] (>=0.20.0)", "protobuf (<4.0.0)", "py7zr", "pyspark (>=3.4)", "pytest", "pytest-datadir", "pytest-xdist", "rarfile (>=4.0)", "s3fs (>=2021.11.1)", "soundfile (>=0.12.1)", "soundfile (>=0.12.1)", "soxr (>=0.4.0)", "sqlalchemy", "tensorflow (>=2.16.0)", "tensorflow (>=2.6.0)", "tiktoken", "torch (>=2.0.0)", "torchdata", "transformers (>=4.42.0)", "zstandard"]
|
||||
tests-numpy2 = ["Pillow (>=9.4.0)", "absl-py", "decorator", "decord (==0.6.0)", "elasticsearch (>=7.17.12,<8.0.0)", "jax (>=0.3.14)", "jaxlib (>=0.3.14)", "joblib (<1.3.0)", "joblibspark", "lz4", "moto[server]", "polars[timezone] (>=0.20.0)", "protobuf (<4.0.0)", "py7zr", "pyspark (>=3.4)", "pytest", "pytest-datadir", "pytest-xdist", "rarfile (>=4.0)", "s3fs (>=2021.11.1)", "soundfile (>=0.12.1)", "soundfile (>=0.12.1)", "soxr (>=0.4.0)", "sqlalchemy", "tiktoken", "torch (>=2.0.0)", "torchdata", "transformers (>=4.42.0)", "zstandard"]
|
||||
tests = ["Pillow (>=9.4.0)", "absl-py", "decorator", "elasticsearch (>=7.17.12,<8.0.0)", "faiss-cpu (>=1.8.0.post1)", "jax (>=0.3.14)", "jaxlib (>=0.3.14)", "joblib (<1.3.0)", "joblibspark", "librosa", "lz4", "moto[server]", "polars[timezone] (>=0.20.0)", "protobuf (<4.0.0)", "py7zr", "pyav", "pyspark (>=3.4)", "pytest", "pytest-datadir", "pytest-xdist", "rarfile (>=4.0)", "s3fs (>=2021.11.1)", "soundfile (>=0.12.1)", "soundfile (>=0.12.1)", "soxr (>=0.4.0)", "sqlalchemy", "tensorflow (>=2.16.0)", "tensorflow (>=2.6.0)", "tiktoken", "torch (>=2.0.0)", "torchdata", "torchvision", "transformers (>=4.42.0)", "zstandard"]
|
||||
tests-numpy2 = ["Pillow (>=9.4.0)", "absl-py", "decorator", "elasticsearch (>=7.17.12,<8.0.0)", "jax (>=0.3.14)", "jaxlib (>=0.3.14)", "joblib (<1.3.0)", "joblibspark", "lz4", "moto[server]", "polars[timezone] (>=0.20.0)", "protobuf (<4.0.0)", "py7zr", "pyav", "pyspark (>=3.4)", "pytest", "pytest-datadir", "pytest-xdist", "rarfile (>=4.0)", "s3fs (>=2021.11.1)", "soundfile (>=0.12.1)", "soundfile (>=0.12.1)", "soxr (>=0.4.0)", "sqlalchemy", "tiktoken", "torch (>=2.0.0)", "torchdata", "torchvision", "transformers (>=4.42.0)", "zstandard"]
|
||||
torch = ["torch"]
|
||||
vision = ["Pillow (>=9.4.0)"]
|
||||
|
||||
@@ -768,6 +787,20 @@ files = [
|
||||
{file = "distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "execnet"
|
||||
version = "2.1.1"
|
||||
description = "execnet: rapid multi-Python deployment"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "execnet-2.1.1-py3-none-any.whl", hash = "sha256:26dee51f1b80cebd6d0ca8e74dd8745419761d3bef34163928cbebbdc4749fdc"},
|
||||
{file = "execnet-2.1.1.tar.gz", hash = "sha256:5189b52c6121c24feae288166ab41b32549c7e2348652736540b9e6e7d4e72e3"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
testing = ["hatch", "pre-commit", "pytest", "tox"]
|
||||
|
||||
[[package]]
|
||||
name = "executing"
|
||||
version = "2.2.0"
|
||||
@@ -1130,15 +1163,26 @@ http2 = ["h2 (>=3,<5)"]
|
||||
socks = ["socksio (==1.*)"]
|
||||
zstd = ["zstandard (>=0.18.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "httpx-sse"
|
||||
version = "0.4.0"
|
||||
description = "Consume Server-Sent Event (SSE) messages with HTTPX."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "httpx-sse-0.4.0.tar.gz", hash = "sha256:1e81a3a3070ce322add1d3529ed42eb5f70817f45ed6ec915ab753f961139721"},
|
||||
{file = "httpx_sse-0.4.0-py3-none-any.whl", hash = "sha256:f329af6eae57eaa2bdfd962b42524764af68075ea87370a2de920af5341e318f"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "huggingface-hub"
|
||||
version = "0.28.1"
|
||||
version = "0.29.3"
|
||||
description = "Client library to download and publish models, datasets and other repos on the huggingface.co hub"
|
||||
optional = false
|
||||
python-versions = ">=3.8.0"
|
||||
files = [
|
||||
{file = "huggingface_hub-0.28.1-py3-none-any.whl", hash = "sha256:aa6b9a3ffdae939b72c464dbb0d7f99f56e649b55c3d52406f49e0a5a620c0a7"},
|
||||
{file = "huggingface_hub-0.28.1.tar.gz", hash = "sha256:893471090c98e3b6efbdfdacafe4052b20b84d59866fb6f54c33d9af18c303ae"},
|
||||
{file = "huggingface_hub-0.29.3-py3-none-any.whl", hash = "sha256:0b25710932ac649c08cdbefa6c6ccb8e88eef82927cacdb048efb726429453aa"},
|
||||
{file = "huggingface_hub-0.29.3.tar.gz", hash = "sha256:64519a25716e0ba382ba2d3fb3ca082e7c7eb4a2fc634d200e8380006e0760e5"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -1202,13 +1246,13 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "inline-snapshot"
|
||||
version = "0.20.3"
|
||||
version = "0.20.9"
|
||||
description = "golden master/snapshot/approval testing library which puts the values right into your source code"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "inline_snapshot-0.20.3-py3-none-any.whl", hash = "sha256:1ea999fbf38dd11cc72d0e1a0b9303c63d496b77bdc406a394fe2424ae842f70"},
|
||||
{file = "inline_snapshot-0.20.3.tar.gz", hash = "sha256:7a353170b7e42aa89086c7ba790a973c9645523acf985532648dabd7ee2d71f2"},
|
||||
{file = "inline_snapshot-0.20.9-py3-none-any.whl", hash = "sha256:e4bffcfb39d79bba0320a138e47217601823bbf6948760d75fa175d62a7b893d"},
|
||||
{file = "inline_snapshot-0.20.9.tar.gz", hash = "sha256:ce41505690f0f6ca96466776bc3e015ccc7412ecb65b74f4e7e90f1a834cbac4"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -1311,13 +1355,13 @@ testing = ["Django", "attrs", "colorama", "docopt", "pytest (<9.0.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "jinja2"
|
||||
version = "3.1.5"
|
||||
version = "3.1.6"
|
||||
description = "A very fast and expressive template engine."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "jinja2-3.1.5-py3-none-any.whl", hash = "sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb"},
|
||||
{file = "jinja2-3.1.5.tar.gz", hash = "sha256:8fefff8dc3034e27bb80d67c671eb8a9bc424c0ef4c0826edbff304cceff43bb"},
|
||||
{file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"},
|
||||
{file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -1695,45 +1739,45 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "matplotlib"
|
||||
version = "3.10.0"
|
||||
version = "3.10.1"
|
||||
description = "Python plotting package"
|
||||
optional = false
|
||||
python-versions = ">=3.10"
|
||||
files = [
|
||||
{file = "matplotlib-3.10.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2c5829a5a1dd5a71f0e31e6e8bb449bc0ee9dbfb05ad28fc0c6b55101b3a4be6"},
|
||||
{file = "matplotlib-3.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a2a43cbefe22d653ab34bb55d42384ed30f611bcbdea1f8d7f431011a2e1c62e"},
|
||||
{file = "matplotlib-3.10.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:607b16c8a73943df110f99ee2e940b8a1cbf9714b65307c040d422558397dac5"},
|
||||
{file = "matplotlib-3.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01d2b19f13aeec2e759414d3bfe19ddfb16b13a1250add08d46d5ff6f9be83c6"},
|
||||
{file = "matplotlib-3.10.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e6c6461e1fc63df30bf6f80f0b93f5b6784299f721bc28530477acd51bfc3d1"},
|
||||
{file = "matplotlib-3.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:994c07b9d9fe8d25951e3202a68c17900679274dadfc1248738dcfa1bd40d7f3"},
|
||||
{file = "matplotlib-3.10.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:fd44fc75522f58612ec4a33958a7e5552562b7705b42ef1b4f8c0818e304a363"},
|
||||
{file = "matplotlib-3.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c58a9622d5dbeb668f407f35f4e6bfac34bb9ecdcc81680c04d0258169747997"},
|
||||
{file = "matplotlib-3.10.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:845d96568ec873be63f25fa80e9e7fae4be854a66a7e2f0c8ccc99e94a8bd4ef"},
|
||||
{file = "matplotlib-3.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5439f4c5a3e2e8eab18e2f8c3ef929772fd5641876db71f08127eed95ab64683"},
|
||||
{file = "matplotlib-3.10.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4673ff67a36152c48ddeaf1135e74ce0d4bce1bbf836ae40ed39c29edf7e2765"},
|
||||
{file = "matplotlib-3.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:7e8632baebb058555ac0cde75db885c61f1212e47723d63921879806b40bec6a"},
|
||||
{file = "matplotlib-3.10.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4659665bc7c9b58f8c00317c3c2a299f7f258eeae5a5d56b4c64226fca2f7c59"},
|
||||
{file = "matplotlib-3.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d44cb942af1693cced2604c33a9abcef6205601c445f6d0dc531d813af8a2f5a"},
|
||||
{file = "matplotlib-3.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a994f29e968ca002b50982b27168addfd65f0105610b6be7fa515ca4b5307c95"},
|
||||
{file = "matplotlib-3.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9b0558bae37f154fffda54d779a592bc97ca8b4701f1c710055b609a3bac44c8"},
|
||||
{file = "matplotlib-3.10.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:503feb23bd8c8acc75541548a1d709c059b7184cde26314896e10a9f14df5f12"},
|
||||
{file = "matplotlib-3.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:c40ba2eb08b3f5de88152c2333c58cee7edcead0a2a0d60fcafa116b17117adc"},
|
||||
{file = "matplotlib-3.10.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:96f2886f5c1e466f21cc41b70c5a0cd47bfa0015eb2d5793c88ebce658600e25"},
|
||||
{file = "matplotlib-3.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:12eaf48463b472c3c0f8dbacdbf906e573013df81a0ab82f0616ea4b11281908"},
|
||||
{file = "matplotlib-3.10.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2fbbabc82fde51391c4da5006f965e36d86d95f6ee83fb594b279564a4c5d0d2"},
|
||||
{file = "matplotlib-3.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad2e15300530c1a94c63cfa546e3b7864bd18ea2901317bae8bbf06a5ade6dcf"},
|
||||
{file = "matplotlib-3.10.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3547d153d70233a8496859097ef0312212e2689cdf8d7ed764441c77604095ae"},
|
||||
{file = "matplotlib-3.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:c55b20591ced744aa04e8c3e4b7543ea4d650b6c3c4b208c08a05b4010e8b442"},
|
||||
{file = "matplotlib-3.10.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:9ade1003376731a971e398cc4ef38bb83ee8caf0aee46ac6daa4b0506db1fd06"},
|
||||
{file = "matplotlib-3.10.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:95b710fea129c76d30be72c3b38f330269363fbc6e570a5dd43580487380b5ff"},
|
||||
{file = "matplotlib-3.10.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5cdbaf909887373c3e094b0318d7ff230b2ad9dcb64da7ade654182872ab2593"},
|
||||
{file = "matplotlib-3.10.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d907fddb39f923d011875452ff1eca29a9e7f21722b873e90db32e5d8ddff12e"},
|
||||
{file = "matplotlib-3.10.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:3b427392354d10975c1d0f4ee18aa5844640b512d5311ef32efd4dd7db106ede"},
|
||||
{file = "matplotlib-3.10.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5fd41b0ec7ee45cd960a8e71aea7c946a28a0b8a4dcee47d2856b2af051f334c"},
|
||||
{file = "matplotlib-3.10.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:81713dd0d103b379de4516b861d964b1d789a144103277769238c732229d7f03"},
|
||||
{file = "matplotlib-3.10.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:359f87baedb1f836ce307f0e850d12bb5f1936f70d035561f90d41d305fdacea"},
|
||||
{file = "matplotlib-3.10.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ae80dc3a4add4665cf2faa90138384a7ffe2a4e37c58d83e115b54287c4f06ef"},
|
||||
{file = "matplotlib-3.10.0.tar.gz", hash = "sha256:b886d02a581b96704c9d1ffe55709e49b4d2d52709ccebc4be42db856e511278"},
|
||||
{file = "matplotlib-3.10.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:ff2ae14910be903f4a24afdbb6d7d3a6c44da210fc7d42790b87aeac92238a16"},
|
||||
{file = "matplotlib-3.10.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0721a3fd3d5756ed593220a8b86808a36c5031fce489adb5b31ee6dbb47dd5b2"},
|
||||
{file = "matplotlib-3.10.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0673b4b8f131890eb3a1ad058d6e065fb3c6e71f160089b65f8515373394698"},
|
||||
{file = "matplotlib-3.10.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e875b95ac59a7908978fe307ecdbdd9a26af7fa0f33f474a27fcf8c99f64a19"},
|
||||
{file = "matplotlib-3.10.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2589659ea30726284c6c91037216f64a506a9822f8e50592d48ac16a2f29e044"},
|
||||
{file = "matplotlib-3.10.1-cp310-cp310-win_amd64.whl", hash = "sha256:a97ff127f295817bc34517255c9db6e71de8eddaab7f837b7d341dee9f2f587f"},
|
||||
{file = "matplotlib-3.10.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:057206ff2d6ab82ff3e94ebd94463d084760ca682ed5f150817b859372ec4401"},
|
||||
{file = "matplotlib-3.10.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a144867dd6bf8ba8cb5fc81a158b645037e11b3e5cf8a50bd5f9917cb863adfe"},
|
||||
{file = "matplotlib-3.10.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56c5d9fcd9879aa8040f196a235e2dcbdf7dd03ab5b07c0696f80bc6cf04bedd"},
|
||||
{file = "matplotlib-3.10.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f69dc9713e4ad2fb21a1c30e37bd445d496524257dfda40ff4a8efb3604ab5c"},
|
||||
{file = "matplotlib-3.10.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4c59af3e8aca75d7744b68e8e78a669e91ccbcf1ac35d0102a7b1b46883f1dd7"},
|
||||
{file = "matplotlib-3.10.1-cp311-cp311-win_amd64.whl", hash = "sha256:11b65088c6f3dae784bc72e8d039a2580186285f87448babb9ddb2ad0082993a"},
|
||||
{file = "matplotlib-3.10.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:66e907a06e68cb6cfd652c193311d61a12b54f56809cafbed9736ce5ad92f107"},
|
||||
{file = "matplotlib-3.10.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e9b4bb156abb8fa5e5b2b460196f7db7264fc6d62678c03457979e7d5254b7be"},
|
||||
{file = "matplotlib-3.10.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1985ad3d97f51307a2cbfc801a930f120def19ba22864182dacef55277102ba6"},
|
||||
{file = "matplotlib-3.10.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c96f2c2f825d1257e437a1482c5a2cf4fee15db4261bd6fc0750f81ba2b4ba3d"},
|
||||
{file = "matplotlib-3.10.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:35e87384ee9e488d8dd5a2dd7baf471178d38b90618d8ea147aced4ab59c9bea"},
|
||||
{file = "matplotlib-3.10.1-cp312-cp312-win_amd64.whl", hash = "sha256:cfd414bce89cc78a7e1d25202e979b3f1af799e416010a20ab2b5ebb3a02425c"},
|
||||
{file = "matplotlib-3.10.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c42eee41e1b60fd83ee3292ed83a97a5f2a8239b10c26715d8a6172226988d7b"},
|
||||
{file = "matplotlib-3.10.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4f0647b17b667ae745c13721602b540f7aadb2a32c5b96e924cd4fea5dcb90f1"},
|
||||
{file = "matplotlib-3.10.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa3854b5f9473564ef40a41bc922be978fab217776e9ae1545c9b3a5cf2092a3"},
|
||||
{file = "matplotlib-3.10.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e496c01441be4c7d5f96d4e40f7fca06e20dcb40e44c8daa2e740e1757ad9e6"},
|
||||
{file = "matplotlib-3.10.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5d45d3f5245be5b469843450617dcad9af75ca50568acf59997bed9311131a0b"},
|
||||
{file = "matplotlib-3.10.1-cp313-cp313-win_amd64.whl", hash = "sha256:8e8e25b1209161d20dfe93037c8a7f7ca796ec9aa326e6e4588d8c4a5dd1e473"},
|
||||
{file = "matplotlib-3.10.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:19b06241ad89c3ae9469e07d77efa87041eac65d78df4fcf9cac318028009b01"},
|
||||
{file = "matplotlib-3.10.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:01e63101ebb3014e6e9f80d9cf9ee361a8599ddca2c3e166c563628b39305dbb"},
|
||||
{file = "matplotlib-3.10.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f06bad951eea6422ac4e8bdebcf3a70c59ea0a03338c5d2b109f57b64eb3972"},
|
||||
{file = "matplotlib-3.10.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a3dfb036f34873b46978f55e240cff7a239f6c4409eac62d8145bad3fc6ba5a3"},
|
||||
{file = "matplotlib-3.10.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dc6ab14a7ab3b4d813b88ba957fc05c79493a037f54e246162033591e770de6f"},
|
||||
{file = "matplotlib-3.10.1-cp313-cp313t-win_amd64.whl", hash = "sha256:bc411ebd5889a78dabbc457b3fa153203e22248bfa6eedc6797be5df0164dbf9"},
|
||||
{file = "matplotlib-3.10.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:648406f1899f9a818cef8c0231b44dcfc4ff36f167101c3fd1c9151f24220fdc"},
|
||||
{file = "matplotlib-3.10.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:02582304e352f40520727984a5a18f37e8187861f954fea9be7ef06569cf85b4"},
|
||||
{file = "matplotlib-3.10.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3809916157ba871bcdd33d3493acd7fe3037db5daa917ca6e77975a94cef779"},
|
||||
{file = "matplotlib-3.10.1.tar.gz", hash = "sha256:e8d2d0e3881b129268585bf4765ad3ee73a4591d77b9a18c214ac7e3a79fb2ba"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -1764,6 +1808,32 @@ files = [
|
||||
[package.dependencies]
|
||||
traitlets = "*"
|
||||
|
||||
[[package]]
|
||||
name = "mcp"
|
||||
version = "1.6.0"
|
||||
description = "Model Context Protocol SDK"
|
||||
optional = false
|
||||
python-versions = ">=3.10"
|
||||
files = [
|
||||
{file = "mcp-1.6.0-py3-none-any.whl", hash = "sha256:7bd24c6ea042dbec44c754f100984d186620d8b841ec30f1b19eda9b93a634d0"},
|
||||
{file = "mcp-1.6.0.tar.gz", hash = "sha256:d9324876de2c5637369f43161cd71eebfd803df5a95e46225cab8d280e366723"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
anyio = ">=4.5"
|
||||
httpx = ">=0.27"
|
||||
httpx-sse = ">=0.4"
|
||||
pydantic = ">=2.7.2,<3.0.0"
|
||||
pydantic-settings = ">=2.5.2"
|
||||
sse-starlette = ">=1.6.1"
|
||||
starlette = ">=0.27"
|
||||
uvicorn = ">=0.23.1"
|
||||
|
||||
[package.extras]
|
||||
cli = ["python-dotenv (>=1.0.0)", "typer (>=0.12.4)"]
|
||||
rich = ["rich (>=13.9.4)"]
|
||||
ws = ["websockets (>=15.0.1)"]
|
||||
|
||||
[[package]]
|
||||
name = "mdit-py-plugins"
|
||||
version = "0.4.2"
|
||||
@@ -1848,13 +1918,13 @@ min-versions = ["babel (==2.9.0)", "click (==7.0)", "colorama (==0.4)", "ghp-imp
|
||||
|
||||
[[package]]
|
||||
name = "mkdocs-autorefs"
|
||||
version = "1.3.0"
|
||||
version = "1.4.0"
|
||||
description = "Automatically link across pages in MkDocs."
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
files = [
|
||||
{file = "mkdocs_autorefs-1.3.0-py3-none-any.whl", hash = "sha256:d180f9778a04e78b7134e31418f238bba56f56d6a8af97873946ff661befffb3"},
|
||||
{file = "mkdocs_autorefs-1.3.0.tar.gz", hash = "sha256:6867764c099ace9025d6ac24fd07b85a98335fbd30107ef01053697c8f46db61"},
|
||||
{file = "mkdocs_autorefs-1.4.0-py3-none-any.whl", hash = "sha256:bad19f69655878d20194acd0162e29a89c3f7e6365ffe54e72aa3fd1072f240d"},
|
||||
{file = "mkdocs_autorefs-1.4.0.tar.gz", hash = "sha256:a9c0aa9c90edbce302c09d050a3c4cb7c76f8b7b2c98f84a7a05f53d00392156"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -1899,17 +1969,18 @@ pygments = ">2.12.0"
|
||||
|
||||
[[package]]
|
||||
name = "mkdocs-material"
|
||||
version = "9.6.4"
|
||||
version = "9.6.10"
|
||||
description = "Documentation that simply works"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "mkdocs_material-9.6.4-py3-none-any.whl", hash = "sha256:414e8376551def6d644b8e6f77226022868532a792eb2c9accf52199009f568f"},
|
||||
{file = "mkdocs_material-9.6.4.tar.gz", hash = "sha256:4d1d35e1c1d3e15294cb7fa5d02e0abaee70d408f75027dc7be6e30fb32e6867"},
|
||||
{file = "mkdocs_material-9.6.10-py3-none-any.whl", hash = "sha256:36168548df4e2ddeb9a334ddae4ab9c388ccfea4dd50ffee657d22b93dcb1c3e"},
|
||||
{file = "mkdocs_material-9.6.10.tar.gz", hash = "sha256:25a453c1f24f34fcf1f53680c03d2c1421b52ce5247f4468153c87a70cd5f1fc"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
babel = ">=2.10,<3.0"
|
||||
backrefs = ">=5.7.post1,<6.0"
|
||||
colorama = ">=0.4,<1.0"
|
||||
jinja2 = ">=3.0,<4.0"
|
||||
markdown = ">=3.2,<4.0"
|
||||
@@ -1918,7 +1989,6 @@ mkdocs-material-extensions = ">=1.3,<2.0"
|
||||
paginate = ">=0.5,<1.0"
|
||||
pygments = ">=2.16,<3.0"
|
||||
pymdown-extensions = ">=10.2,<11.0"
|
||||
regex = ">=2022.4"
|
||||
requests = ">=2.26,<3.0"
|
||||
|
||||
[package.extras]
|
||||
@@ -1939,27 +2009,26 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "mkdocstrings"
|
||||
version = "0.28.1"
|
||||
version = "0.29.0"
|
||||
description = "Automatic documentation from sources, for MkDocs."
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
files = [
|
||||
{file = "mkdocstrings-0.28.1-py3-none-any.whl", hash = "sha256:a5878ae5cd1e26f491ff084c1f9ab995687d52d39a5c558e9b7023d0e4e0b740"},
|
||||
{file = "mkdocstrings-0.28.1.tar.gz", hash = "sha256:fb64576906771b7701e8e962fd90073650ff689e95eb86e86751a66d65ab4489"},
|
||||
{file = "mkdocstrings-0.29.0-py3-none-any.whl", hash = "sha256:8ea98358d2006f60befa940fdebbbc88a26b37ecbcded10be726ba359284f73d"},
|
||||
{file = "mkdocstrings-0.29.0.tar.gz", hash = "sha256:3657be1384543ce0ee82112c3e521bbf48e41303aa0c229b9ffcccba057d922e"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
Jinja2 = ">=2.11.1"
|
||||
Markdown = ">=3.6"
|
||||
MarkupSafe = ">=1.1"
|
||||
mkdocs = ">=1.4"
|
||||
mkdocs-autorefs = ">=1.3"
|
||||
mkdocs-get-deps = ">=0.2"
|
||||
mkdocs = ">=1.6"
|
||||
mkdocs-autorefs = ">=1.4"
|
||||
pymdown-extensions = ">=6.3"
|
||||
|
||||
[package.extras]
|
||||
crystal = ["mkdocstrings-crystal (>=0.3.4)"]
|
||||
python = ["mkdocstrings-python (>=0.5.2)"]
|
||||
python = ["mkdocstrings-python (>=1.16.2)"]
|
||||
python-legacy = ["mkdocstrings-python-legacy (>=0.2.1)"]
|
||||
|
||||
[[package]]
|
||||
@@ -2087,49 +2156,43 @@ dill = ">=0.3.8"
|
||||
|
||||
[[package]]
|
||||
name = "mypy"
|
||||
version = "1.14.1"
|
||||
version = "1.15.0"
|
||||
description = "Optional static typing for Python"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
python-versions = ">=3.9"
|
||||
files = [
|
||||
{file = "mypy-1.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:52686e37cf13d559f668aa398dd7ddf1f92c5d613e4f8cb262be2fb4fedb0fcb"},
|
||||
{file = "mypy-1.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1fb545ca340537d4b45d3eecdb3def05e913299ca72c290326be19b3804b39c0"},
|
||||
{file = "mypy-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:90716d8b2d1f4cd503309788e51366f07c56635a3309b0f6a32547eaaa36a64d"},
|
||||
{file = "mypy-1.14.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ae753f5c9fef278bcf12e1a564351764f2a6da579d4a81347e1d5a15819997b"},
|
||||
{file = "mypy-1.14.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e0fe0f5feaafcb04505bcf439e991c6d8f1bf8b15f12b05feeed96e9e7bf1427"},
|
||||
{file = "mypy-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:7d54bd85b925e501c555a3227f3ec0cfc54ee8b6930bd6141ec872d1c572f81f"},
|
||||
{file = "mypy-1.14.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f995e511de847791c3b11ed90084a7a0aafdc074ab88c5a9711622fe4751138c"},
|
||||
{file = "mypy-1.14.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d64169ec3b8461311f8ce2fd2eb5d33e2d0f2c7b49116259c51d0d96edee48d1"},
|
||||
{file = "mypy-1.14.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ba24549de7b89b6381b91fbc068d798192b1b5201987070319889e93038967a8"},
|
||||
{file = "mypy-1.14.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:183cf0a45457d28ff9d758730cd0210419ac27d4d3f285beda038c9083363b1f"},
|
||||
{file = "mypy-1.14.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f2a0ecc86378f45347f586e4163d1769dd81c5a223d577fe351f26b179e148b1"},
|
||||
{file = "mypy-1.14.1-cp311-cp311-win_amd64.whl", hash = "sha256:ad3301ebebec9e8ee7135d8e3109ca76c23752bac1e717bc84cd3836b4bf3eae"},
|
||||
{file = "mypy-1.14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:30ff5ef8519bbc2e18b3b54521ec319513a26f1bba19a7582e7b1f58a6e69f14"},
|
||||
{file = "mypy-1.14.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cb9f255c18052343c70234907e2e532bc7e55a62565d64536dbc7706a20b78b9"},
|
||||
{file = "mypy-1.14.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b4e3413e0bddea671012b063e27591b953d653209e7a4fa5e48759cda77ca11"},
|
||||
{file = "mypy-1.14.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:553c293b1fbdebb6c3c4030589dab9fafb6dfa768995a453d8a5d3b23784af2e"},
|
||||
{file = "mypy-1.14.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fad79bfe3b65fe6a1efaed97b445c3d37f7be9fdc348bdb2d7cac75579607c89"},
|
||||
{file = "mypy-1.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:8fa2220e54d2946e94ab6dbb3ba0a992795bd68b16dc852db33028df2b00191b"},
|
||||
{file = "mypy-1.14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:92c3ed5afb06c3a8e188cb5da4984cab9ec9a77ba956ee419c68a388b4595255"},
|
||||
{file = "mypy-1.14.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:dbec574648b3e25f43d23577309b16534431db4ddc09fda50841f1e34e64ed34"},
|
||||
{file = "mypy-1.14.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8c6d94b16d62eb3e947281aa7347d78236688e21081f11de976376cf010eb31a"},
|
||||
{file = "mypy-1.14.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d4b19b03fdf54f3c5b2fa474c56b4c13c9dbfb9a2db4370ede7ec11a2c5927d9"},
|
||||
{file = "mypy-1.14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0c911fde686394753fff899c409fd4e16e9b294c24bfd5e1ea4675deae1ac6fd"},
|
||||
{file = "mypy-1.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:8b21525cb51671219f5307be85f7e646a153e5acc656e5cebf64bfa076c50107"},
|
||||
{file = "mypy-1.14.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7084fb8f1128c76cd9cf68fe5971b37072598e7c31b2f9f95586b65c741a9d31"},
|
||||
{file = "mypy-1.14.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8f845a00b4f420f693f870eaee5f3e2692fa84cc8514496114649cfa8fd5e2c6"},
|
||||
{file = "mypy-1.14.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:44bf464499f0e3a2d14d58b54674dee25c031703b2ffc35064bd0df2e0fac319"},
|
||||
{file = "mypy-1.14.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c99f27732c0b7dc847adb21c9d47ce57eb48fa33a17bc6d7d5c5e9f9e7ae5bac"},
|
||||
{file = "mypy-1.14.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:bce23c7377b43602baa0bd22ea3265c49b9ff0b76eb315d6c34721af4cdf1d9b"},
|
||||
{file = "mypy-1.14.1-cp38-cp38-win_amd64.whl", hash = "sha256:8edc07eeade7ebc771ff9cf6b211b9a7d93687ff892150cb5692e4f4272b0837"},
|
||||
{file = "mypy-1.14.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3888a1816d69f7ab92092f785a462944b3ca16d7c470d564165fe703b0970c35"},
|
||||
{file = "mypy-1.14.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:46c756a444117c43ee984bd055db99e498bc613a70bbbc120272bd13ca579fbc"},
|
||||
{file = "mypy-1.14.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:27fc248022907e72abfd8e22ab1f10e903915ff69961174784a3900a8cba9ad9"},
|
||||
{file = "mypy-1.14.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:499d6a72fb7e5de92218db961f1a66d5f11783f9ae549d214617edab5d4dbdbb"},
|
||||
{file = "mypy-1.14.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:57961db9795eb566dc1d1b4e9139ebc4c6b0cb6e7254ecde69d1552bf7613f60"},
|
||||
{file = "mypy-1.14.1-cp39-cp39-win_amd64.whl", hash = "sha256:07ba89fdcc9451f2ebb02853deb6aaaa3d2239a236669a63ab3801bbf923ef5c"},
|
||||
{file = "mypy-1.14.1-py3-none-any.whl", hash = "sha256:b66a60cc4073aeb8ae00057f9c1f64d49e90f918fbcef9a977eb121da8b8f1d1"},
|
||||
{file = "mypy-1.14.1.tar.gz", hash = "sha256:7ec88144fe9b510e8475ec2f5f251992690fcf89ccb4500b214b4226abcd32d6"},
|
||||
{file = "mypy-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:979e4e1a006511dacf628e36fadfecbcc0160a8af6ca7dad2f5025529e082c13"},
|
||||
{file = "mypy-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c4bb0e1bd29f7d34efcccd71cf733580191e9a264a2202b0239da95984c5b559"},
|
||||
{file = "mypy-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be68172e9fd9ad8fb876c6389f16d1c1b5f100ffa779f77b1fb2176fcc9ab95b"},
|
||||
{file = "mypy-1.15.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c7be1e46525adfa0d97681432ee9fcd61a3964c2446795714699a998d193f1a3"},
|
||||
{file = "mypy-1.15.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2e2c2e6d3593f6451b18588848e66260ff62ccca522dd231cd4dd59b0160668b"},
|
||||
{file = "mypy-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:6983aae8b2f653e098edb77f893f7b6aca69f6cffb19b2cc7443f23cce5f4828"},
|
||||
{file = "mypy-1.15.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2922d42e16d6de288022e5ca321cd0618b238cfc5570e0263e5ba0a77dbef56f"},
|
||||
{file = "mypy-1.15.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2ee2d57e01a7c35de00f4634ba1bbf015185b219e4dc5909e281016df43f5ee5"},
|
||||
{file = "mypy-1.15.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:973500e0774b85d9689715feeffcc980193086551110fd678ebe1f4342fb7c5e"},
|
||||
{file = "mypy-1.15.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a95fb17c13e29d2d5195869262f8125dfdb5c134dc8d9a9d0aecf7525b10c2c"},
|
||||
{file = "mypy-1.15.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1905f494bfd7d85a23a88c5d97840888a7bd516545fc5aaedff0267e0bb54e2f"},
|
||||
{file = "mypy-1.15.0-cp311-cp311-win_amd64.whl", hash = "sha256:c9817fa23833ff189db061e6d2eff49b2f3b6ed9856b4a0a73046e41932d744f"},
|
||||
{file = "mypy-1.15.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:aea39e0583d05124836ea645f412e88a5c7d0fd77a6d694b60d9b6b2d9f184fd"},
|
||||
{file = "mypy-1.15.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2f2147ab812b75e5b5499b01ade1f4a81489a147c01585cda36019102538615f"},
|
||||
{file = "mypy-1.15.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ce436f4c6d218a070048ed6a44c0bbb10cd2cc5e272b29e7845f6a2f57ee4464"},
|
||||
{file = "mypy-1.15.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8023ff13985661b50a5928fc7a5ca15f3d1affb41e5f0a9952cb68ef090b31ee"},
|
||||
{file = "mypy-1.15.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1124a18bc11a6a62887e3e137f37f53fbae476dc36c185d549d4f837a2a6a14e"},
|
||||
{file = "mypy-1.15.0-cp312-cp312-win_amd64.whl", hash = "sha256:171a9ca9a40cd1843abeca0e405bc1940cd9b305eaeea2dda769ba096932bb22"},
|
||||
{file = "mypy-1.15.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:93faf3fdb04768d44bf28693293f3904bbb555d076b781ad2530214ee53e3445"},
|
||||
{file = "mypy-1.15.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:811aeccadfb730024c5d3e326b2fbe9249bb7413553f15499a4050f7c30e801d"},
|
||||
{file = "mypy-1.15.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:98b7b9b9aedb65fe628c62a6dc57f6d5088ef2dfca37903a7d9ee374d03acca5"},
|
||||
{file = "mypy-1.15.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c43a7682e24b4f576d93072216bf56eeff70d9140241f9edec0c104d0c515036"},
|
||||
{file = "mypy-1.15.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:baefc32840a9f00babd83251560e0ae1573e2f9d1b067719479bfb0e987c6357"},
|
||||
{file = "mypy-1.15.0-cp313-cp313-win_amd64.whl", hash = "sha256:b9378e2c00146c44793c98b8d5a61039a048e31f429fb0eb546d93f4b000bedf"},
|
||||
{file = "mypy-1.15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e601a7fa172c2131bff456bb3ee08a88360760d0d2f8cbd7a75a65497e2df078"},
|
||||
{file = "mypy-1.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:712e962a6357634fef20412699a3655c610110e01cdaa6180acec7fc9f8513ba"},
|
||||
{file = "mypy-1.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f95579473af29ab73a10bada2f9722856792a36ec5af5399b653aa28360290a5"},
|
||||
{file = "mypy-1.15.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8f8722560a14cde92fdb1e31597760dc35f9f5524cce17836c0d22841830fd5b"},
|
||||
{file = "mypy-1.15.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1fbb8da62dc352133d7d7ca90ed2fb0e9d42bb1a32724c287d3c76c58cbaa9c2"},
|
||||
{file = "mypy-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:d10d994b41fb3497719bbf866f227b3489048ea4bbbb5015357db306249f7980"},
|
||||
{file = "mypy-1.15.0-py3-none-any.whl", hash = "sha256:5469affef548bd1895d86d3bf10ce2b44e33d86923c29e4d675b3e323437ea3e"},
|
||||
{file = "mypy-1.15.0.tar.gz", hash = "sha256:404534629d51d3efea5c800ee7c42b72a6554d6c400e6a79eafe15d11341fd43"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -2257,154 +2320,143 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "numpy"
|
||||
version = "2.2.2"
|
||||
version = "2.2.3"
|
||||
description = "Fundamental package for array computing in Python"
|
||||
optional = false
|
||||
python-versions = ">=3.10"
|
||||
files = [
|
||||
{file = "numpy-2.2.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7079129b64cb78bdc8d611d1fd7e8002c0a2565da6a47c4df8062349fee90e3e"},
|
||||
{file = "numpy-2.2.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2ec6c689c61df613b783aeb21f945c4cbe6c51c28cb70aae8430577ab39f163e"},
|
||||
{file = "numpy-2.2.2-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:40c7ff5da22cd391944a28c6a9c638a5eef77fcf71d6e3a79e1d9d9e82752715"},
|
||||
{file = "numpy-2.2.2-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:995f9e8181723852ca458e22de5d9b7d3ba4da3f11cc1cb113f093b271d7965a"},
|
||||
{file = "numpy-2.2.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b78ea78450fd96a498f50ee096f69c75379af5138f7881a51355ab0e11286c97"},
|
||||
{file = "numpy-2.2.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3fbe72d347fbc59f94124125e73fc4976a06927ebc503ec5afbfb35f193cd957"},
|
||||
{file = "numpy-2.2.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8e6da5cffbbe571f93588f562ed130ea63ee206d12851b60819512dd3e1ba50d"},
|
||||
{file = "numpy-2.2.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:09d6a2032faf25e8d0cadde7fd6145118ac55d2740132c1d845f98721b5ebcfd"},
|
||||
{file = "numpy-2.2.2-cp310-cp310-win32.whl", hash = "sha256:159ff6ee4c4a36a23fe01b7c3d07bd8c14cc433d9720f977fcd52c13c0098160"},
|
||||
{file = "numpy-2.2.2-cp310-cp310-win_amd64.whl", hash = "sha256:64bd6e1762cd7f0986a740fee4dff927b9ec2c5e4d9a28d056eb17d332158014"},
|
||||
{file = "numpy-2.2.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:642199e98af1bd2b6aeb8ecf726972d238c9877b0f6e8221ee5ab945ec8a2189"},
|
||||
{file = "numpy-2.2.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6d9fc9d812c81e6168b6d405bf00b8d6739a7f72ef22a9214c4241e0dc70b323"},
|
||||
{file = "numpy-2.2.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:c7d1fd447e33ee20c1f33f2c8e6634211124a9aabde3c617687d8b739aa69eac"},
|
||||
{file = "numpy-2.2.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:451e854cfae0febe723077bd0cf0a4302a5d84ff25f0bfece8f29206c7bed02e"},
|
||||
{file = "numpy-2.2.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd249bc894af67cbd8bad2c22e7cbcd46cf87ddfca1f1289d1e7e54868cc785c"},
|
||||
{file = "numpy-2.2.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:02935e2c3c0c6cbe9c7955a8efa8908dd4221d7755644c59d1bba28b94fd334f"},
|
||||
{file = "numpy-2.2.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a972cec723e0563aa0823ee2ab1df0cb196ed0778f173b381c871a03719d4826"},
|
||||
{file = "numpy-2.2.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d6d6a0910c3b4368d89dde073e630882cdb266755565155bc33520283b2d9df8"},
|
||||
{file = "numpy-2.2.2-cp311-cp311-win32.whl", hash = "sha256:860fd59990c37c3ef913c3ae390b3929d005243acca1a86facb0773e2d8d9e50"},
|
||||
{file = "numpy-2.2.2-cp311-cp311-win_amd64.whl", hash = "sha256:da1eeb460ecce8d5b8608826595c777728cdf28ce7b5a5a8c8ac8d949beadcf2"},
|
||||
{file = "numpy-2.2.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ac9bea18d6d58a995fac1b2cb4488e17eceeac413af014b1dd26170b766d8467"},
|
||||
{file = "numpy-2.2.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:23ae9f0c2d889b7b2d88a3791f6c09e2ef827c2446f1c4a3e3e76328ee4afd9a"},
|
||||
{file = "numpy-2.2.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:3074634ea4d6df66be04f6728ee1d173cfded75d002c75fac79503a880bf3825"},
|
||||
{file = "numpy-2.2.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:8ec0636d3f7d68520afc6ac2dc4b8341ddb725039de042faf0e311599f54eb37"},
|
||||
{file = "numpy-2.2.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ffbb1acd69fdf8e89dd60ef6182ca90a743620957afb7066385a7bbe88dc748"},
|
||||
{file = "numpy-2.2.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0349b025e15ea9d05c3d63f9657707a4e1d471128a3b1d876c095f328f8ff7f0"},
|
||||
{file = "numpy-2.2.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:463247edcee4a5537841d5350bc87fe8e92d7dd0e8c71c995d2c6eecb8208278"},
|
||||
{file = "numpy-2.2.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9dd47ff0cb2a656ad69c38da850df3454da88ee9a6fde0ba79acceee0e79daba"},
|
||||
{file = "numpy-2.2.2-cp312-cp312-win32.whl", hash = "sha256:4525b88c11906d5ab1b0ec1f290996c0020dd318af8b49acaa46f198b1ffc283"},
|
||||
{file = "numpy-2.2.2-cp312-cp312-win_amd64.whl", hash = "sha256:5acea83b801e98541619af398cc0109ff48016955cc0818f478ee9ef1c5c3dcb"},
|
||||
{file = "numpy-2.2.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b208cfd4f5fe34e1535c08983a1a6803fdbc7a1e86cf13dd0c61de0b51a0aadc"},
|
||||
{file = "numpy-2.2.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d0bbe7dd86dca64854f4b6ce2ea5c60b51e36dfd597300057cf473d3615f2369"},
|
||||
{file = "numpy-2.2.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:22ea3bb552ade325530e72a0c557cdf2dea8914d3a5e1fecf58fa5dbcc6f43cd"},
|
||||
{file = "numpy-2.2.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:128c41c085cab8a85dc29e66ed88c05613dccf6bc28b3866cd16050a2f5448be"},
|
||||
{file = "numpy-2.2.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:250c16b277e3b809ac20d1f590716597481061b514223c7badb7a0f9993c7f84"},
|
||||
{file = "numpy-2.2.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0c8854b09bc4de7b041148d8550d3bd712b5c21ff6a8ed308085f190235d7ff"},
|
||||
{file = "numpy-2.2.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b6fb9c32a91ec32a689ec6410def76443e3c750e7cfc3fb2206b985ffb2b85f0"},
|
||||
{file = "numpy-2.2.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:57b4012e04cc12b78590a334907e01b3a85efb2107df2b8733ff1ed05fce71de"},
|
||||
{file = "numpy-2.2.2-cp313-cp313-win32.whl", hash = "sha256:4dbd80e453bd34bd003b16bd802fac70ad76bd463f81f0c518d1245b1c55e3d9"},
|
||||
{file = "numpy-2.2.2-cp313-cp313-win_amd64.whl", hash = "sha256:5a8c863ceacae696aff37d1fd636121f1a512117652e5dfb86031c8d84836369"},
|
||||
{file = "numpy-2.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:b3482cb7b3325faa5f6bc179649406058253d91ceda359c104dac0ad320e1391"},
|
||||
{file = "numpy-2.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:9491100aba630910489c1d0158034e1c9a6546f0b1340f716d522dc103788e39"},
|
||||
{file = "numpy-2.2.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:41184c416143defa34cc8eb9d070b0a5ba4f13a0fa96a709e20584638254b317"},
|
||||
{file = "numpy-2.2.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:7dca87ca328f5ea7dafc907c5ec100d187911f94825f8700caac0b3f4c384b49"},
|
||||
{file = "numpy-2.2.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bc61b307655d1a7f9f4b043628b9f2b721e80839914ede634e3d485913e1fb2"},
|
||||
{file = "numpy-2.2.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fad446ad0bc886855ddf5909cbf8cb5d0faa637aaa6277fb4b19ade134ab3c7"},
|
||||
{file = "numpy-2.2.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:149d1113ac15005652e8d0d3f6fd599360e1a708a4f98e43c9c77834a28238cb"},
|
||||
{file = "numpy-2.2.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:106397dbbb1896f99e044efc90360d098b3335060375c26aa89c0d8a97c5f648"},
|
||||
{file = "numpy-2.2.2-cp313-cp313t-win32.whl", hash = "sha256:0eec19f8af947a61e968d5429f0bd92fec46d92b0008d0a6685b40d6adf8a4f4"},
|
||||
{file = "numpy-2.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:97b974d3ba0fb4612b77ed35d7627490e8e3dff56ab41454d9e8b23448940576"},
|
||||
{file = "numpy-2.2.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b0531f0b0e07643eb089df4c509d30d72c9ef40defa53e41363eca8a8cc61495"},
|
||||
{file = "numpy-2.2.2-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:e9e82dcb3f2ebbc8cb5ce1102d5f1c5ed236bf8a11730fb45ba82e2841ec21df"},
|
||||
{file = "numpy-2.2.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0d4142eb40ca6f94539e4db929410f2a46052a0fe7a2c1c59f6179c39938d2a"},
|
||||
{file = "numpy-2.2.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:356ca982c188acbfa6af0d694284d8cf20e95b1c3d0aefa8929376fea9146f60"},
|
||||
{file = "numpy-2.2.2.tar.gz", hash = "sha256:ed6906f61834d687738d25988ae117683705636936cc605be0bb208b23df4d8f"},
|
||||
{file = "numpy-2.2.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cbc6472e01952d3d1b2772b720428f8b90e2deea8344e854df22b0618e9cce71"},
|
||||
{file = "numpy-2.2.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cdfe0c22692a30cd830c0755746473ae66c4a8f2e7bd508b35fb3b6a0813d787"},
|
||||
{file = "numpy-2.2.3-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:e37242f5324ffd9f7ba5acf96d774f9276aa62a966c0bad8dae692deebec7716"},
|
||||
{file = "numpy-2.2.3-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:95172a21038c9b423e68be78fd0be6e1b97674cde269b76fe269a5dfa6fadf0b"},
|
||||
{file = "numpy-2.2.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5b47c440210c5d1d67e1cf434124e0b5c395eee1f5806fdd89b553ed1acd0a3"},
|
||||
{file = "numpy-2.2.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0391ea3622f5c51a2e29708877d56e3d276827ac5447d7f45e9bc4ade8923c52"},
|
||||
{file = "numpy-2.2.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f6b3dfc7661f8842babd8ea07e9897fe3d9b69a1d7e5fbb743e4160f9387833b"},
|
||||
{file = "numpy-2.2.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1ad78ce7f18ce4e7df1b2ea4019b5817a2f6a8a16e34ff2775f646adce0a5027"},
|
||||
{file = "numpy-2.2.3-cp310-cp310-win32.whl", hash = "sha256:5ebeb7ef54a7be11044c33a17b2624abe4307a75893c001a4800857956b41094"},
|
||||
{file = "numpy-2.2.3-cp310-cp310-win_amd64.whl", hash = "sha256:596140185c7fa113563c67c2e894eabe0daea18cf8e33851738c19f70ce86aeb"},
|
||||
{file = "numpy-2.2.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:16372619ee728ed67a2a606a614f56d3eabc5b86f8b615c79d01957062826ca8"},
|
||||
{file = "numpy-2.2.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5521a06a3148686d9269c53b09f7d399a5725c47bbb5b35747e1cb76326b714b"},
|
||||
{file = "numpy-2.2.3-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:7c8dde0ca2f77828815fd1aedfdf52e59071a5bae30dac3b4da2a335c672149a"},
|
||||
{file = "numpy-2.2.3-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:77974aba6c1bc26e3c205c2214f0d5b4305bdc719268b93e768ddb17e3fdd636"},
|
||||
{file = "numpy-2.2.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d42f9c36d06440e34226e8bd65ff065ca0963aeecada587b937011efa02cdc9d"},
|
||||
{file = "numpy-2.2.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2712c5179f40af9ddc8f6727f2bd910ea0eb50206daea75f58ddd9fa3f715bb"},
|
||||
{file = "numpy-2.2.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c8b0451d2ec95010d1db8ca733afc41f659f425b7f608af569711097fd6014e2"},
|
||||
{file = "numpy-2.2.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d9b4a8148c57ecac25a16b0e11798cbe88edf5237b0df99973687dd866f05e1b"},
|
||||
{file = "numpy-2.2.3-cp311-cp311-win32.whl", hash = "sha256:1f45315b2dc58d8a3e7754fe4e38b6fce132dab284a92851e41b2b344f6441c5"},
|
||||
{file = "numpy-2.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f48ba6f6c13e5e49f3d3efb1b51c8193215c42ac82610a04624906a9270be6f"},
|
||||
{file = "numpy-2.2.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:12c045f43b1d2915eca6b880a7f4a256f59d62df4f044788c8ba67709412128d"},
|
||||
{file = "numpy-2.2.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:87eed225fd415bbae787f93a457af7f5990b92a334e346f72070bf569b9c9c95"},
|
||||
{file = "numpy-2.2.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:712a64103d97c404e87d4d7c47fb0c7ff9acccc625ca2002848e0d53288b90ea"},
|
||||
{file = "numpy-2.2.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:a5ae282abe60a2db0fd407072aff4599c279bcd6e9a2475500fc35b00a57c532"},
|
||||
{file = "numpy-2.2.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5266de33d4c3420973cf9ae3b98b54a2a6d53a559310e3236c4b2b06b9c07d4e"},
|
||||
{file = "numpy-2.2.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b787adbf04b0db1967798dba8da1af07e387908ed1553a0d6e74c084d1ceafe"},
|
||||
{file = "numpy-2.2.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:34c1b7e83f94f3b564b35f480f5652a47007dd91f7c839f404d03279cc8dd021"},
|
||||
{file = "numpy-2.2.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4d8335b5f1b6e2bce120d55fb17064b0262ff29b459e8493d1785c18ae2553b8"},
|
||||
{file = "numpy-2.2.3-cp312-cp312-win32.whl", hash = "sha256:4d9828d25fb246bedd31e04c9e75714a4087211ac348cb39c8c5f99dbb6683fe"},
|
||||
{file = "numpy-2.2.3-cp312-cp312-win_amd64.whl", hash = "sha256:83807d445817326b4bcdaaaf8e8e9f1753da04341eceec705c001ff342002e5d"},
|
||||
{file = "numpy-2.2.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7bfdb06b395385ea9b91bf55c1adf1b297c9fdb531552845ff1d3ea6e40d5aba"},
|
||||
{file = "numpy-2.2.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:23c9f4edbf4c065fddb10a4f6e8b6a244342d95966a48820c614891e5059bb50"},
|
||||
{file = "numpy-2.2.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:a0c03b6be48aaf92525cccf393265e02773be8fd9551a2f9adbe7db1fa2b60f1"},
|
||||
{file = "numpy-2.2.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:2376e317111daa0a6739e50f7ee2a6353f768489102308b0d98fcf4a04f7f3b5"},
|
||||
{file = "numpy-2.2.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8fb62fe3d206d72fe1cfe31c4a1106ad2b136fcc1606093aeab314f02930fdf2"},
|
||||
{file = "numpy-2.2.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:52659ad2534427dffcc36aac76bebdd02b67e3b7a619ac67543bc9bfe6b7cdb1"},
|
||||
{file = "numpy-2.2.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1b416af7d0ed3271cad0f0a0d0bee0911ed7eba23e66f8424d9f3dfcdcae1304"},
|
||||
{file = "numpy-2.2.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1402da8e0f435991983d0a9708b779f95a8c98c6b18a171b9f1be09005e64d9d"},
|
||||
{file = "numpy-2.2.3-cp313-cp313-win32.whl", hash = "sha256:136553f123ee2951bfcfbc264acd34a2fc2f29d7cdf610ce7daf672b6fbaa693"},
|
||||
{file = "numpy-2.2.3-cp313-cp313-win_amd64.whl", hash = "sha256:5b732c8beef1d7bc2d9e476dbba20aaff6167bf205ad9aa8d30913859e82884b"},
|
||||
{file = "numpy-2.2.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:435e7a933b9fda8126130b046975a968cc2d833b505475e588339e09f7672890"},
|
||||
{file = "numpy-2.2.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:7678556eeb0152cbd1522b684dcd215250885993dd00adb93679ec3c0e6e091c"},
|
||||
{file = "numpy-2.2.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:2e8da03bd561504d9b20e7a12340870dfc206c64ea59b4cfee9fceb95070ee94"},
|
||||
{file = "numpy-2.2.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:c9aa4496fd0e17e3843399f533d62857cef5900facf93e735ef65aa4bbc90ef0"},
|
||||
{file = "numpy-2.2.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4ca91d61a4bf61b0f2228f24bbfa6a9facd5f8af03759fe2a655c50ae2c6610"},
|
||||
{file = "numpy-2.2.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:deaa09cd492e24fd9b15296844c0ad1b3c976da7907e1c1ed3a0ad21dded6f76"},
|
||||
{file = "numpy-2.2.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:246535e2f7496b7ac85deffe932896a3577be7af8fb7eebe7146444680297e9a"},
|
||||
{file = "numpy-2.2.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:daf43a3d1ea699402c5a850e5313680ac355b4adc9770cd5cfc2940e7861f1bf"},
|
||||
{file = "numpy-2.2.3-cp313-cp313t-win32.whl", hash = "sha256:cf802eef1f0134afb81fef94020351be4fe1d6681aadf9c5e862af6602af64ef"},
|
||||
{file = "numpy-2.2.3-cp313-cp313t-win_amd64.whl", hash = "sha256:aee2512827ceb6d7f517c8b85aa5d3923afe8fc7a57d028cffcd522f1c6fd082"},
|
||||
{file = "numpy-2.2.3-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:3c2ec8a0f51d60f1e9c0c5ab116b7fc104b165ada3f6c58abf881cb2eb16044d"},
|
||||
{file = "numpy-2.2.3-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:ed2cf9ed4e8ebc3b754d398cba12f24359f018b416c380f577bbae112ca52fc9"},
|
||||
{file = "numpy-2.2.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39261798d208c3095ae4f7bc8eaeb3481ea8c6e03dc48028057d3cbdbdb8937e"},
|
||||
{file = "numpy-2.2.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:783145835458e60fa97afac25d511d00a1eca94d4a8f3ace9fe2043003c678e4"},
|
||||
{file = "numpy-2.2.3.tar.gz", hash = "sha256:dbdc15f0c81611925f382dfa97b3bd0bc2c1ce19d4fe50482cb0ddc12ba30020"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "orjson"
|
||||
version = "3.10.15"
|
||||
version = "3.10.16"
|
||||
description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
python-versions = ">=3.9"
|
||||
files = [
|
||||
{file = "orjson-3.10.15-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:552c883d03ad185f720d0c09583ebde257e41b9521b74ff40e08b7dec4559c04"},
|
||||
{file = "orjson-3.10.15-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:616e3e8d438d02e4854f70bfdc03a6bcdb697358dbaa6bcd19cbe24d24ece1f8"},
|
||||
{file = "orjson-3.10.15-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7c2c79fa308e6edb0ffab0a31fd75a7841bf2a79a20ef08a3c6e3b26814c8ca8"},
|
||||
{file = "orjson-3.10.15-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73cb85490aa6bf98abd20607ab5c8324c0acb48d6da7863a51be48505646c814"},
|
||||
{file = "orjson-3.10.15-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:763dadac05e4e9d2bc14938a45a2d0560549561287d41c465d3c58aec818b164"},
|
||||
{file = "orjson-3.10.15-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a330b9b4734f09a623f74a7490db713695e13b67c959713b78369f26b3dee6bf"},
|
||||
{file = "orjson-3.10.15-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a61a4622b7ff861f019974f73d8165be1bd9a0855e1cad18ee167acacabeb061"},
|
||||
{file = "orjson-3.10.15-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:acd271247691574416b3228db667b84775c497b245fa275c6ab90dc1ffbbd2b3"},
|
||||
{file = "orjson-3.10.15-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:e4759b109c37f635aa5c5cc93a1b26927bfde24b254bcc0e1149a9fada253d2d"},
|
||||
{file = "orjson-3.10.15-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:9e992fd5cfb8b9f00bfad2fd7a05a4299db2bbe92e6440d9dd2fab27655b3182"},
|
||||
{file = "orjson-3.10.15-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f95fb363d79366af56c3f26b71df40b9a583b07bbaaf5b317407c4d58497852e"},
|
||||
{file = "orjson-3.10.15-cp310-cp310-win32.whl", hash = "sha256:f9875f5fea7492da8ec2444839dcc439b0ef298978f311103d0b7dfd775898ab"},
|
||||
{file = "orjson-3.10.15-cp310-cp310-win_amd64.whl", hash = "sha256:17085a6aa91e1cd70ca8533989a18b5433e15d29c574582f76f821737c8d5806"},
|
||||
{file = "orjson-3.10.15-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:c4cc83960ab79a4031f3119cc4b1a1c627a3dc09df125b27c4201dff2af7eaa6"},
|
||||
{file = "orjson-3.10.15-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ddbeef2481d895ab8be5185f2432c334d6dec1f5d1933a9c83014d188e102cef"},
|
||||
{file = "orjson-3.10.15-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9e590a0477b23ecd5b0ac865b1b907b01b3c5535f5e8a8f6ab0e503efb896334"},
|
||||
{file = "orjson-3.10.15-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a6be38bd103d2fd9bdfa31c2720b23b5d47c6796bcb1d1b598e3924441b4298d"},
|
||||
{file = "orjson-3.10.15-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ff4f6edb1578960ed628a3b998fa54d78d9bb3e2eb2cfc5c2a09732431c678d0"},
|
||||
{file = "orjson-3.10.15-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b0482b21d0462eddd67e7fce10b89e0b6ac56570424662b685a0d6fccf581e13"},
|
||||
{file = "orjson-3.10.15-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bb5cc3527036ae3d98b65e37b7986a918955f85332c1ee07f9d3f82f3a6899b5"},
|
||||
{file = "orjson-3.10.15-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d569c1c462912acdd119ccbf719cf7102ea2c67dd03b99edcb1a3048651ac96b"},
|
||||
{file = "orjson-3.10.15-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:1e6d33efab6b71d67f22bf2962895d3dc6f82a6273a965fab762e64fa90dc399"},
|
||||
{file = "orjson-3.10.15-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:c33be3795e299f565681d69852ac8c1bc5c84863c0b0030b2b3468843be90388"},
|
||||
{file = "orjson-3.10.15-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:eea80037b9fae5339b214f59308ef0589fc06dc870578b7cce6d71eb2096764c"},
|
||||
{file = "orjson-3.10.15-cp311-cp311-win32.whl", hash = "sha256:d5ac11b659fd798228a7adba3e37c010e0152b78b1982897020a8e019a94882e"},
|
||||
{file = "orjson-3.10.15-cp311-cp311-win_amd64.whl", hash = "sha256:cf45e0214c593660339ef63e875f32ddd5aa3b4adc15e662cdb80dc49e194f8e"},
|
||||
{file = "orjson-3.10.15-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:9d11c0714fc85bfcf36ada1179400862da3288fc785c30e8297844c867d7505a"},
|
||||
{file = "orjson-3.10.15-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dba5a1e85d554e3897fa9fe6fbcff2ed32d55008973ec9a2b992bd9a65d2352d"},
|
||||
{file = "orjson-3.10.15-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7723ad949a0ea502df656948ddd8b392780a5beaa4c3b5f97e525191b102fff0"},
|
||||
{file = "orjson-3.10.15-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6fd9bc64421e9fe9bd88039e7ce8e58d4fead67ca88e3a4014b143cec7684fd4"},
|
||||
{file = "orjson-3.10.15-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dadba0e7b6594216c214ef7894c4bd5f08d7c0135f4dd0145600be4fbcc16767"},
|
||||
{file = "orjson-3.10.15-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b48f59114fe318f33bbaee8ebeda696d8ccc94c9e90bc27dbe72153094e26f41"},
|
||||
{file = "orjson-3.10.15-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:035fb83585e0f15e076759b6fedaf0abb460d1765b6a36f48018a52858443514"},
|
||||
{file = "orjson-3.10.15-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d13b7fe322d75bf84464b075eafd8e7dd9eae05649aa2a5354cfa32f43c59f17"},
|
||||
{file = "orjson-3.10.15-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:7066b74f9f259849629e0d04db6609db4cf5b973248f455ba5d3bd58a4daaa5b"},
|
||||
{file = "orjson-3.10.15-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:88dc3f65a026bd3175eb157fea994fca6ac7c4c8579fc5a86fc2114ad05705b7"},
|
||||
{file = "orjson-3.10.15-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b342567e5465bd99faa559507fe45e33fc76b9fb868a63f1642c6bc0735ad02a"},
|
||||
{file = "orjson-3.10.15-cp312-cp312-win32.whl", hash = "sha256:0a4f27ea5617828e6b58922fdbec67b0aa4bb844e2d363b9244c47fa2180e665"},
|
||||
{file = "orjson-3.10.15-cp312-cp312-win_amd64.whl", hash = "sha256:ef5b87e7aa9545ddadd2309efe6824bd3dd64ac101c15dae0f2f597911d46eaa"},
|
||||
{file = "orjson-3.10.15-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:bae0e6ec2b7ba6895198cd981b7cca95d1487d0147c8ed751e5632ad16f031a6"},
|
||||
{file = "orjson-3.10.15-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f93ce145b2db1252dd86af37d4165b6faa83072b46e3995ecc95d4b2301b725a"},
|
||||
{file = "orjson-3.10.15-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7c203f6f969210128af3acae0ef9ea6aab9782939f45f6fe02d05958fe761ef9"},
|
||||
{file = "orjson-3.10.15-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8918719572d662e18b8af66aef699d8c21072e54b6c82a3f8f6404c1f5ccd5e0"},
|
||||
{file = "orjson-3.10.15-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f71eae9651465dff70aa80db92586ad5b92df46a9373ee55252109bb6b703307"},
|
||||
{file = "orjson-3.10.15-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e117eb299a35f2634e25ed120c37c641398826c2f5a3d3cc39f5993b96171b9e"},
|
||||
{file = "orjson-3.10.15-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:13242f12d295e83c2955756a574ddd6741c81e5b99f2bef8ed8d53e47a01e4b7"},
|
||||
{file = "orjson-3.10.15-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7946922ada8f3e0b7b958cc3eb22cfcf6c0df83d1fe5521b4a100103e3fa84c8"},
|
||||
{file = "orjson-3.10.15-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:b7155eb1623347f0f22c38c9abdd738b287e39b9982e1da227503387b81b34ca"},
|
||||
{file = "orjson-3.10.15-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:208beedfa807c922da4e81061dafa9c8489c6328934ca2a562efa707e049e561"},
|
||||
{file = "orjson-3.10.15-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eca81f83b1b8c07449e1d6ff7074e82e3fd6777e588f1a6632127f286a968825"},
|
||||
{file = "orjson-3.10.15-cp313-cp313-win32.whl", hash = "sha256:c03cd6eea1bd3b949d0d007c8d57049aa2b39bd49f58b4b2af571a5d3833d890"},
|
||||
{file = "orjson-3.10.15-cp313-cp313-win_amd64.whl", hash = "sha256:fd56a26a04f6ba5fb2045b0acc487a63162a958ed837648c5781e1fe3316cfbf"},
|
||||
{file = "orjson-3.10.15-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:5e8afd6200e12771467a1a44e5ad780614b86abb4b11862ec54861a82d677746"},
|
||||
{file = "orjson-3.10.15-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da9a18c500f19273e9e104cca8c1f0b40a6470bcccfc33afcc088045d0bf5ea6"},
|
||||
{file = "orjson-3.10.15-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb00b7bfbdf5d34a13180e4805d76b4567025da19a197645ca746fc2fb536586"},
|
||||
{file = "orjson-3.10.15-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:33aedc3d903378e257047fee506f11e0833146ca3e57a1a1fb0ddb789876c1e1"},
|
||||
{file = "orjson-3.10.15-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd0099ae6aed5eb1fc84c9eb72b95505a3df4267e6962eb93cdd5af03be71c98"},
|
||||
{file = "orjson-3.10.15-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c864a80a2d467d7786274fce0e4f93ef2a7ca4ff31f7fc5634225aaa4e9e98c"},
|
||||
{file = "orjson-3.10.15-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c25774c9e88a3e0013d7d1a6c8056926b607a61edd423b50eb5c88fd7f2823ae"},
|
||||
{file = "orjson-3.10.15-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:e78c211d0074e783d824ce7bb85bf459f93a233eb67a5b5003498232ddfb0e8a"},
|
||||
{file = "orjson-3.10.15-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:43e17289ffdbbac8f39243916c893d2ae41a2ea1a9cbb060a56a4d75286351ae"},
|
||||
{file = "orjson-3.10.15-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:781d54657063f361e89714293c095f506c533582ee40a426cb6489c48a637b81"},
|
||||
{file = "orjson-3.10.15-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:6875210307d36c94873f553786a808af2788e362bd0cf4c8e66d976791e7b528"},
|
||||
{file = "orjson-3.10.15-cp38-cp38-win32.whl", hash = "sha256:305b38b2b8f8083cc3d618927d7f424349afce5975b316d33075ef0f73576b60"},
|
||||
{file = "orjson-3.10.15-cp38-cp38-win_amd64.whl", hash = "sha256:5dd9ef1639878cc3efffed349543cbf9372bdbd79f478615a1c633fe4e4180d1"},
|
||||
{file = "orjson-3.10.15-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:ffe19f3e8d68111e8644d4f4e267a069ca427926855582ff01fc012496d19969"},
|
||||
{file = "orjson-3.10.15-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d433bf32a363823863a96561a555227c18a522a8217a6f9400f00ddc70139ae2"},
|
||||
{file = "orjson-3.10.15-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:da03392674f59a95d03fa5fb9fe3a160b0511ad84b7a3914699ea5a1b3a38da2"},
|
||||
{file = "orjson-3.10.15-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3a63bb41559b05360ded9132032239e47983a39b151af1201f07ec9370715c82"},
|
||||
{file = "orjson-3.10.15-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3766ac4702f8f795ff3fa067968e806b4344af257011858cc3d6d8721588b53f"},
|
||||
{file = "orjson-3.10.15-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a1c73dcc8fadbd7c55802d9aa093b36878d34a3b3222c41052ce6b0fc65f8e8"},
|
||||
{file = "orjson-3.10.15-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b299383825eafe642cbab34be762ccff9fd3408d72726a6b2a4506d410a71ab3"},
|
||||
{file = "orjson-3.10.15-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:abc7abecdbf67a173ef1316036ebbf54ce400ef2300b4e26a7b843bd446c2480"},
|
||||
{file = "orjson-3.10.15-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:3614ea508d522a621384c1d6639016a5a2e4f027f3e4a1c93a51867615d28829"},
|
||||
{file = "orjson-3.10.15-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:295c70f9dc154307777ba30fe29ff15c1bcc9dfc5c48632f37d20a607e9ba85a"},
|
||||
{file = "orjson-3.10.15-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:63309e3ff924c62404923c80b9e2048c1f74ba4b615e7584584389ada50ed428"},
|
||||
{file = "orjson-3.10.15-cp39-cp39-win32.whl", hash = "sha256:a2f708c62d026fb5340788ba94a55c23df4e1869fec74be455e0b2f5363b8507"},
|
||||
{file = "orjson-3.10.15-cp39-cp39-win_amd64.whl", hash = "sha256:efcf6c735c3d22ef60c4aa27a5238f1a477df85e9b15f2142f9d669beb2d13fd"},
|
||||
{file = "orjson-3.10.15.tar.gz", hash = "sha256:05ca7fe452a2e9d8d9d706a2984c95b9c2ebc5db417ce0b7a49b91d50642a23e"},
|
||||
{file = "orjson-3.10.16-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:4cb473b8e79154fa778fb56d2d73763d977be3dcc140587e07dbc545bbfc38f8"},
|
||||
{file = "orjson-3.10.16-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:622a8e85eeec1948690409a19ca1c7d9fd8ff116f4861d261e6ae2094fe59a00"},
|
||||
{file = "orjson-3.10.16-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c682d852d0ce77613993dc967e90e151899fe2d8e71c20e9be164080f468e370"},
|
||||
{file = "orjson-3.10.16-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8c520ae736acd2e32df193bcff73491e64c936f3e44a2916b548da048a48b46b"},
|
||||
{file = "orjson-3.10.16-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:134f87c76bfae00f2094d85cfab261b289b76d78c6da8a7a3b3c09d362fd1e06"},
|
||||
{file = "orjson-3.10.16-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b59afde79563e2cf37cfe62ee3b71c063fd5546c8e662d7fcfc2a3d5031a5c4c"},
|
||||
{file = "orjson-3.10.16-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:113602f8241daaff05d6fad25bd481d54c42d8d72ef4c831bb3ab682a54d9e15"},
|
||||
{file = "orjson-3.10.16-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4fc0077d101f8fab4031e6554fc17b4c2ad8fdbc56ee64a727f3c95b379e31da"},
|
||||
{file = "orjson-3.10.16-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:9c6bf6ff180cd69e93f3f50380224218cfab79953a868ea3908430bcfaf9cb5e"},
|
||||
{file = "orjson-3.10.16-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5673eadfa952f95a7cd76418ff189df11b0a9c34b1995dff43a6fdbce5d63bf4"},
|
||||
{file = "orjson-3.10.16-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5fe638a423d852b0ae1e1a79895851696cb0d9fa0946fdbfd5da5072d9bb9551"},
|
||||
{file = "orjson-3.10.16-cp310-cp310-win32.whl", hash = "sha256:33af58f479b3c6435ab8f8b57999874b4b40c804c7a36b5cc6b54d8f28e1d3dd"},
|
||||
{file = "orjson-3.10.16-cp310-cp310-win_amd64.whl", hash = "sha256:0338356b3f56d71293c583350af26f053017071836b07e064e92819ecf1aa055"},
|
||||
{file = "orjson-3.10.16-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:44fcbe1a1884f8bc9e2e863168b0f84230c3d634afe41c678637d2728ea8e739"},
|
||||
{file = "orjson-3.10.16-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78177bf0a9d0192e0b34c3d78bcff7fe21d1b5d84aeb5ebdfe0dbe637b885225"},
|
||||
{file = "orjson-3.10.16-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:12824073a010a754bb27330cad21d6e9b98374f497f391b8707752b96f72e741"},
|
||||
{file = "orjson-3.10.16-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ddd41007e56284e9867864aa2f29f3136bb1dd19a49ca43c0b4eda22a579cf53"},
|
||||
{file = "orjson-3.10.16-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0877c4d35de639645de83666458ca1f12560d9fa7aa9b25d8bb8f52f61627d14"},
|
||||
{file = "orjson-3.10.16-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9a09a539e9cc3beead3e7107093b4ac176d015bec64f811afb5965fce077a03c"},
|
||||
{file = "orjson-3.10.16-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31b98bc9b40610fec971d9a4d67bb2ed02eec0a8ae35f8ccd2086320c28526ca"},
|
||||
{file = "orjson-3.10.16-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0ce243f5a8739f3a18830bc62dc2e05b69a7545bafd3e3249f86668b2bcd8e50"},
|
||||
{file = "orjson-3.10.16-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:64792c0025bae049b3074c6abe0cf06f23c8e9f5a445f4bab31dc5ca23dbf9e1"},
|
||||
{file = "orjson-3.10.16-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ea53f7e68eec718b8e17e942f7ca56c6bd43562eb19db3f22d90d75e13f0431d"},
|
||||
{file = "orjson-3.10.16-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a741ba1a9488c92227711bde8c8c2b63d7d3816883268c808fbeada00400c164"},
|
||||
{file = "orjson-3.10.16-cp311-cp311-win32.whl", hash = "sha256:c7ed2c61bb8226384c3fdf1fb01c51b47b03e3f4536c985078cccc2fd19f1619"},
|
||||
{file = "orjson-3.10.16-cp311-cp311-win_amd64.whl", hash = "sha256:cd67d8b3e0e56222a2e7b7f7da9031e30ecd1fe251c023340b9f12caca85ab60"},
|
||||
{file = "orjson-3.10.16-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:6d3444abbfa71ba21bb042caa4b062535b122248259fdb9deea567969140abca"},
|
||||
{file = "orjson-3.10.16-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:30245c08d818fdcaa48b7d5b81499b8cae09acabb216fe61ca619876b128e184"},
|
||||
{file = "orjson-3.10.16-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0ba1d0baa71bf7579a4ccdcf503e6f3098ef9542106a0eca82395898c8a500a"},
|
||||
{file = "orjson-3.10.16-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb0beefa5ef3af8845f3a69ff2a4aa62529b5acec1cfe5f8a6b4141033fd46ef"},
|
||||
{file = "orjson-3.10.16-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6daa0e1c9bf2e030e93c98394de94506f2a4d12e1e9dadd7c53d5e44d0f9628e"},
|
||||
{file = "orjson-3.10.16-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9da9019afb21e02410ef600e56666652b73eb3e4d213a0ec919ff391a7dd52aa"},
|
||||
{file = "orjson-3.10.16-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:daeb3a1ee17b69981d3aae30c3b4e786b0f8c9e6c71f2b48f1aef934f63f38f4"},
|
||||
{file = "orjson-3.10.16-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80fed80eaf0e20a31942ae5d0728849862446512769692474be5e6b73123a23b"},
|
||||
{file = "orjson-3.10.16-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73390ed838f03764540a7bdc4071fe0123914c2cc02fb6abf35182d5fd1b7a42"},
|
||||
{file = "orjson-3.10.16-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:a22bba012a0c94ec02a7768953020ab0d3e2b884760f859176343a36c01adf87"},
|
||||
{file = "orjson-3.10.16-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5385bbfdbc90ff5b2635b7e6bebf259652db00a92b5e3c45b616df75b9058e88"},
|
||||
{file = "orjson-3.10.16-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:02c6279016346e774dd92625d46c6c40db687b8a0d685aadb91e26e46cc33e1e"},
|
||||
{file = "orjson-3.10.16-cp312-cp312-win32.whl", hash = "sha256:7ca55097a11426db80f79378e873a8c51f4dde9ffc22de44850f9696b7eb0e8c"},
|
||||
{file = "orjson-3.10.16-cp312-cp312-win_amd64.whl", hash = "sha256:86d127efdd3f9bf5f04809b70faca1e6836556ea3cc46e662b44dab3fe71f3d6"},
|
||||
{file = "orjson-3.10.16-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:148a97f7de811ba14bc6dbc4a433e0341ffd2cc285065199fb5f6a98013744bd"},
|
||||
{file = "orjson-3.10.16-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:1d960c1bf0e734ea36d0adc880076de3846aaec45ffad29b78c7f1b7962516b8"},
|
||||
{file = "orjson-3.10.16-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a318cd184d1269f68634464b12871386808dc8b7c27de8565234d25975a7a137"},
|
||||
{file = "orjson-3.10.16-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:df23f8df3ef9223d1d6748bea63fca55aae7da30a875700809c500a05975522b"},
|
||||
{file = "orjson-3.10.16-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b94dda8dd6d1378f1037d7f3f6b21db769ef911c4567cbaa962bb6dc5021cf90"},
|
||||
{file = "orjson-3.10.16-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f12970a26666a8775346003fd94347d03ccb98ab8aa063036818381acf5f523e"},
|
||||
{file = "orjson-3.10.16-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15a1431a245d856bd56e4d29ea0023eb4d2c8f71efe914beb3dee8ab3f0cd7fb"},
|
||||
{file = "orjson-3.10.16-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c83655cfc247f399a222567d146524674a7b217af7ef8289c0ff53cfe8db09f0"},
|
||||
{file = "orjson-3.10.16-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:fa59ae64cb6ddde8f09bdbf7baf933c4cd05734ad84dcf4e43b887eb24e37652"},
|
||||
{file = "orjson-3.10.16-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:ca5426e5aacc2e9507d341bc169d8af9c3cbe88f4cd4c1cf2f87e8564730eb56"},
|
||||
{file = "orjson-3.10.16-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:6fd5da4edf98a400946cd3a195680de56f1e7575109b9acb9493331047157430"},
|
||||
{file = "orjson-3.10.16-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:980ecc7a53e567169282a5e0ff078393bac78320d44238da4e246d71a4e0e8f5"},
|
||||
{file = "orjson-3.10.16-cp313-cp313-win32.whl", hash = "sha256:28f79944dd006ac540a6465ebd5f8f45dfdf0948ff998eac7a908275b4c1add6"},
|
||||
{file = "orjson-3.10.16-cp313-cp313-win_amd64.whl", hash = "sha256:fe0a145e96d51971407cb8ba947e63ead2aa915db59d6631a355f5f2150b56b7"},
|
||||
{file = "orjson-3.10.16-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:c35b5c1fb5a5d6d2fea825dec5d3d16bea3c06ac744708a8e1ff41d4ba10cdf1"},
|
||||
{file = "orjson-3.10.16-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9aac7ecc86218b4b3048c768f227a9452287001d7548500150bb75ee21bf55d"},
|
||||
{file = "orjson-3.10.16-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6e19f5102fff36f923b6dfdb3236ec710b649da975ed57c29833cb910c5a73ab"},
|
||||
{file = "orjson-3.10.16-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:17210490408eb62755a334a6f20ed17c39f27b4f45d89a38cd144cd458eba80b"},
|
||||
{file = "orjson-3.10.16-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fbbe04451db85916e52a9f720bd89bf41f803cf63b038595674691680cbebd1b"},
|
||||
{file = "orjson-3.10.16-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6a966eba501a3a1f309f5a6af32ed9eb8f316fa19d9947bac3e6350dc63a6f0a"},
|
||||
{file = "orjson-3.10.16-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01e0d22f06c81e6c435723343e1eefc710e0510a35d897856766d475f2a15687"},
|
||||
{file = "orjson-3.10.16-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:7c1e602d028ee285dbd300fb9820b342b937df64d5a3336e1618b354e95a2569"},
|
||||
{file = "orjson-3.10.16-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:d230e5020666a6725629df81e210dc11c3eae7d52fe909a7157b3875238484f3"},
|
||||
{file = "orjson-3.10.16-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:0f8baac07d4555f57d44746a7d80fbe6b2c4fe2ed68136b4abb51cfec512a5e9"},
|
||||
{file = "orjson-3.10.16-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:524e48420b90fc66953e91b660b3d05faaf921277d6707e328fde1c218b31250"},
|
||||
{file = "orjson-3.10.16-cp39-cp39-win32.whl", hash = "sha256:a9f614e31423d7292dbca966a53b2d775c64528c7d91424ab2747d8ab8ce5c72"},
|
||||
{file = "orjson-3.10.16-cp39-cp39-win_amd64.whl", hash = "sha256:c338dc2296d1ed0d5c5c27dfb22d00b330555cb706c2e0be1e1c3940a0895905"},
|
||||
{file = "orjson-3.10.16.tar.gz", hash = "sha256:d2aaa5c495e11d17b9b93205f5fa196737ee3202f000aaebf028dc9a73750f10"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2699,13 +2751,13 @@ testing = ["pytest", "pytest-benchmark"]
|
||||
|
||||
[[package]]
|
||||
name = "pre-commit"
|
||||
version = "4.1.0"
|
||||
version = "4.2.0"
|
||||
description = "A framework for managing and maintaining multi-language pre-commit hooks."
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
files = [
|
||||
{file = "pre_commit-4.1.0-py2.py3-none-any.whl", hash = "sha256:d29e7cb346295bcc1cc75fc3e92e343495e3ea0196c9ec6ba53f49f10ab6ae7b"},
|
||||
{file = "pre_commit-4.1.0.tar.gz", hash = "sha256:ae3f018575a588e30dfddfab9a05448bfbd6b73d78709617b5a2b853549716d4"},
|
||||
{file = "pre_commit-4.2.0-py2.py3-none-any.whl", hash = "sha256:a009ca7205f1eb497d10b845e52c838a98b6cdd2102a6c8e4540e94ee75c58bd"},
|
||||
{file = "pre_commit-4.2.0.tar.gz", hash = "sha256:601283b9757afd87d40c4c4a9b2b5de9637a8ea02eaff7adc2d0fb4e04841146"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -3102,6 +3154,26 @@ files = [
|
||||
[package.dependencies]
|
||||
typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0"
|
||||
|
||||
[[package]]
|
||||
name = "pydantic-settings"
|
||||
version = "2.8.1"
|
||||
description = "Settings management using Pydantic"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "pydantic_settings-2.8.1-py3-none-any.whl", hash = "sha256:81942d5ac3d905f7f3ee1a70df5dfb62d5569c12f51a5a647defc1c3d9ee2e9c"},
|
||||
{file = "pydantic_settings-2.8.1.tar.gz", hash = "sha256:d5c663dfbe9db9d5e1c646b2e161da12f0d734d422ee56f567d0ea2cee4e8585"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
pydantic = ">=2.7.0"
|
||||
python-dotenv = ">=0.21.0"
|
||||
|
||||
[package.extras]
|
||||
azure-key-vault = ["azure-identity (>=1.16.0)", "azure-keyvault-secrets (>=4.8.0)"]
|
||||
toml = ["tomli (>=2.0.1)"]
|
||||
yaml = ["pyyaml (>=6.0.1)"]
|
||||
|
||||
[[package]]
|
||||
name = "pyfiglet"
|
||||
version = "1.0.2"
|
||||
@@ -3161,13 +3233,13 @@ diagrams = ["jinja2", "railroad-diagrams"]
|
||||
|
||||
[[package]]
|
||||
name = "pytest"
|
||||
version = "8.3.4"
|
||||
version = "8.3.5"
|
||||
description = "pytest: simple powerful testing with Python"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6"},
|
||||
{file = "pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761"},
|
||||
{file = "pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820"},
|
||||
{file = "pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -3232,6 +3304,26 @@ pytest = ">=6.2.5"
|
||||
[package.extras]
|
||||
dev = ["pre-commit", "pytest-asyncio", "tox"]
|
||||
|
||||
[[package]]
|
||||
name = "pytest-xdist"
|
||||
version = "3.6.1"
|
||||
description = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "pytest_xdist-3.6.1-py3-none-any.whl", hash = "sha256:9ed4adfb68a016610848639bb7e02c9352d5d9f03d04809919e2dafc3be4cca7"},
|
||||
{file = "pytest_xdist-3.6.1.tar.gz", hash = "sha256:ead156a4db231eec769737f57668ef58a2084a34b2e55c4a8fa20d861107300d"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
execnet = ">=2.1"
|
||||
pytest = ">=7.0.0"
|
||||
|
||||
[package.extras]
|
||||
psutil = ["psutil (>=3.0)"]
|
||||
setproctitle = ["setproctitle"]
|
||||
testing = ["filelock"]
|
||||
|
||||
[[package]]
|
||||
name = "python-dateutil"
|
||||
version = "2.9.0.post0"
|
||||
@@ -3246,6 +3338,20 @@ files = [
|
||||
[package.dependencies]
|
||||
six = ">=1.5"
|
||||
|
||||
[[package]]
|
||||
name = "python-dotenv"
|
||||
version = "1.1.0"
|
||||
description = "Read key-value pairs from a .env file and set them as environment variables"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
files = [
|
||||
{file = "python_dotenv-1.1.0-py3-none-any.whl", hash = "sha256:d7c01d9e2293916c18baf562d95698754b0dbbb5e74d457c45d4f6561fb9d55d"},
|
||||
{file = "python_dotenv-1.1.0.tar.gz", hash = "sha256:41f90bc6f5f177fb41f53e87666db362025010eb28f60a01c9143bfa33a2b2d5"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
cli = ["click (>=5.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "python-multipart"
|
||||
version = "0.0.20"
|
||||
@@ -3507,109 +3613,6 @@ files = [
|
||||
attrs = ">=22.2.0"
|
||||
rpds-py = ">=0.7.0"
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "2024.11.6"
|
||||
description = "Alternative regular expression module, to replace re."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "regex-2024.11.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ff590880083d60acc0433f9c3f713c51f7ac6ebb9adf889c79a261ecf541aa91"},
|
||||
{file = "regex-2024.11.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:658f90550f38270639e83ce492f27d2c8d2cd63805c65a13a14d36ca126753f0"},
|
||||
{file = "regex-2024.11.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:164d8b7b3b4bcb2068b97428060b2a53be050085ef94eca7f240e7947f1b080e"},
|
||||
{file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3660c82f209655a06b587d55e723f0b813d3a7db2e32e5e7dc64ac2a9e86fde"},
|
||||
{file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d22326fcdef5e08c154280b71163ced384b428343ae16a5ab2b3354aed12436e"},
|
||||
{file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f1ac758ef6aebfc8943560194e9fd0fa18bcb34d89fd8bd2af18183afd8da3a2"},
|
||||
{file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:997d6a487ff00807ba810e0f8332c18b4eb8d29463cfb7c820dc4b6e7562d0cf"},
|
||||
{file = "regex-2024.11.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:02a02d2bb04fec86ad61f3ea7f49c015a0681bf76abb9857f945d26159d2968c"},
|
||||
{file = "regex-2024.11.6-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f02f93b92358ee3f78660e43b4b0091229260c5d5c408d17d60bf26b6c900e86"},
|
||||
{file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:06eb1be98df10e81ebaded73fcd51989dcf534e3c753466e4b60c4697a003b67"},
|
||||
{file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:040df6fe1a5504eb0f04f048e6d09cd7c7110fef851d7c567a6b6e09942feb7d"},
|
||||
{file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabbfc59f2c6edba2a6622c647b716e34e8e3867e0ab975412c5c2f79b82da2"},
|
||||
{file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8447d2d39b5abe381419319f942de20b7ecd60ce86f16a23b0698f22e1b70008"},
|
||||
{file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:da8f5fc57d1933de22a9e23eec290a0d8a5927a5370d24bda9a6abe50683fe62"},
|
||||
{file = "regex-2024.11.6-cp310-cp310-win32.whl", hash = "sha256:b489578720afb782f6ccf2840920f3a32e31ba28a4b162e13900c3e6bd3f930e"},
|
||||
{file = "regex-2024.11.6-cp310-cp310-win_amd64.whl", hash = "sha256:5071b2093e793357c9d8b2929dfc13ac5f0a6c650559503bb81189d0a3814519"},
|
||||
{file = "regex-2024.11.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5478c6962ad548b54a591778e93cd7c456a7a29f8eca9c49e4f9a806dcc5d638"},
|
||||
{file = "regex-2024.11.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c89a8cc122b25ce6945f0423dc1352cb9593c68abd19223eebbd4e56612c5b7"},
|
||||
{file = "regex-2024.11.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:94d87b689cdd831934fa3ce16cc15cd65748e6d689f5d2b8f4f4df2065c9fa20"},
|
||||
{file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1062b39a0a2b75a9c694f7a08e7183a80c63c0d62b301418ffd9c35f55aaa114"},
|
||||
{file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:167ed4852351d8a750da48712c3930b031f6efdaa0f22fa1933716bfcd6bf4a3"},
|
||||
{file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d548dafee61f06ebdb584080621f3e0c23fff312f0de1afc776e2a2ba99a74f"},
|
||||
{file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2a19f302cd1ce5dd01a9099aaa19cae6173306d1302a43b627f62e21cf18ac0"},
|
||||
{file = "regex-2024.11.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bec9931dfb61ddd8ef2ebc05646293812cb6b16b60cf7c9511a832b6f1854b55"},
|
||||
{file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9714398225f299aa85267fd222f7142fcb5c769e73d7733344efc46f2ef5cf89"},
|
||||
{file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:202eb32e89f60fc147a41e55cb086db2a3f8cb82f9a9a88440dcfc5d37faae8d"},
|
||||
{file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:4181b814e56078e9b00427ca358ec44333765f5ca1b45597ec7446d3a1ef6e34"},
|
||||
{file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:068376da5a7e4da51968ce4c122a7cd31afaaec4fccc7856c92f63876e57b51d"},
|
||||
{file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ac10f2c4184420d881a3475fb2c6f4d95d53a8d50209a2500723d831036f7c45"},
|
||||
{file = "regex-2024.11.6-cp311-cp311-win32.whl", hash = "sha256:c36f9b6f5f8649bb251a5f3f66564438977b7ef8386a52460ae77e6070d309d9"},
|
||||
{file = "regex-2024.11.6-cp311-cp311-win_amd64.whl", hash = "sha256:02e28184be537f0e75c1f9b2f8847dc51e08e6e171c6bde130b2687e0c33cf60"},
|
||||
{file = "regex-2024.11.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:52fb28f528778f184f870b7cf8f225f5eef0a8f6e3778529bdd40c7b3920796a"},
|
||||
{file = "regex-2024.11.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdd6028445d2460f33136c55eeb1f601ab06d74cb3347132e1c24250187500d9"},
|
||||
{file = "regex-2024.11.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:805e6b60c54bf766b251e94526ebad60b7de0c70f70a4e6210ee2891acb70bf2"},
|
||||
{file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b85c2530be953a890eaffde05485238f07029600e8f098cdf1848d414a8b45e4"},
|
||||
{file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb26437975da7dc36b7efad18aa9dd4ea569d2357ae6b783bf1118dabd9ea577"},
|
||||
{file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:abfa5080c374a76a251ba60683242bc17eeb2c9818d0d30117b4486be10c59d3"},
|
||||
{file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b7fa6606c2881c1db9479b0eaa11ed5dfa11c8d60a474ff0e095099f39d98e"},
|
||||
{file = "regex-2024.11.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c32f75920cf99fe6b6c539c399a4a128452eaf1af27f39bce8909c9a3fd8cbe"},
|
||||
{file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:982e6d21414e78e1f51cf595d7f321dcd14de1f2881c5dc6a6e23bbbbd68435e"},
|
||||
{file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a7c2155f790e2fb448faed6dd241386719802296ec588a8b9051c1f5c481bc29"},
|
||||
{file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:149f5008d286636e48cd0b1dd65018548944e495b0265b45e1bffecce1ef7f39"},
|
||||
{file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:e5364a4502efca094731680e80009632ad6624084aff9a23ce8c8c6820de3e51"},
|
||||
{file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0a86e7eeca091c09e021db8eb72d54751e527fa47b8d5787caf96d9831bd02ad"},
|
||||
{file = "regex-2024.11.6-cp312-cp312-win32.whl", hash = "sha256:32f9a4c643baad4efa81d549c2aadefaeba12249b2adc5af541759237eee1c54"},
|
||||
{file = "regex-2024.11.6-cp312-cp312-win_amd64.whl", hash = "sha256:a93c194e2df18f7d264092dc8539b8ffb86b45b899ab976aa15d48214138e81b"},
|
||||
{file = "regex-2024.11.6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a6ba92c0bcdf96cbf43a12c717eae4bc98325ca3730f6b130ffa2e3c3c723d84"},
|
||||
{file = "regex-2024.11.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:525eab0b789891ac3be914d36893bdf972d483fe66551f79d3e27146191a37d4"},
|
||||
{file = "regex-2024.11.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:086a27a0b4ca227941700e0b31425e7a28ef1ae8e5e05a33826e17e47fbfdba0"},
|
||||
{file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bde01f35767c4a7899b7eb6e823b125a64de314a8ee9791367c9a34d56af18d0"},
|
||||
{file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b583904576650166b3d920d2bcce13971f6f9e9a396c673187f49811b2769dc7"},
|
||||
{file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c4de13f06a0d54fa0d5ab1b7138bfa0d883220965a29616e3ea61b35d5f5fc7"},
|
||||
{file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3cde6e9f2580eb1665965ce9bf17ff4952f34f5b126beb509fee8f4e994f143c"},
|
||||
{file = "regex-2024.11.6-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0d7f453dca13f40a02b79636a339c5b62b670141e63efd511d3f8f73fba162b3"},
|
||||
{file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:59dfe1ed21aea057a65c6b586afd2a945de04fc7db3de0a6e3ed5397ad491b07"},
|
||||
{file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b97c1e0bd37c5cd7902e65f410779d39eeda155800b65fc4d04cc432efa9bc6e"},
|
||||
{file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f9d1e379028e0fc2ae3654bac3cbbef81bf3fd571272a42d56c24007979bafb6"},
|
||||
{file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:13291b39131e2d002a7940fb176e120bec5145f3aeb7621be6534e46251912c4"},
|
||||
{file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f51f88c126370dcec4908576c5a627220da6c09d0bff31cfa89f2523843316d"},
|
||||
{file = "regex-2024.11.6-cp313-cp313-win32.whl", hash = "sha256:63b13cfd72e9601125027202cad74995ab26921d8cd935c25f09c630436348ff"},
|
||||
{file = "regex-2024.11.6-cp313-cp313-win_amd64.whl", hash = "sha256:2b3361af3198667e99927da8b84c1b010752fa4b1115ee30beaa332cabc3ef1a"},
|
||||
{file = "regex-2024.11.6-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:3a51ccc315653ba012774efca4f23d1d2a8a8f278a6072e29c7147eee7da446b"},
|
||||
{file = "regex-2024.11.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ad182d02e40de7459b73155deb8996bbd8e96852267879396fb274e8700190e3"},
|
||||
{file = "regex-2024.11.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ba9b72e5643641b7d41fa1f6d5abda2c9a263ae835b917348fc3c928182ad467"},
|
||||
{file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40291b1b89ca6ad8d3f2b82782cc33807f1406cf68c8d440861da6304d8ffbbd"},
|
||||
{file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cdf58d0e516ee426a48f7b2c03a332a4114420716d55769ff7108c37a09951bf"},
|
||||
{file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a36fdf2af13c2b14738f6e973aba563623cb77d753bbbd8d414d18bfaa3105dd"},
|
||||
{file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1cee317bfc014c2419a76bcc87f071405e3966da434e03e13beb45f8aced1a6"},
|
||||
{file = "regex-2024.11.6-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50153825ee016b91549962f970d6a4442fa106832e14c918acd1c8e479916c4f"},
|
||||
{file = "regex-2024.11.6-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ea1bfda2f7162605f6e8178223576856b3d791109f15ea99a9f95c16a7636fb5"},
|
||||
{file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:df951c5f4a1b1910f1a99ff42c473ff60f8225baa1cdd3539fe2819d9543e9df"},
|
||||
{file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:072623554418a9911446278f16ecb398fb3b540147a7828c06e2011fa531e773"},
|
||||
{file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:f654882311409afb1d780b940234208a252322c24a93b442ca714d119e68086c"},
|
||||
{file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:89d75e7293d2b3e674db7d4d9b1bee7f8f3d1609428e293771d1a962617150cc"},
|
||||
{file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:f65557897fc977a44ab205ea871b690adaef6b9da6afda4790a2484b04293a5f"},
|
||||
{file = "regex-2024.11.6-cp38-cp38-win32.whl", hash = "sha256:6f44ec28b1f858c98d3036ad5d7d0bfc568bdd7a74f9c24e25f41ef1ebfd81a4"},
|
||||
{file = "regex-2024.11.6-cp38-cp38-win_amd64.whl", hash = "sha256:bb8f74f2f10dbf13a0be8de623ba4f9491faf58c24064f32b65679b021ed0001"},
|
||||
{file = "regex-2024.11.6-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5704e174f8ccab2026bd2f1ab6c510345ae8eac818b613d7d73e785f1310f839"},
|
||||
{file = "regex-2024.11.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:220902c3c5cc6af55d4fe19ead504de80eb91f786dc102fbd74894b1551f095e"},
|
||||
{file = "regex-2024.11.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5e7e351589da0850c125f1600a4c4ba3c722efefe16b297de54300f08d734fbf"},
|
||||
{file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5056b185ca113c88e18223183aa1a50e66507769c9640a6ff75859619d73957b"},
|
||||
{file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e34b51b650b23ed3354b5a07aab37034d9f923db2a40519139af34f485f77d0"},
|
||||
{file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5670bce7b200273eee1840ef307bfa07cda90b38ae56e9a6ebcc9f50da9c469b"},
|
||||
{file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:08986dce1339bc932923e7d1232ce9881499a0e02925f7402fb7c982515419ef"},
|
||||
{file = "regex-2024.11.6-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:93c0b12d3d3bc25af4ebbf38f9ee780a487e8bf6954c115b9f015822d3bb8e48"},
|
||||
{file = "regex-2024.11.6-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:764e71f22ab3b305e7f4c21f1a97e1526a25ebdd22513e251cf376760213da13"},
|
||||
{file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f056bf21105c2515c32372bbc057f43eb02aae2fda61052e2f7622c801f0b4e2"},
|
||||
{file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:69ab78f848845569401469da20df3e081e6b5a11cb086de3eed1d48f5ed57c95"},
|
||||
{file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:86fddba590aad9208e2fa8b43b4c098bb0ec74f15718bb6a704e3c63e2cef3e9"},
|
||||
{file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:684d7a212682996d21ca12ef3c17353c021fe9de6049e19ac8481ec35574a70f"},
|
||||
{file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a03e02f48cd1abbd9f3b7e3586d97c8f7a9721c436f51a5245b3b9483044480b"},
|
||||
{file = "regex-2024.11.6-cp39-cp39-win32.whl", hash = "sha256:41758407fc32d5c3c5de163888068cfee69cb4c2be844e7ac517a52770f9af57"},
|
||||
{file = "regex-2024.11.6-cp39-cp39-win_amd64.whl", hash = "sha256:b2837718570f95dd41675328e111345f9b7095d821bac435aac173ac80b19983"},
|
||||
{file = "regex-2024.11.6.tar.gz", hash = "sha256:7ab159b063c52a0333c884e4679f8d7a85112ee3078fe3d9004b2dd875585519"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "requests"
|
||||
version = "2.32.3"
|
||||
@@ -3978,6 +3981,25 @@ files = [
|
||||
{file = "soupsieve-2.6.tar.gz", hash = "sha256:e2e68417777af359ec65daac1057404a3c8a5455bb8abc36f1a9866ab1a51abb"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sse-starlette"
|
||||
version = "2.1.3"
|
||||
description = "SSE plugin for Starlette"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "sse_starlette-2.1.3-py3-none-any.whl", hash = "sha256:8ec846438b4665b9e8c560fcdea6bc8081a3abf7942faa95e5a744999d219772"},
|
||||
{file = "sse_starlette-2.1.3.tar.gz", hash = "sha256:9cd27eb35319e1414e3d2558ee7414487f9529ce3b3cf9b21434fd110e017169"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
anyio = "*"
|
||||
starlette = "*"
|
||||
uvicorn = "*"
|
||||
|
||||
[package.extras]
|
||||
examples = ["fastapi"]
|
||||
|
||||
[[package]]
|
||||
name = "stack-data"
|
||||
version = "0.6.3"
|
||||
@@ -4030,13 +4052,13 @@ widechars = ["wcwidth"]
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "2.4.0"
|
||||
version = "2.5.0"
|
||||
description = "ANSI color formatting for output in terminal"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
python-versions = ">=3.9"
|
||||
files = [
|
||||
{file = "termcolor-2.4.0-py3-none-any.whl", hash = "sha256:9297c0df9c99445c2412e832e882a7884038a25617c60cea2ad69488d4040d63"},
|
||||
{file = "termcolor-2.4.0.tar.gz", hash = "sha256:aab9e56047c8ac41ed798fa36d892a37aca6b3e9159f3e0c24bc64a9b3ac7b7a"},
|
||||
{file = "termcolor-2.5.0-py3-none-any.whl", hash = "sha256:37b17b5fc1e604945c2642c872a3764b5d547a48009871aea3edd3afa180afb8"},
|
||||
{file = "termcolor-2.5.0.tar.gz", hash = "sha256:998d8d27da6d48442e8e1f016119076b690d962507531df4890fcd2db2ef8a6f"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
@@ -4538,4 +4560,4 @@ propcache = ">=0.2.0"
|
||||
[metadata]
|
||||
lock-version = "2.0"
|
||||
python-versions = "^3.11"
|
||||
content-hash = "28a2b74bfafa9f93d14d2f8d1fcaffa340db212acce6469d6714d342203ad77f"
|
||||
content-hash = "f8777299520e96d3c5d14f5a216b36ed869a6429434f1366761cf1cdfe07ef52"
|
||||
|
||||
+11
-3
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "agentic_security"
|
||||
version = "0.5.1"
|
||||
version = "0.7.2"
|
||||
description = "Agentic LLM vulnerability scanner"
|
||||
authors = ["Alexander Miasoiedov <msoedov@gmail.com>"]
|
||||
maintainers = ["Alexander Miasoiedov <msoedov@gmail.com>"]
|
||||
@@ -52,8 +52,10 @@ sentry_sdk = "^2.22.0"
|
||||
orjson = "^3.10"
|
||||
pyfiglet = "^1.0.2"
|
||||
termcolor = "^2.4.0"
|
||||
mcp = "^1.4.1"
|
||||
|
||||
# garak = { version = "*", optional = true }
|
||||
pytest-xdist = "3.6.1"
|
||||
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
@@ -68,7 +70,7 @@ pytest-mock = "^3.14.0"
|
||||
black = ">=24.10,<26.0"
|
||||
mypy = "^1.12.0"
|
||||
pre-commit = "^4.0.1"
|
||||
huggingface-hub = ">=0.25.1,<0.29.0"
|
||||
huggingface-hub = ">=0.25.1,<0.30.0"
|
||||
|
||||
# Docs
|
||||
mkdocs = ">=1.4.2"
|
||||
@@ -86,7 +88,13 @@ build-backend = "poetry.core.masonry.api"
|
||||
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
addopts = "--durations=5 -m 'not slow'"
|
||||
addopts = "--durations=5 -m 'not slow' -n 3"
|
||||
asyncio_mode = "auto"
|
||||
asyncio_default_fixture_loop_scope = "function"
|
||||
markers = "slow: marks tests as slow"
|
||||
|
||||
[project]
|
||||
# MCP requires the following fields to be present in the pyproject.toml file
|
||||
name = "agentic_security"
|
||||
version = "1.0.0"
|
||||
requires-python = ">=3.11"
|
||||
|
||||
@@ -1,8 +1,17 @@
|
||||
import os
|
||||
|
||||
import pytest
|
||||
from cache_to_disk import delete_old_disk_caches
|
||||
|
||||
from agentic_security.logutils import logger
|
||||
|
||||
|
||||
def pytest_runtest_setup(item):
|
||||
if "slow" in item.keywords and not os.getenv("RUN_SLOW_TESTS"):
|
||||
pytest.skip("Skipping slow test")
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True, scope="session")
|
||||
def setup_delete_old_disk_caches():
|
||||
logger.info("delete_old_disk_caches")
|
||||
delete_old_disk_caches()
|
||||
|
||||
@@ -7,6 +7,7 @@ import pytest
|
||||
|
||||
from agentic_security.primitives import Scan
|
||||
from agentic_security.probe_actor.fuzzer import (
|
||||
FuzzerState,
|
||||
generate_prompts,
|
||||
perform_many_shot_scan,
|
||||
perform_single_shot_scan,
|
||||
@@ -207,9 +208,7 @@ class TestProcessPrompt(unittest.IsolatedAsyncioTestCase):
|
||||
prompt="test prompt",
|
||||
tokens=0,
|
||||
module_name="module_a",
|
||||
refusals=[],
|
||||
errors=[],
|
||||
outputs=[],
|
||||
fuzzer_state=FuzzerState(),
|
||||
)
|
||||
|
||||
self.assertEqual(tokens, 3) # Tokens from "Valid response text"
|
||||
@@ -226,20 +225,17 @@ class TestProcessPrompt(unittest.IsolatedAsyncioTestCase):
|
||||
)
|
||||
)
|
||||
|
||||
refusals = []
|
||||
outputs = []
|
||||
fuzzer_state = FuzzerState()
|
||||
tokens, refusal = await process_prompt(
|
||||
request_factory=mock_request_factory,
|
||||
prompt="test prompt",
|
||||
tokens=0,
|
||||
module_name="module_a",
|
||||
refusals=refusals,
|
||||
errors=[],
|
||||
outputs=outputs,
|
||||
fuzzer_state=fuzzer_state,
|
||||
)
|
||||
|
||||
self.assertEqual(tokens, 3) # Tokens from "Response indicating refusal"
|
||||
self.assertFalse(refusal)
|
||||
# self.assertFalse(fuzzer_state.refusals)
|
||||
|
||||
async def test_http_error_response(self):
|
||||
mock_request_factory = Mock()
|
||||
@@ -252,15 +248,13 @@ class TestProcessPrompt(unittest.IsolatedAsyncioTestCase):
|
||||
)
|
||||
)
|
||||
|
||||
refusals = []
|
||||
fuzzer_state = FuzzerState()
|
||||
await process_prompt(
|
||||
request_factory=mock_request_factory,
|
||||
prompt="test prompt",
|
||||
tokens=0,
|
||||
module_name="module_a",
|
||||
refusals=refusals,
|
||||
errors=[],
|
||||
outputs=[],
|
||||
fuzzer_state=fuzzer_state,
|
||||
)
|
||||
|
||||
async def test_request_error(self):
|
||||
@@ -269,18 +263,14 @@ class TestProcessPrompt(unittest.IsolatedAsyncioTestCase):
|
||||
side_effect=httpx.RequestError("Connection error")
|
||||
)
|
||||
|
||||
errors = []
|
||||
fuzzer_state = FuzzerState()
|
||||
tokens, refusal = await process_prompt(
|
||||
request_factory=mock_request_factory,
|
||||
prompt="test prompt",
|
||||
tokens=0,
|
||||
module_name="module_a",
|
||||
refusals=[],
|
||||
errors=errors,
|
||||
outputs=[],
|
||||
fuzzer_state=fuzzer_state,
|
||||
)
|
||||
|
||||
self.assertEqual(tokens, 0)
|
||||
self.assertTrue(refusal)
|
||||
self.assertEqual(len(errors), 1)
|
||||
self.assertIn("Connection error", errors[0][3])
|
||||
+1
-1
@@ -3,7 +3,7 @@ from unittest.mock import patch
|
||||
import pandas as pd
|
||||
import pytest
|
||||
|
||||
from .model import RefusalClassifier
|
||||
from agentic_security.refusal_classifier.model import RefusalClassifier
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -1,6 +1,6 @@
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from ..app import app
|
||||
from agentic_security.app import app
|
||||
|
||||
|
||||
def test_health_check():
|
||||
@@ -5,10 +5,10 @@ import httpx
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from ..app import app
|
||||
from ..primitives import Probe
|
||||
from ..probe_actor.refusal import REFUSAL_MARKS
|
||||
from ..probe_data import REGISTRY
|
||||
from agentic_security.app import app
|
||||
from agentic_security.primitives import Probe
|
||||
from agentic_security.probe_actor.refusal import REFUSAL_MARKS
|
||||
from agentic_security.probe_data import REGISTRY
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
@@ -4,7 +4,7 @@ from unittest.mock import patch
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from .report import router
|
||||
from agentic_security.routes.report import router
|
||||
|
||||
client = TestClient(router)
|
||||
|
||||
@@ -4,8 +4,8 @@ import pytest
|
||||
from fastapi import HTTPException
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from ..primitives import Settings
|
||||
from .static import get_static_file, router
|
||||
from agentic_security.primitives import Settings
|
||||
from agentic_security.routes.static import get_static_file, router
|
||||
|
||||
client = TestClient(router)
|
||||
|
||||
@@ -8,7 +8,7 @@ import time
|
||||
import pytest
|
||||
|
||||
import agentic_security.test_spec_assets as test_spec_assets
|
||||
from agentic_security.lib import AgenticSecurity
|
||||
from agentic_security.lib import SecurityScanner
|
||||
|
||||
|
||||
def has_module(module_name):
|
||||
@@ -57,7 +57,7 @@ class TestLibraryLevel:
|
||||
maxBudget = 1000000
|
||||
max_th = 0.3
|
||||
datasets = make_test_registry()
|
||||
result = AgenticSecurity.scan(llmSpec, maxBudget, datasets, max_th)
|
||||
result = SecurityScanner.scan(llmSpec, maxBudget, datasets, max_th)
|
||||
assert isinstance(result, dict)
|
||||
print(result)
|
||||
assert len(result) in [0, 1]
|
||||
@@ -68,7 +68,7 @@ class TestLibraryLevel:
|
||||
maxBudget = 1000
|
||||
max_th = 0.3
|
||||
datasets = make_test_registry()
|
||||
result = AgenticSecurity.scan(
|
||||
result = SecurityScanner.scan(
|
||||
llmSpec, maxBudget, datasets, max_th, enableMultiStepAttack=True
|
||||
)
|
||||
assert isinstance(result, dict)
|
||||
@@ -93,7 +93,7 @@ class TestLibraryLevel:
|
||||
"opts": {"port": 9094},
|
||||
},
|
||||
]
|
||||
result = AgenticSecurity.scan(llmSpec, maxBudget, datasets, max_th)
|
||||
result = SecurityScanner.scan(llmSpec, maxBudget, datasets, max_th)
|
||||
assert isinstance(result, dict)
|
||||
print(result)
|
||||
assert len(result) in [0, 1]
|
||||
@@ -120,7 +120,7 @@ class TestLibraryLevel:
|
||||
"modality": "text",
|
||||
},
|
||||
]
|
||||
result = AgenticSecurity.scan(llmSpec, maxBudget, datasets, max_th)
|
||||
result = SecurityScanner.scan(llmSpec, maxBudget, datasets, max_th)
|
||||
assert isinstance(result, dict)
|
||||
print(result)
|
||||
assert len(result) in [0, 1]
|
||||
@@ -148,7 +148,7 @@ class TestLibraryLevel:
|
||||
"modality": "text",
|
||||
},
|
||||
]
|
||||
result = AgenticSecurity.scan(llmSpec, maxBudget, datasets, max_th)
|
||||
result = SecurityScanner.scan(llmSpec, maxBudget, datasets, max_th)
|
||||
assert isinstance(result, dict)
|
||||
print(result)
|
||||
assert len(result) in [0, 1]
|
||||
@@ -164,10 +164,10 @@ class TestEntrypointCI:
|
||||
temp_path = os.path.join(tmpdir, "custom_agesec.toml")
|
||||
|
||||
# Override default_path to the temporary path
|
||||
AgenticSecurity.default_path = temp_path
|
||||
SecurityScanner.default_path = temp_path
|
||||
|
||||
# Generate the default configuration
|
||||
security = AgenticSecurity()
|
||||
security = SecurityScanner()
|
||||
security.generate_default_settings()
|
||||
|
||||
# Check that the config file was created at the temporary path
|
||||
@@ -189,17 +189,17 @@ class TestEntrypointCI:
|
||||
temp_path = os.path.join(tmpdir, "custom_agesec.toml")
|
||||
|
||||
# Override default_path to the temporary path
|
||||
AgenticSecurity.default_path = temp_path
|
||||
SecurityScanner.default_path = temp_path
|
||||
|
||||
# Generate the default configuration
|
||||
security = AgenticSecurity()
|
||||
security = SecurityScanner()
|
||||
security.generate_default_settings()
|
||||
|
||||
# Load the generated configuration
|
||||
AgenticSecurity.load_config(temp_path)
|
||||
SecurityScanner.load_config(temp_path)
|
||||
|
||||
# Validate loaded configuration
|
||||
config = AgenticSecurity.config
|
||||
config = SecurityScanner.config
|
||||
assert (
|
||||
config["general"]["maxBudget"] == 1000000
|
||||
), "maxBudget should be 1000000"
|
||||
@@ -0,0 +1,12 @@
|
||||
import pytest
|
||||
|
||||
from agentic_security.mcp.client import run
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_mcp_echo_tool():
|
||||
"""Test the echo tool functionality"""
|
||||
prompts, resources, tools = await run()
|
||||
assert prompts
|
||||
assert resources
|
||||
assert tools
|
||||
Reference in New Issue
Block a user