> ## Documentation Index
> Fetch the complete documentation index at: https://docs.upsonic.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Creating Action

> Build custom content handling actions

## Overall Class Structure

Actions inherit from `ActionBase` and must implement the `action` method. They decide what to do when content is detected (block, allow, replace, anonymize, or raise exception).

```python theme={null}
from upsonic.safety_engine.base import ActionBase
from upsonic.safety_engine.models import RuleOutput, PolicyOutput

class MyCustomAction(ActionBase):
    name = "My Custom Action"
    description = "Handles detected content"
    language = "en"

    def action(self, rule_result: RuleOutput) -> PolicyOutput:
        if rule_result.confidence < 0.5:
            return self.allow_content()

        return self.raise_block_error("Content blocked")
```

## Available Action Methods

### Content Flow

* `allow_content()` / `allow_content_async()`: Let content pass through unchanged
* `raise_block_error(message)` / `raise_block_error_async(message)`: Block with a message
* `raise_exception(message)`: Raise `DisallowedOperation` exception

### Content Transformation

* `replace_triggered_keywords(replacement)` / `replace_triggered_keywords_async(replacement)`: Replace detected keywords with a fixed placeholder string (all values share the same placeholder)
* `anonymize_triggered_keywords()` / `anonymize_triggered_keywords_async()`: Replace detected keywords with unique random values that preserve format (each value gets a distinct placeholder)

### LLM-Powered

* `llm_raise_block_error(reason)` / `llm_raise_block_error_async(reason)`: Generate contextual block message using LLM
* `llm_raise_exception(reason)` / `llm_raise_exception_async(reason)`: Generate exception message using LLM

## Anonymize vs Replace

Both methods are **reversible** — the original values are automatically restored in the agent's final response. The difference is in what the LLM sees during processing:

### `anonymize_triggered_keywords()` — Random Placeholders

Replaces sensitive data with random characters while preserving format. Digits become random digits, letters become random letters. Each detected value gets a **unique** replacement, so the LLM can distinguish between different pieces of sensitive data.

```python theme={null}
class PIIAnonymizeAction(ActionBase):
    name = "PII Anonymize"
    description = "Anonymizes PII with reversible random values"
    language = "en"

    def action(self, rule_result: RuleOutput) -> PolicyOutput:
        if rule_result.confidence >= 0.5:
            return self.anonymize_triggered_keywords()
        return self.allow_content()
```

The LLM sees: `"Your email is xhkw.abc@defghij.klm and phone is 382-947-1056"`

This is ideal when you need the LLM to reason about the structure of the data (e.g., distinguish between different values) without seeing real content.

### `replace_triggered_keywords(replacement)` — Fixed Placeholders

Replaces sensitive data with a fixed placeholder string. All detected values get the **same** replacement, making the content more opaque to the LLM.

```python theme={null}
class PIIReplaceAction(ActionBase):
    name = "PII Replace"
    description = "Replaces PII with fixed placeholder"
    language = "en"

    def action(self, rule_result: RuleOutput) -> PolicyOutput:
        if rule_result.confidence >= 0.5:
            return self.replace_triggered_keywords("[PII_REDACTED]")
        return self.allow_content()
```

The LLM sees: `"Your email is [PII_REDACTED] and phone is [PII_REDACTED]"`

This is ideal when you want maximum opacity — the LLM cannot infer anything about the format or structure of the sensitive data.

## Example Action

Here's a complete example of a custom action that handles confidential content:

```python theme={null}
from upsonic.safety_engine.base import ActionBase
from upsonic.safety_engine.models import RuleOutput, PolicyOutput

class CompanySecretAction(ActionBase):
    name = "Company Secret Action"
    description = "Blocks or redacts confidential company information"
    language = "en"

    def action(self, rule_result: RuleOutput) -> PolicyOutput:
        if rule_result.confidence < 0.3:
            return self.allow_content()

        if rule_result.confidence < 0.7:
            return self.replace_triggered_keywords("[REDACTED]")

        block_message = (
            "This content has been blocked because it contains "
            "confidential company information. Please remove any "
            "internal project names, codes, or proprietary data."
        )
        return self.raise_block_error(block_message)
```

## Using LLM in Actions

For context-aware messages, use LLM-powered action methods:

```python theme={null}
class SmartSecretAction(ActionBase):
    name = "Smart Secret Action"
    description = "Uses LLM to generate contextual messages"
    language = "en"

    def action(self, rule_result: RuleOutput) -> PolicyOutput:
        if rule_result.confidence < 0.5:
            return self.allow_content()

        reason = (
            f"Content contains confidential information: "
            f"{rule_result.details}"
        )
        return self.llm_raise_block_error(reason)
```
