Skip to content

Commit 4a7fc6d

Browse files
committed
Clarify imports and quiet REVIEW logs
1 parent 316e2e1 commit 4a7fc6d

9 files changed

Lines changed: 54 additions & 2 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ All notable changes to the `unplug-ai` SDK.
1414

1515
### Fixed
1616

17+
- Agent usability: `Guard.init()` docstring no longer claims auto-instrumentation; docs standardize on `from unplug import ...` for apps and `unplug.api.*` for server/MCP dependents; routine `REVIEW` pipeline outcomes log at INFO instead of WARNING
1718
- ML inference hardening: BIOES decode no longer crashes on checkpoints without `*-INJ` labels; label maps are validated at load with a clear `ModelError`; forced torch devices are validated (`ConfigError`); `ModelProvider`/`SpanInferenceModel` load is thread-safe; ML modules log device/tokenizer fallbacks and import torch via `unplug.optional.ml` helpers
1819
- Model store hardening: corrupt manifests no longer crash Guard or `unplug-models`; checkpoint validation requires weight files; atomic manifest writes and download swaps preserve existing installs on failure; `list_status` correctly reports stale revisions as upgrade-available; invalid `UNPLUG_MODEL_PATH` logs a warning; CLI download errors distinguish missing ML extras from network/repo failures
1920

‎README.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ uv sync && uv pip install -e ".[ml]"
3030

3131
## Quickstart
3232

33+
App and agent code imports from the top-level package (`from unplug import Guard`).
34+
Server/MCP integrations use `unplug.api.*` for wire types and facades — see
35+
[`sdk/docs/PUBLIC_API.md`](sdk/docs/PUBLIC_API.md). Do not use `unplug.core.*`.
36+
3337
```python
3438
from unplug import Guard
3539
from unplug.api.enums import Source

‎sdk/README.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ pip install unplug-ai # regex-only core, zero ML deps
3030
pip install "unplug-ai[ml]" # add the ML span model
3131
```
3232

33+
**Imports:** App and agent code uses `from unplug import Guard` (and other top-level
34+
exports). Server/MCP dependents that need wire types or facades use `unplug.api.*`
35+
— see [`docs/PUBLIC_API.md`](docs/PUBLIC_API.md). Do not import from `unplug.core.*`.
36+
3337
Or from source:
3438

3539
```bash

‎sdk/docs/AGENT_ACTIONS.md‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
Every Unplug scan returns an **`Action`**. Agent hosts and integration adapters must handle each action differently — especially **`REVIEW`**, which is not the same as **`BLOCK`**.
44

5+
Examples below use `from unplug import Guard` for the host and `from unplug.api.*` only
6+
where wire types or enums are needed. Do not import from `unplug.core.*`.
7+
58
## Decision table
69

710
| `result.action` | `result.safe` | `HookDecision.allowed` | Agent host should |

‎sdk/docs/GETTING_STARTED.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ Package name on PyPI is **`unplug-ai`**. You import it as **`unplug`**:
1414
from unplug import Guard
1515
```
1616

17+
**Import style:** Apps and agents use top-level `unplug` exports. Server/MCP code
18+
that needs policy, cache, or boundary facades uses `unplug.api.*`
19+
([`PUBLIC_API.md`](PUBLIC_API.md)). Avoid `unplug.core.*` — it is internal.
20+
1721
Optional ML model (better recall on indirect injection):
1822

1923
```bash

‎sdk/docs/PUBLIC_API.md‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
# Public SDK API Surface
22

3+
## Which import path?
4+
5+
| Audience | Import from | Example |
6+
|----------|-------------|---------|
7+
| **App and agent authors** | Top-level `unplug` exports | `from unplug import Guard` |
8+
| **Server, MCP, and dependents** needing policy, cache, boundaries, or ML facades | `unplug.api.*` | `from unplug.api.types import ScanRequest` |
9+
| **Do not use in new code** | `unplug.core.*` | Internal; may change in minor releases |
10+
311
Use `unplug.api.*` for cross-repository integrations. Treat `unplug.core.*` and
412
most of `unplug.ml.*` as private implementation details that can move during SDK
513
refactors.

‎sdk/src/unplug/guard.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -702,7 +702,7 @@ def stats(self) -> dict:
702702

703703
@classmethod
704704
def init(cls, **kwargs: Any) -> Guard:
705-
"""Initialize a global Guard and auto-instrument detected frameworks."""
705+
"""Create and store a process-wide Guard singleton (see ``get()``)."""
706706
with cls._lock:
707707
cls._instance = Guard(**kwargs)
708708
return cls._instance

‎sdk/src/unplug/pipelines/base.py‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,10 @@ def run(self, input_data: Any, *, context: ExecutionContext | None = None) -> Sc
114114

115115
ctx.update_risk(risk_score)
116116

117-
log_fn = _log.warning if 0.3 <= risk_score < 0.5 else _log.info
117+
if action == Action.REVIEW:
118+
log_fn = _log.info
119+
else:
120+
log_fn = _log.warning if 0.3 <= risk_score < 0.5 else _log.info
118121
log_fn(
119122
"pipeline %s: action=%s risk=%.2f findings=%d latency=%.1fms",
120123
self.name,

‎sdk/tests/unit/pipelines/test_pipelines.py‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
"""Tests for pipelines: input, output, tool call."""
22

3+
import logging
4+
5+
import pytest
6+
37
from unplug.core.context import ExecutionContext, ToolCall
48
from unplug.core.privacy.secrets import SecretsRegistry, SecretsSanitizer
59
from unplug.core.taint import TaintedText, TrustLevel
10+
from unplug.guard import Guard
611
from unplug.models import Action, Source
712
from unplug.pipelines.input import InputPipeline
813
from unplug.pipelines.output import OutputPipeline
@@ -226,3 +231,23 @@ def test_drop_table_in_json_value(self):
226231
result = pipeline.run(tc)
227232
assert not result.safe
228233
assert any(f.category == "destructive" for f in result.findings)
234+
235+
236+
class TestPipelineLogging:
237+
def test_review_outcome_logs_at_info(
238+
self,
239+
caplog: pytest.LogCaptureFixture,
240+
) -> None:
241+
guard = Guard()
242+
guard.scan("Poisoned doc", source=Source.RETRIEVED)
243+
with caplog.at_level(logging.DEBUG, logger="unplug.pipelines"):
244+
result = guard.check_tool_call("shell", {"command": "echo hello"})
245+
assert result.action == Action.REVIEW
246+
review_records = [
247+
record
248+
for record in caplog.records
249+
if record.name == "unplug.pipelines" and "action=review" in record.getMessage()
250+
]
251+
assert review_records
252+
assert all(record.levelno == logging.INFO for record in review_records)
253+
assert not any(record.levelno == logging.WARNING for record in review_records)

0 commit comments

Comments
 (0)