|
8 | 8 | from typing import Optional |
9 | 9 | import requests |
10 | 10 | from bs4 import BeautifulSoup |
11 | | -from openai import OpenAI |
| 11 | +from openai import OpenAI, AsyncOpenAI |
12 | 12 |
|
13 | 13 | from ..datamodels.report import Report, ReportMetadata |
14 | 14 | from ..datamodels.components import ( |
@@ -50,6 +50,7 @@ def __init__(self, api_key: Optional[str] = None, model: str = "gpt-4o-mini"): |
50 | 50 | ) |
51 | 51 | self.model = model |
52 | 52 | self.client = OpenAI(api_key=self.api_key) |
| 53 | + self.async_client = AsyncOpenAI(api_key=self.api_key) |
53 | 54 |
|
54 | 55 | def scrape_url(self, url: str) -> dict: |
55 | 56 | """ |
@@ -114,19 +115,79 @@ def _create_ai_prompt(self, scraped_data: dict) -> str: |
114 | 115 | """ |
115 | 116 | prompt = f"""You are an AI security expert tasked with analyzing web content about AI/ML vulnerabilities, incidents, or security issues and extracting structured information to create an AVID (AI Vulnerability Database) report. |
116 | 117 |
|
117 | | -The AVID report structure includes: |
118 | | -- **report_id**: A unique identifier (generate one like "AVID-YYYY-R-XXXX") |
119 | | -- **affects**: Information about affected artifacts including: |
120 | | - - developer: List of developers/organizations |
121 | | - - deployer: List of deployers/organizations |
122 | | - - artifacts: List of artifacts with type (MUST be one of: "Model", "Dataset", "System") and name |
123 | | -- **problemtype**: Problem description with: |
124 | | - - classof: Class (MUST be one of: "AIID Incident", "ATLAS Case Study", "CVE Entry", "LLM Evaluation", "Third-party Report", "Undefined"). Default to "Third-party Report" if unsure. |
125 | | - - type: Type (MUST be one of: "Issue", "Advisory", "Measurement", "Detection") |
126 | | - - description: language ("eng") and value (description text) |
127 | | -- **description**: High-level description with lang and value |
128 | | -- **references**: List of references with label and url (MUST include the source URL) |
129 | | -- **reported_date**: Date in YYYY-MM-DD format (use today's date if not specified) |
| 118 | +The AVID Report follows this exact schema: |
| 119 | +
|
| 120 | +```json |
| 121 | +{{ |
| 122 | + "data_type": "AVID", |
| 123 | + "data_version": "string (optional)", |
| 124 | + "metadata": {{ |
| 125 | + "report_id": "string (required, format: AVID-YYYY-R-XXXX)" |
| 126 | + }}, |
| 127 | + "affects": {{ |
| 128 | + "developer": ["list of developer organizations of the model/system involved"], |
| 129 | + "deployer": ["list of deployer organizations"], |
| 130 | + "artifacts": [ |
| 131 | + {{ |
| 132 | + "type": "Model|Dataset|System (required)", |
| 133 | + "name": "artifact name (required)" |
| 134 | + }} |
| 135 | + ] |
| 136 | + }}, |
| 137 | + "problemtype": {{ |
| 138 | + "classof": "AIID Incident|ATLAS Case Study|CVE Entry|LLM Evaluation|Third-party Report|Undefined (required, default: Third-party Report)", |
| 139 | + "type": "Issue|Advisory|Measurement|Detection (optional)", |
| 140 | + "description": {{ |
| 141 | + "lang": "eng", |
| 142 | + "value": "description text" |
| 143 | + }} |
| 144 | + }}, |
| 145 | + "metrics": [ |
| 146 | + {{ |
| 147 | + "name": "metric name", |
| 148 | + "detection_method": {{ |
| 149 | + "type": "Significance Test|Static Threshold", |
| 150 | + "name": "method name" |
| 151 | + }}, |
| 152 | + "results": {{}} or [] |
| 153 | + }} |
| 154 | + ], |
| 155 | + "references": [ |
| 156 | + {{ |
| 157 | + "label": "reference label", |
| 158 | + "url": "reference url (MUST include source URL)" |
| 159 | + }} |
| 160 | + ], |
| 161 | + "description": {{ |
| 162 | + "lang": "eng", |
| 163 | + "value": "high-level description" |
| 164 | + }}, |
| 165 | + "impact": {{ |
| 166 | + "avid": {{ |
| 167 | + "risk_domain": ["list of risk domains"], |
| 168 | + "sep_view": ["list of SEP taxonomy IDs"], |
| 169 | + "lifecycle_view": ["list of lifecycle stage IDs"], |
| 170 | + "taxonomy_version": "version string" |
| 171 | + }}, |
| 172 | + "atlas": [ |
| 173 | + {{ |
| 174 | + "tactic": "tactic name", |
| 175 | + "technique": "technique name", |
| 176 | + "subtechnique": "subtechnique name" |
| 177 | + }} |
| 178 | + ] |
| 179 | + }}, |
| 180 | + "credit": [ |
| 181 | + {{ |
| 182 | + "lang": "eng", |
| 183 | + "value": "credited person or organization" |
| 184 | + }} |
| 185 | + ], |
| 186 | + "reported_date": "YYYY-MM-DD" |
| 187 | +}} |
| 188 | +``` |
| 189 | +
|
| 190 | +All fields except those marked as required are optional. Omit fields if information is not available. |
130 | 191 |
|
131 | 192 | Here is the web content to analyze: |
132 | 193 |
|
@@ -239,10 +300,50 @@ def _build_report_from_json(self, data: dict) -> Report: |
239 | 300 | artifacts = [] |
240 | 301 | if "artifacts" in affects_data: |
241 | 302 | for artifact_data in affects_data["artifacts"]: |
| 303 | + artifact_type = ArtifactTypeEnum(artifact_data["type"]) |
| 304 | + artifact_name = artifact_data["name"] |
| 305 | + |
| 306 | + # Reclassify models as systems based on provider-specific rules |
| 307 | + if artifact_type == ArtifactTypeEnum.model: |
| 308 | + artifact_name_lower = artifact_name.lower() |
| 309 | + |
| 310 | + # OpenAI: All LLMs are systems |
| 311 | + if "openai" in artifact_name_lower: |
| 312 | + artifact_type = ArtifactTypeEnum.system |
| 313 | + |
| 314 | + # Anthropic: All LLMs are systems |
| 315 | + elif "anthropic" in artifact_name_lower: |
| 316 | + artifact_type = ArtifactTypeEnum.system |
| 317 | + |
| 318 | + # Google: Gemini series are systems, Gemma are models |
| 319 | + elif "google" in artifact_name_lower or "gemini" in artifact_name_lower: |
| 320 | + if "gemini" in artifact_name_lower: |
| 321 | + artifact_type = ArtifactTypeEnum.system |
| 322 | + # gemma remains as model |
| 323 | + |
| 324 | + # Cohere: All except Command R and Aya are systems |
| 325 | + elif "cohere" in artifact_name_lower: |
| 326 | + if "command r" not in artifact_name_lower and "aya" not in artifact_name_lower: |
| 327 | + artifact_type = ArtifactTypeEnum.system |
| 328 | + |
| 329 | + # Mistral: Large, Medium, Moderation, Embed are systems |
| 330 | + elif "mistral" in artifact_name_lower: |
| 331 | + if any(variant in artifact_name_lower for variant in ["large", "medium", "moderation", "embed"]): |
| 332 | + artifact_type = ArtifactTypeEnum.system |
| 333 | + |
| 334 | + # Alibaba: Qwen Max and Turbo are systems |
| 335 | + elif "alibaba" in artifact_name_lower or "qwen" in artifact_name_lower: |
| 336 | + if "qwen max" in artifact_name_lower or "qwen turbo" in artifact_name_lower: |
| 337 | + artifact_type = ArtifactTypeEnum.system |
| 338 | + |
| 339 | + # Meta, Twitter/X, Mozilla remain as systems (closed APIs) |
| 340 | + elif any(provider in artifact_name_lower for provider in ["twitter", "grok"]): |
| 341 | + artifact_type = ArtifactTypeEnum.system |
| 342 | + |
242 | 343 | artifacts.append( |
243 | 344 | Artifact( |
244 | | - type=ArtifactTypeEnum(artifact_data["type"]), |
245 | | - name=artifact_data["name"], |
| 345 | + type=artifact_type, |
| 346 | + name=artifact_name, |
246 | 347 | ) |
247 | 348 | ) |
248 | 349 | affects = Affects( |
|
0 commit comments