Skip to content

Commit 34cb465

Browse files
authored
Merge pull request trustyai-explainability#140 from trustyai-explainability/claude-md
Add CLAUDE.md
2 parents 69f1462 + daa2462 commit 34cb465

File tree

2 files changed

+115
-10
lines changed

2 files changed

+115
-10
lines changed

CLAUDE.md

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
# AI Agent Context
2+
3+
## What This Repo Is
4+
5+
This repo contains **two independent integrations** for running
6+
[Garak](https://github.com/NVIDIA/garak) LLM red-teaming scans. They share
7+
core logic but serve different orchestration surfaces:
8+
9+
1. **Llama Stack Provider** — An out-of-tree eval provider for the
10+
[Llama Stack](https://llamastack.github.io/) framework. Exposes garak
11+
through the Llama Stack `benchmarks.register` / `eval.run_eval` API.
12+
13+
2. **Eval-Hub Adapter** — A `FrameworkAdapter` for the eval-hub SDK.
14+
Completely independent of Llama Stack. Used by the RHOAI evaluation
15+
platform to orchestrate garak scans via K8s jobs.
16+
17+
## Four Execution Modes
18+
19+
```
20+
Llama Stack Eval-Hub
21+
(Llama Stack API) (eval-hub SDK)
22+
┌────────┬────────┐ ┌────────┬────────┐
23+
│ Inline │ Remote │ │ Simple │ KFP │
24+
│ │ KFP │ │ (pod) │ (pod + │
25+
│ │ │ │ │ KFP) │
26+
└────────┴────────┘ └────────┴────────┘
27+
local KFP in-pod K8s job
28+
garak pipelines garak submits to
29+
KFP, polls
30+
```
31+
32+
| Mode | Code Location | How Garak Runs | Intents Support |
33+
|------|--------------|----------------|-----------------|
34+
| **Llama Stack Inline** | `inline/` | Locally in the Llama Stack server process | No |
35+
| **Llama Stack Remote KFP** | `remote/` | As KFP pipeline steps on Kubernetes | **Yes** |
36+
| **Eval-Hub Simple** | `evalhub/` (simple mode) | Directly in the eval-hub K8s job pod | No |
37+
| **Eval-Hub KFP** | `evalhub/` (KFP mode) | K8s job submits to KFP, polls status, pulls artifacts via S3 | **Yes** |
38+
39+
**Intents** is a key upcoming feature — it uses SDG (synthetic data generation),
40+
TAPIntent probes, and MulticlassJudge detectors to test model behavior against
41+
policy taxonomies. Only the two KFP-based modes support it because it requires
42+
the six-step pipeline (`core/pipeline_steps.py`) running as KFP components.
43+
44+
## Code Layout
45+
46+
```
47+
src/llama_stack_provider_trustyai_garak/
48+
├── core/ # Shared logic used by ALL modes
49+
│ ├── config_resolution.py # Deep-merge user overrides onto benchmark profiles
50+
│ ├── command_builder.py # Build garak CLI args for OpenAI-compatible endpoints
51+
│ ├── garak_runner.py # Subprocess runner for garak CLI
52+
│ └── pipeline_steps.py # Six-step pipeline (validate→taxonomy→SDG→prompts→scan→parse)
53+
54+
├── inline/ # Llama Stack Inline mode
55+
│ ├── garak_eval.py # Async adapter wrapping garak subprocess
56+
│ └── provider.py # Provider spec with pip dependencies
57+
58+
├── remote/ # Llama Stack Remote KFP mode
59+
│ ├── garak_remote_eval.py # Async adapter managing KFP job lifecycle
60+
│ └── kfp_utils/ # KFP pipeline DAG and @dsl.component steps
61+
62+
├── evalhub/ # Eval-Hub integration (NO Llama Stack dependency)
63+
│ ├── garak_adapter.py # FrameworkAdapter: benchmark resolution, intents overlay, callbacks
64+
│ ├── kfp_adapter.py # KFP-specific adapter (forces KFP execution mode)
65+
│ ├── kfp_pipeline.py # Eval-hub KFP pipeline with S3 artifact flow
66+
│ └── s3_utils.py # S3/Data Connection client
67+
68+
├── base_eval.py # Shared Llama Stack eval lifecycle (NOT used by eval-hub)
69+
├── garak_command_config.py # Pydantic models for garak YAML config
70+
├── intents.py # Policy taxonomy dataset loading (SDG/intents flows)
71+
├── sdg.py # Synthetic data generation via sdg-hub
72+
├── result_utils.py # Parse garak outputs, TBSA scoring, HTML reports
73+
└── resources/ # Jinja2 templates and Vega chart specs
74+
```
75+
76+
## Key Conventions
77+
78+
- **Config merging**: User overrides are deep-merged onto benchmark profiles via
79+
`deep_merge_dicts` in `core/config_resolution.py`. Only leaf values are replaced.
80+
- **Intents model overlay**: When `intents_models` is provided, model endpoints
81+
are applied using `x.get("key") or default` pattern — fills empty slots but
82+
preserves user-configured values. `api_key` is always forced to `__FROM_ENV__`
83+
(K8s Secret injection).
84+
- **Benchmark profiles**: Predefined configs live in `base_eval.py` (Llama Stack)
85+
and `evalhub/garak_adapter.py` (eval-hub). The `intents` profile is the most
86+
complex — it includes TAPIntent, MulticlassJudge, and SDG configuration.
87+
- **Provider specs**: `inline/provider.py` and `remote/provider.py` define Llama
88+
Stack provider specs. `pip_packages` is auto-populated from `get_garak_version()`.
89+
90+
## Build & Install
91+
92+
```bash
93+
pip install -e . # Core (Llama Stack remote mode)
94+
pip install -e ".[inline]" # With garak for local scans
95+
pip install -e ".[dev]" # Dev (tests + ruff + pre-commit)
96+
```
97+
98+
## Running Tests
99+
100+
```bash
101+
make test # All tests (no cluster/GPU/network needed)
102+
make coverage # With coverage report
103+
make lint # ruff check
104+
```
105+
106+
Tests are 100% unit tests. Garak is mocked — it does not need to be installed.
107+
108+
## Debugging
109+
110+
- `GARAK_SCAN_DIR` — controls where scan artifacts land
111+
- `LOG_LEVEL=DEBUG` — verbose eval-hub adapter logging
112+
- `scan.log` in scan directory — garak subprocess output
113+
- `__FROM_ENV__` in configs — placeholder for K8s Secret api_key injection

tests/test_config.py

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -447,7 +447,7 @@ def test_override_nested_dict_leaf_preserves_sibling_keys(self):
447447
"system_prompt": "Default prompt",
448448
"score_key": "complied",
449449
"confidence_cutoff": 70,
450-
}
450+
},
451451
}
452452
}
453453
}
@@ -482,15 +482,7 @@ def test_override_does_not_mutate_base(self):
482482

483483
def test_adding_new_key_at_deep_level(self):
484484
base = {"plugins": {"detectors": {"judge": {"detector_model_name": "m1"}}}}
485-
override = {
486-
"plugins": {
487-
"detectors": {
488-
"judge": {
489-
"MulticlassJudge": {"system_prompt": "Added later"}
490-
}
491-
}
492-
}
493-
}
485+
override = {"plugins": {"detectors": {"judge": {"MulticlassJudge": {"system_prompt": "Added later"}}}}}
494486
result = deep_merge_dicts(base, override)
495487
judge = result["plugins"]["detectors"]["judge"]
496488
assert judge["detector_model_name"] == "m1"

0 commit comments

Comments
 (0)