Scanning
Every scan method on the Python client — analyze, RAG protection, and agent protection.
Every scan method hits the same underlying detection pipeline with different
provenance — a plain prompt, a retrieved document chunk, a tool call, and
so on — and they all return the same ScanResult shape (RAG-family methods
add a couple of extra fields; scan_retrieval returns a list plus an
aggregate verdict instead). Methods on AsyncTawaGuard have the same
signatures as TawaGuard, as coroutines.
analyze
The general-purpose entry point. Scans a plain prompt before it reaches your LLM.
result = client.analyze("Ignore all previous instructions…")Result
ScanResult is a frozen dataclass:
| Field | Type | Description |
|---|---|---|
action | ScanAction | The action to enforce: "allow" or "block". The gateway collapses the engine's "review" per your app's setting. Against a gateway that predates engine_action, the engine's raw label (any of "allow" | "block" | "soft_block" | "hard_block" | "review" | "sandbox" | "ambiguous" | "ood") |
engine_action | ScanAction | None | The engine's own label before collapsing, for example "review". Falls back to action when the gateway does not send it |
needs_review | bool | True when the engine was uncertain (its action was "review"), whether action is "allow" or "block". "ood" does not set it |
risk | float | None | How likely the input is malicious (0-1), regardless of action. None for older responses or out-of-distribution input |
confidence | float | Confidence in action (0-1). Equals risk for any flagging action, and 1 - risk for allow |
suggestion | "allow" | "block" | None | Coarse hint for the undecided actions (ambiguous/review/sandbox) |
rationale | str | Human-readable explanation of the decision |
policy | PolicyVerdict | None | Directives signaled by the worker's policy engine, if any |
language | LanguageInfo | None | Language identification detail, None while language ID is disabled |
score_breakdown | dict[str, Any] | None | Diagnostic detail behind the decision — shape varies by which decision path fired |
if result.action == "allow":
print(f"Safe ({result.confidence:.2f} confidence)")
else:
print(f"Blocked: {result.rationale}")
if result.needs_review:
# The engine was uncertain; engine_action holds its own label.
print(f"Flagged for review (engine said {result.engine_action})")See The review flag. Against a gateway that
predates these fields, needs_review is derived from action == "review" and
action carries the engine's raw label, so keep handling "review",
"soft_block", "sandbox", and "ambiguous" if you support such a gateway.
Options
Every scan method accepts cuid, tenant_id, policy_overrides, and
language as keyword arguments, forwarded straight to the worker per call.
RAG protection
Scan retrieved content before it's assembled into a prompt.
# A single retrieved chunk, before it enters the assembled context.
chunk_result = client.scan_chunk(chunk_text, chunk_id="doc-42#3")
# The fully assembled prompt (system + retrieved chunks + user turn),
# immediately before the LLM call.
context_result = client.scan_context(assembled_prompt)
# An entire retrieved set in one call — a per-chunk verdict plus an
# aggregate.
from tawaguard import RagChunkInput
result = client.scan_retrieval(
query,
[
RagChunkInput(chunk_id="doc-42#1", text="…"),
RagChunkInput(chunk_id="doc-42#2", text="…"),
],
)
print(result.per_chunk, result.aggregate)
result.aggregate.flagged_chunk_ids # chunks enforced as block
result.aggregate.review_chunk_ids # chunks the engine flagged, blocked or notSee the RAG protection cookbook for a full walkthrough combining all three.
Agent protection
Inspect a proposed tool call before it executes, and its output before it re-enters the agent loop.
call_result = client.scan_tool_call("run_query", {"sql": "SELECT …"})
if call_result.action != "allow":
# withhold the tool call
...
output_result = client.scan_tool_output("run_query", query_result)See the Agent protection cookbook for a full walkthrough with both gates and policy directive handling.
Scanning a model's reply
There's no dedicated scan_response method in the Python SDK yet — use
analyze() on the model's reply text if you want to apply an equivalent
check by hand.