content_safety is a content-safety guardrail provided through the agent plugin mechanism: around model and tool calls it sends the content under review to the configured moderation backend, and a blocking decision can replace content or stop a tool call. Use it to connect an agent to existing content policies.
Overview
Tool identifiercontent_safety.
content_safety is an opt-in agent plugin: by default it is not mounted and makes no network requests. Use provider to select a moderation backend; both backends must be enabled explicitly:
Both backends are wired into the following audit points:
- Before Model Callback — review user input before it reaches the model
- After Model Callback — review the model’s output
- Before Tool Callback — review tool arguments before a tool call
- After Tool Callback — review the tool’s return value
Enabling content safety
content_safety supports three ways to enable it, in descending precedence:
- Configure it directly through the
content_safetyconstructor parameter onAgent; - Configure a
tool.content_safetyblock inconfig.yaml; - Call
build_content_safety(config)to build the plugin manually and pass it to an ADKApp.
Runner or deployed through AgentKit resolve the configuration and mount the plugin automatically at startup, with no manual callback wiring required.
Agent constructor parameter
Agent exposes a content_safety parameter that controls plugin resolution:
main.py
App manually, use the agent.content_safety_plugins property to obtain the resolved plugin list and pass it in:
main.py
Configuration reference
Thetool.content_safety block maps to ContentSafetyConfig; its full fields:
DecisionConfig fields:
QuestionConfig fields:
Decision service connection fields can be overridden from environment variables:
The
tool.content_safety block is structured runtime configuration and is not flattened into environment variables. Nested values such as questions and decision are passed to the plugin verbatim.LLM Shield backend
provider: llm_shield uses the Volcengine LLM Application Firewall to review content at each stage and block unsafe input and output.
Prerequisites
Before usingcontent_safety, purchase an instance, add assets, and obtain its AppID. Configure it through the tool.content_safety block in config.yaml or through environment variables:
config.yaml
tool.llm_shield block. content_safety reads the following settings at initialization. Configure AppID before importing it; initialization fails when AppID is missing.
config.yaml
config.yaml at startup. Alternatively, set the environment variable before starting Python; it takes precedence:
Values set through environment variables take precedence over the matching keys in
config.yaml.Policy categories
content_safety relies on the content-safety policies of the Volcengine LLM Application Firewall to detect and block different types of risky content. The top-level categories are:
Notes:
- The General Topic Control policy is not configured by default when you add firewall assets; after adding them, configure the topic-control policy yourself.
- The Computational Resource Consumption policy does not trigger on a single request: it blocks requests only once the system detects a behavior pattern with similar attack vectors accompanied by high compute output over a time window.
Endpoints & authentication
content_safety supports two content-moderation endpoints, selected automatically based on TOOL_LLM_SHIELD_URL.
Default firewall endpoint
Used when theTOOL_LLM_SHIELD_URL path does not contain /OpenTOP/V1/Lumen/Moderate (the default behavior when url is not set). Requests are sent to <url>/v2/moderate, with authentication chosen as follows:
- When
TOOL_LLM_SHIELD_API_KEYis configured, API key authentication is used (thex-api-keyrequest header is sent). - When no API key is configured, Volcengine AK/SK authentication is used: the
VOLCENGINE_ACCESS_KEYandVOLCENGINE_SECRET_KEYenvironment variables are read first; if both are missing, temporary credentials are obtained from the IAM role of the runtime environment. The request headers carry the service region and service identifier.
CLOUD_PROVIDER=byteplus, global configuration can map BytePlus AK/SK to the credential settings above. Confirm that the moderation service accepts those account credentials. Using a BytePlus model does not configure the moderation endpoint, region, or permissions
Lumen Moderate endpoint
Used when theTOOL_LLM_SHIELD_URL path contains /OpenTOP/V1/Lumen/Moderate. Requests are sent directly to the configured full address, authenticating with the AppID as the endpoint identifier and the API key as the endpoint secret.
Compared with the default endpoint, the Lumen Moderate endpoint attaches the current session identifier and invocation identifier during tool-call reviews, so that multiple reviews within the same session can be correlated on the audit side. Model-input review, model-output review, tool-argument review, and tool-return review each issue a request at the corresponding callback point, with blocking behavior matching the default endpoint.
AgentSentry V3 private moderation
WhenAGENTSENTRY_URL is set, content_safety enters V3 private moderation mode, which is fully independent of the public firewall endpoints (default endpoint and V1 Lumen Moderate endpoint). This mode takes precedence over TOOL_LLM_SHIELD_* configuration: once AGENTSENTRY_URL is set, the public-mode settings no longer apply. V3 private moderation uses the AgentSentry V3 Moderate endpoint (path /OpenTOP/V3/Lumen/Moderate) with endpoint AK/SK signature authentication.
Environment variables
Configuration example:
V3 private moderation must be configured through
AGENTSENTRY_URL. If the path of TOOL_LLM_SHIELD_URL is /OpenTOP/V3/Lumen/Moderate, initialization raises an error.Review scope and behavior
V3 private moderation mode reviews only the following two points:
Key differences from public mode:
- User input review: Reviews all text parts of the user content, not just the first text part of the last user content. User input is reviewed only once per invocation.
- Model output: Does not review model output, but records the model’s visible text (excluding thought-process text) as a plan when the model generates tool calls, for use in subsequent tool-call review.
- Tool-call review: Requires the model to provide a visible text plan when generating tool calls. If no matching plan is found, the tool call is blocked rather than executed directly. The reviewed content is the model’s plan text and tool call information, not serialized tool arguments.
- Tool results: Not reviewed.
- Moderation failure handling: When the V3 service returns a degraded response, a non-200 status code, a timeout, or an unknown decision, tool execution is blocked with a service-unavailable message rather than allowing the content through.
Usage
provider: llm_shield is mounted automatically when run through Runner or deployed through AgentKit. You can also hook the content_safety callbacks onto the agent directly:
config.yaml; the calling code stays the same:
config.yaml
Checking moderation
Configure settings before importingcontent_safety, and provide the key through TOOL_LLM_SHIELD_API_KEY. Save the example and run python app.py. Ordinary input should receive a response; verify blocking with test text covered by your configured policy and check the service audit records. A fixed sample sentence does not guarantee a particular policy match
Model-input moderation checks the first text part of the last user content in the current request; model-output moderation checks its first text part. It does not automatically cover all history, images, audio, or multipart content. Tool callbacks moderate serialized arguments and results. The default request timeout is 50 seconds
V3 private moderation mode has a different review scope from the public mode described above: user input review checks all text parts rather than only the first, tool-call review checks the model’s visible plan rather than serialized arguments, and model output and tool results are not reviewed. See the “AgentSentry V3 private moderation” section above.
Adjusting the request timeout
In the example above, replace thecontent_safety import with this configuration fragment and retain the same four callbacks:
Decision backend (System One)
provider: decision assembles the content under review into the System One state, asks a configurable set of questions, and turns the returned risk probabilities into allow or block decisions by threshold. The default question set covers prompt injection, credential theft, data exfiltration, dangerous operations, harmful content, and related categories.
Configuration
Configure it inconfig.yaml; agents run through Runner or deployed through AgentKit mount the plugin automatically:
config.yaml
TOOL_CONTENT_SAFETY_DECISION_API_URL, TOOL_CONTENT_SAFETY_DECISION_API_KEY, and TOOL_CONTENT_SAFETY_DECISION_MODEL.
You can configure it in code instead:
main.py
build_content_safety(config) to get the plugin and pass it to a manually created ADK App(plugins=[...]).
Built-in question set
Whenprovider=decision and use_default_questions=true (the default), the built-in question set is merged as the baseline. Questions use tiered thresholds by risk category: safety-critical categories (such as child_safety, self_harm) use lower thresholds to catch borderline cases; context-dependent categories (such as tool_abuse, sensitive_data) use higher thresholds that require stronger evidence. Same-named entries in questions override built-ins, and new names are appended. When use_default_questions=false, only the questions declared in questions are used, and at least one is required.
Audit window and block behavior
before_model: audits only the lastusermessage, plus the most recent tool response when one is present. Earlier conversation turns are not re-audited.after_model: audits the complete model response. For streaming responses, visible text and media chunks are withheld until the response ends orturn_completefires, then audited once before release.before_tool: audits the tool name and arguments (sensitive fields are redacted first).after_tool: audits the tool name, arguments, and result (also redacted first).thoughtcontent, function-call signatures, and other non-visible content do not enterstate.
before_model returns the fixed block message with turn_complete=True; after_model replaces the response content with the block message while preserving the original turn_complete; tool callbacks return a result with error and blocked: true; streaming responses replace the withheld chunks with the block message.
When the decision service times out, hits a network error, or returns an unparseable response, the on_error policy applies: allow (default) lets content through and marks the result degraded, while block fails closed. Each question counts as a hit when its risk probability is greater than or equal to its threshold.
When the request body is too large and the decision service rejects it (HTTP 413/431, or HTTP 400 with a response that clearly indicates size/too large), the content was never actually audited, so the SDK ignores on_error=allow and treats it as block.
Multimodal audit and state structure
state is always a System One content array; text and media use the same structure. Text-only example:
Unrecognized or unsupported MIME types are skipped with a warning and do not fail the whole audit. A single inline media item larger than 20 MiB is skipped with a warning; for large files that need auditing, upload them to TOS first and use an HTTP(S) URL. If all media is skipped and there is nothing left to audit, the result status is
skipped and the warnings are retained.
The 32,000-character limit on state applies to text fragments; media URLs or data URLs are not subject to that truncation. <untrusted> / </untrusted> tags in audited text are escaped to <...> so content cannot close or forge the wrapper. Field-name redaction matches credential keywords by word boundary (such as access_token, secretKey); metadata fields like tokenizer, token_count, and next_page_token_data are kept verbatim to avoid false positives that would create audit blind spots. Redaction is best-effort defense in depth and does not replace dedicated DLP.