Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

範例

本頁將撰寫 Rules的全部概念套用至完整 rulesets。首先逐一說明 demo 中實際可執行的 ruleset files,再介紹 ruleset 擴大後常用的兩種 patterns,包括使用 Labels 保存 state,以及將 multi-signal Rule 分散整理於多個檔案。範例使用與其他文件相同的小型社群網站。

逐檔說明 demo ruleset

Demo 會執行 example_rules/ 中的實際 ruleset。內容只有兩個 Models、一個 Rule 與一個 Label,可以一次讀完。受治理平台有一項嚴格政策,使用者永遠不能說 hello

Events 會以 JSON 傳入,包含 event name action_name、ID 與平台送出的資料。

{
  "action_id": 1,
  "action_name": "create_post",
  "data": {
    "user_id": "user_1923",
    "event_type": "create_post",
    "post": { "text": "hello world" }
  }
}

main.sml 是 entrypoint。這個 ruleset 很小,不需要 conditional index.sml files,只要 import base Model,再 require 唯一的 Rule file。

Import(rules=['models/base.sml'])

Require(rule='rules/post_contains_hello.sml')

models/base.sml 定義每個 event 都有的 Features。UserIdEventType 會宣告為 Entities,讓 Osprey 把它們視為跨 Events 持續存在的對象,因此後續可以讓 Label 留在使用者上。預設為 true 的 coerce_type=True 會將 numeric user ID 等不符合宣告 Type 的值轉換成指定 Type,而非產生 error。

UserId: Entity[str] = EntityJson(
  type='User',
  path='$.user_id',
  coerce_type=True
)

EventType: Entity[str] = EntityJson(
  type='EventType',
  path='$.event_type',
  coerce_type=True
)

ActionName=GetActionName()

ActionId=GetActionId()

最後兩行從 Osprey 本身取得 event name 與 ID,不從 JSON payload 讀取。GetActionName()GetActionId() 是 stdlib UDFs,將結果公開為 Features 後,就能在介面中查詢。

models/post.sml 加入只適用於貼文的 Feature。

PostText: Entity[str] = EntityJson(
  type='PostText',
  path='$.post.text',
  coerce_type=True
)

rules/post_contains_hello.sml 會 import 兩個 Models、定義 Rule,再將 Rule 連接至 Effects,完整迴路都位於同一個檔案。

Import(
  rules=[
    'models/base.sml',
    'models/post.sml',
  ]
)

ContainsHello = Rule(
  when_all=[
    EventType == 'create_post',
    TextContains(text=PostText, phrase='hello')
  ],
  description='Post contains the word "hello"',
)

WhenRules(
  rules_any=[ContainsHello],
  then=[
    BanUser(entity=UserId, comment='User said "hello"'),
    LabelAdd(entity=UserId, label='meow'),
  ],
)

TextContainsBanUser 不屬於 stdlib,而是 example_plugins/ 提供的 custom UDFs。自訂方式請見撰寫 UDFsLabelAdd 屬於 stdlib。

config/labels.yaml 會宣告 Rule 套用的 Label,包括適用的 Entity types 與 connotation。

labels:
  meow:
    valid_for: [User]
    connotation: positive
    description: testing label

Events 開始流入後,每個處理完成的貼文都會出現在 Event Stream,其中擷取的 Features 包括 UserIdEventTypePostTextContainsHello。查詢 ContainsHello == True 可以只顯示符合 Rule 的貼文。開啟符合條件貼文的作者後,可以看到 User Entity 上的 meow Label,以及記錄於 event 的 BanUser Effect。開始使用會使用即時 demo 資料逐步操作這些介面。

使用 Labels 保存 state

Rules 無法直接查看過去 Events,但 Labels 會跨 Events 保留在 Entities 上,因此某個 Rule 新增的 Label 可以成為另一個 Rule 後續檢查的條件。假設已經標示傳送過多 DMs 的使用者。

WhenRules(
    rules_any=[
        Sent_Too_Many_DMs,
    ],
    then=[
        LabelAdd(entity=UserId, label='likely_spammer')
    ],
)

此後,該使用者的每個 event 都會帶有這項 state,其他 Rules 可以在完全不同的 event type 上使用它。

Should_Warn_User_Of_Spammer = Rule(
    when_all=[
        HasLabel(entity=UserId, label='likely_spammer'),
        This_Is_A_New_DM,
    ],
    description=f'Likely spammer {UserId} started a new DM',
)

Labels 也會顯示在介面的 Entity 上,並可手動新增或移除。需要注意一項不對稱行為。HasLabel() 可以用於 Rules,但不能用於查詢列,因為查詢搜尋的是 Events,不是目前的 Entity state。若要尋找套用某個 Label 的 Events,改為查詢 DidAddLabel(entity_type="User", label_name="likely_spammer")。詳情請見查詢語法

分散在多個檔案的 multi-signal Rule

下列完整流程使用撰寫 Rules 的 Rule 結構。目標是標示第一篇貼文同時 mention 至少一位使用者並包含 link 的帳號。三個 Signals 各自可能沒有問題,但同時出現時較為可疑。

撰寫 Rule

rules/record/post/first_post_link.sml 撰寫 Rule logic。檔案同時定義讓 Rule 得到 True 的條件,以及符合時採取的 actions。

# First, import the models that you will need inside of this rule
Import(
    rules=[
        'models/base.sml',
        'models/record/post.sml',
    ],
)

# Next, define a variable that uses the `Rule` UDF
FirstPostLinkRule = Rule(
    # Set the conditions in which this rule will be `True`
    when_all=[
        PostCount == 1, # if this is the user's first post
        EmbedLink != None, # if there is a link inside of the post
        ListLength(list=MentionIds) >= 1, # if there is at least one mention in the post
    ],
    description='First post for user includes a link embed',
)

# Finally, set which effect UDFs will be triggered
WhenRules(
    rules_any=[FirstPostLinkRule],
    then=[
        # This is a custom effect UDF that we have implemented
        ReportRecord(
            entity=PostId,
            comment='This was the first post by a user and included a link',
            severity=3,
        ),
    ],
)

連接 Rule

這個 Rule 只應在 event 是 post event 時執行。使用上述專案結構時會涉及三個檔案。

首先,project root 的 main.sml 使用單一 Require statement,指向 top-level Rules index。

Require(
    rule='rules/index.sml',
)

接著,rules/index.sml 會在 event type 符合時,有條件地 require post Rules。

Import(
    rules=[
        'models/base.sml',
    ],
)

Require(
    rule='rules/record/post/index.sml',
    require_if=EventType == 'userPost',
)

最後,rules/record/post/index.sml require 新 Rule。

Import(
    rules=[
        'models/base.sml',
        'models/record/post.sml',
    ],
)

Require(
    rule='rules/record/post/first_post_link.sml',
)

可以在 demo ruleset 執行時修改它,例如為 TextContains 加入 phrase,或建立另一個針對 PostText 的 Rule。當所需 pattern 缺少 UDF 時,請前往 Integrations 與 Plugins

治理提醒

Demo 中「出現 hello 就停用使用者」是刻意簡化的測試政策。正式環境的 Rule 應使用具脈絡的 Signals、guardrails、分階段 rollout、誤判監測、人工複核與可回復 Effects。Labels 會跨 Events 影響未來判斷,應同時設計原因、到期、移除及稽核方式。