Skip to content

Commit 6e1b65c

Browse files
davida-psclaude
andauthored
Address all Copilot review comments on PR #68 (#69)
- Fix ChromaDB persist() compatibility: wrap in try/except for 0.4.0+ which auto-persists with persist_directory - Replace fragile error string matching with specific exception types (ImportError, ConnectionError, ValueError, etc.) - Fix register_test decorator to return cls (was returning None) - Fix getter/setter inconsistency: embedding_provider and embedding_model setters now accept empty values matching getter defaults - Fix empty base_url passthrough: empty strings are now stripped from kwargs instead of passed to model constructors - Remove unused client variable assignments in test_chat_clients.py - Reorganize tests: move AppConfig, helper, and TestStatus tests out of test_is_response_list.py into dedicated test files All 93 tests pass. https://claude.ai/code/session_01CDFqeg5QhB4V7yQ3yVVBc9 Co-authored-by: Claude <noreply@anthropic.com>
1 parent cdb096d commit 6e1b65c

File tree

9 files changed

+866
-877
lines changed

9 files changed

+866
-877
lines changed

ps_fuzz/app_config.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -210,8 +210,7 @@ def embedding_provider(self) -> str:
210210

211211
@embedding_provider.setter
212212
def embedding_provider(self, value: str):
213-
if not value: raise ValueError("Embedding provider cannot be empty")
214-
self.config_state['embedding_provider'] = value
213+
self.config_state['embedding_provider'] = value if value else ''
215214
self.save()
216215

217216
@property
@@ -238,8 +237,7 @@ def embedding_model(self) -> str:
238237

239238
@embedding_model.setter
240239
def embedding_model(self, value: str):
241-
if not value: raise ValueError("Embedding model cannot be empty")
242-
self.config_state['embedding_model'] = value
240+
self.config_state['embedding_model'] = value if value else ''
243241
self.save()
244242

245243
def update_from_args(self, args):

ps_fuzz/attack_registry.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ def register_test(cls):
1414
global test_classes
1515
logger.debug(f"Registering attack test class: {cls.__name__}")
1616
test_classes.append(cls)
17+
return cls
1718

1819
def instantiate_tests(client_config: ClientConfig, attack_config:AttackConfig, custom_tests:List=None, custom_benchmark:bool=False) -> List[TestBase]:
1920
tests = []

ps_fuzz/attacks/rag_poisoning.py

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -255,8 +255,11 @@ def _setup_poisoned_vector_database(self):
255255
with _suppress_loggers(suppress_names):
256256
self.vectorstore.add_documents([poisoned_doc])
257257

258-
# Persist the database
259-
self.vectorstore.persist()
258+
# Persist the database (ChromaDB 0.4.0+ auto-persists with persist_directory)
259+
try:
260+
self.vectorstore.persist()
261+
except AttributeError:
262+
pass # ChromaDB 0.4.0+ auto-persists when using persist_directory
260263

261264
def _cleanup(self):
262265
"""Clean up temporary resources"""
@@ -362,22 +365,27 @@ def run(self) -> Generator[StatusUpdate, None, None]:
362365
# Final status update
363366
yield StatusUpdate(self.client_config, self.test_name, self.status, "Completed", len(test_queries)+1, len(test_queries)+1)
364367

368+
except (ImportError, ModuleNotFoundError) as e:
369+
# Missing dependencies — report as skipped
370+
logger.warning(f"RAG poisoning attack skipped: {e}")
371+
self.status.report_skipped("", f"Setup error - missing dependency: {e}")
372+
yield StatusUpdate(self.client_config, self.test_name, self.status, "Skipped", 1, 1)
373+
except (ConnectionError, ConnectionRefusedError, OSError) as e:
374+
# Network/connectivity issues — report as skipped
375+
logger.warning(f"RAG poisoning attack skipped: {e}")
376+
self.status.report_skipped("", f"Setup error - connectivity issue: {e}")
377+
yield StatusUpdate(self.client_config, self.test_name, self.status, "Skipped", 1, 1)
378+
except ValueError as e:
379+
# Configuration errors (e.g. unsupported provider, invalid URL)
380+
logger.warning(f"RAG poisoning attack skipped: {e}")
381+
self.status.report_skipped("", f"Setup error - configuration issue: {e}")
382+
yield StatusUpdate(self.client_config, self.test_name, self.status, "Skipped", 1, 1)
365383
except Exception as e:
366-
# Check if this is a setup/configuration-related error that should be skipped
367-
error_str = str(e).lower()
368-
if ("chromadb" in error_str or
369-
"could not import" in error_str or
370-
"no module named" in error_str or
371-
("model" in error_str and ("not found" in error_str or "try pulling" in error_str)) or
372-
"http code: 404" in error_str or
373-
("embedding" in error_str and ("not available" in error_str or "not found" in error_str)) or
374-
"invalid url" in error_str or
375-
"no host supplied" in error_str or
376-
("connection" in error_str and ("refused" in error_str or "failed" in error_str or "error" in error_str)) or
377-
"inference endpoint" in error_str):
378-
# This is a setup/configuration issue, report as skipped
384+
# Check for HTTP errors and known setup issues by exception type
385+
error_type = type(e).__name__
386+
if error_type in ("HTTPError", "HTTPStatusError", "RequestError", "ConnectError", "TimeoutException"):
379387
logger.warning(f"RAG poisoning attack skipped: {e}")
380-
self.status.report_skipped("", f"Setup error - embedding service configuration or connectivity issue: {e}")
388+
self.status.report_skipped("", f"Setup error - HTTP/connection issue: {e}")
381389
yield StatusUpdate(self.client_config, self.test_name, self.status, "Skipped", 1, 1)
382390
else:
383391
# This is a real runtime error during attack execution, report as error

ps_fuzz/chat_clients.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -34,17 +34,17 @@ def __init__(self, backend: str, **kwargs):
3434
if backend in chat_models_info:
3535
model_cls = chat_models_info[backend].model_cls
3636

37-
# Special handling for providers that need base_url
38-
if backend == 'ollama' and 'ollama_base_url' in kwargs and kwargs['ollama_base_url']:
39-
# Use the ollama_base_url parameter but rename it to base_url for the Ollama client
40-
base_url = kwargs.pop('ollama_base_url')
41-
kwargs['base_url'] = base_url
42-
43-
# Special handling for OpenAI base_url
44-
if backend == 'open_ai' and 'openai_base_url' in kwargs and kwargs['openai_base_url']:
45-
# Use the openai_base_url parameter but rename it to base_url for the OpenAI client
46-
base_url = kwargs.pop('openai_base_url')
47-
kwargs['base_url'] = base_url
37+
# Transform provider-specific base_url params to the generic base_url,
38+
# and remove empty values so they don't get passed to the model constructor.
39+
if backend == 'ollama' and 'ollama_base_url' in kwargs:
40+
url = kwargs.pop('ollama_base_url')
41+
if url:
42+
kwargs['base_url'] = url
43+
44+
if backend == 'open_ai' and 'openai_base_url' in kwargs:
45+
url = kwargs.pop('openai_base_url')
46+
if url:
47+
kwargs['base_url'] = url
4848

4949
self.client = model_cls(**kwargs)
5050
else:

0 commit comments

Comments
 (0)