From 817e2587c9d0cfe6de1521d8da57df81d19950d6 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 14 Aug 2025 03:00:05 +0000 Subject: [PATCH 1/3] Add tool rejection plan documentation with examples and usage guide Co-authored-by: sahil --- fern/docs.yml | 3 + fern/tools/tool-rejection-plan.mdx | 153 +++++++++++++++++++++++++++++ 2 files changed, 156 insertions(+) create mode 100644 fern/tools/tool-rejection-plan.mdx diff --git a/fern/docs.yml b/fern/docs.yml index fc009a046..116e6b8f4 100644 --- a/fern/docs.yml +++ b/fern/docs.yml @@ -175,6 +175,9 @@ navigation: - page: Custom tools path: tools/custom-tools.mdx icon: fa-light fa-screwdriver-wrench + - page: Tool rejection plan + path: tools/tool-rejection-plan.mdx + icon: fa-light fa-shield-xmark - page: Custom tools troubleshooting path: tools/custom-tools-troubleshooting.mdx icon: fa-light fa-wrench diff --git a/fern/tools/tool-rejection-plan.mdx b/fern/tools/tool-rejection-plan.mdx new file mode 100644 index 000000000..38ca086dd --- /dev/null +++ b/fern/tools/tool-rejection-plan.mdx @@ -0,0 +1,153 @@ +--- +title: Tool rejection plan +subtitle: Prevent unintended tool calls using conditions based on conversation state +slug: tools/tool-rejection-plan +--- + +## Overview + +A rejection plan lets you prevent a tool from executing when certain conditions are met. You attach it to any tool call and it evaluates the recent conversation state to decide whether to reject the call. + +- If all conditions match (AND logic), the tool call is rejected. +- To express OR at the top level, use a single group condition with `operator: "OR"`. +- If `conditions` is empty or omitted, the tool always executes. + + +Use on any tool call, e.g., `Assistant.hooks.do[type=tool].tool.rejectionPlan`. + + +## Schema + +- **conditions**: Array of condition objects. Defaults to `[]`. + - Types: + - **RegexCondition**: Match message content with a regex + - `type`: "regex" + - `regex`: String pattern. RegExp.test-style substring matching. Escape backslashes in JSON (e.g., `"\\bhello\\b"`). Supports inline flags like `(?i)` for case-insensitive. + - `target` (optional): Which message to inspect + - `role`: `user` | `assistant` + - `position`: Integer index in history (default `-1` for the most recent). Negative counts from the end; `0` is the first message + - `negate` (optional): When `true`, the condition matches if the regex does NOT match (default `false`) + - **LiquidCondition**: Evaluate a [Liquid](https://liquidjs.com/) template that must output exactly `"true"` or `"false"` + - `type`: "liquid" + - `liquid`: The template. You can access `messages` (recent chat messages), `now`, and assistant variables. Useful filters include `last`, `where`, and `reverse` + - **GroupCondition**: Combine multiple conditions + - `type`: "group" + - `operator`: `AND` | `OR` + - `conditions`: Nested list of conditions (can recursively nest groups) + +## Examples + +### 1) Reject endCall unless the user says goodbye + +```json +{ + "conditions": [ + { + "type": "regex", + "regex": "(?i)\\b(bye|goodbye|farewell|see you later|take care)\\b", + "target": { "position": -1, "role": "user" }, + "negate": true + } + ] +} +``` + +### 2) Reject transfer if the user is actually asking a question + +```json +{ + "conditions": [ + { + "type": "regex", + "regex": "\\?", + "target": { "position": -1, "role": "user" } + } + ] +} +``` + +### 3) Reject transfer if the user hasn't mentioned transfer recently (Liquid) + +Liquid template for readability: + +```liquid +{% assign recentMessages = messages | last: 5 %} +{% assign userMessages = recentMessages | where: 'role', 'user' %} +{% assign mentioned = false %} +{% for msg in userMessages %} + {% if msg.content contains 'transfer' or msg.content contains 'connect' or msg.content contains 'representative' %} + {% assign mentioned = true %} + {% endif %} +{% endfor %} +{% if mentioned %}false{% else %}true{% endif %} +``` + +Wired into a rejection plan: + +```json +{ + "conditions": [ + { + "type": "liquid", + "liquid": "{% assign recentMessages = messages | last: 5 %}{% assign userMessages = recentMessages | where: 'role', 'user' %}{% assign mentioned = false %}{% for msg in userMessages %}{% if msg.content contains 'transfer' or msg.content contains 'connect' or msg.content contains 'representative' %}{% assign mentioned = true %}{% endif %}{% endfor %}{% if mentioned %}false{% else %}true{% endif %}" + } + ] +} +``` + +### 4) Top-level OR using a group + +```json +{ + "conditions": [ + { + "type": "group", + "operator": "OR", + "conditions": [ + { "type": "regex", "regex": "(?i)\\bcancel\\b", "target": { "role": "user" } }, + { "type": "regex", "regex": "(?i)\\bstop\\b", "target": { "role": "user" } } + ] + } + ] +} +``` + +## Using it in assistant hooks + +Attach `rejectionPlan` to any tool call inside a hook: + +```json +{ + "hooks": [ + { + "on": "call.ending", + "do": [ + { + "type": "tool", + "tool": { + "type": "endCall", + "rejectionPlan": { + "conditions": [ + { + "type": "regex", + "regex": "(?i)\\b(bye|goodbye|farewell|see you later|take care)\\b", + "target": { "position": -1, "role": "user" }, + "negate": true + } + ] + } + } + } + ] + } + ] +} +``` + +## Tips + +- Escape backslashes in regex patterns: write `\\b` in JSON to mean `\b` in the regex engine. +- `position: -1` targets the most recent message. Omit `role` to target regardless of role. +- Prefer a `group` with `operator: "OR"` for disjunctive logic at the top level. + +See also: [Swagger](https://api.vapi.ai/api), [OpenAPI JSON](https://api.vapi.ai/api-json). \ No newline at end of file From c346328afbe564f2a01351e30974621239755847 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 14 Aug 2025 03:02:49 +0000 Subject: [PATCH 2/3] Refactor tool rejection plan example to use model.tools configuration Co-authored-by: sahil --- fern/tools/tool-rejection-plan.mdx | 50 ++++++++++++++---------------- 1 file changed, 23 insertions(+), 27 deletions(-) diff --git a/fern/tools/tool-rejection-plan.mdx b/fern/tools/tool-rejection-plan.mdx index 38ca086dd..6bece8940 100644 --- a/fern/tools/tool-rejection-plan.mdx +++ b/fern/tools/tool-rejection-plan.mdx @@ -112,42 +112,38 @@ Wired into a rejection plan: } ``` -## Using it in assistant hooks +## Normal tool call example -Attach `rejectionPlan` to any tool call inside a hook: +Attach `rejectionPlan` directly on a tool in your assistant configuration (`model.tools`): ```json { - "hooks": [ - { - "on": "call.ending", - "do": [ - { - "type": "tool", - "tool": { - "type": "endCall", - "rejectionPlan": { - "conditions": [ - { - "type": "regex", - "regex": "(?i)\\b(bye|goodbye|farewell|see you later|take care)\\b", - "target": { "position": -1, "role": "user" }, - "negate": true - } - ] + "model": { + "provider": "openai", + "model": "gpt-4o", + "messages": [ + { "role": "system", "content": "Only end the call after the user says goodbye." } + ], + "tools": [ + { + "type": "endCall", + "rejectionPlan": { + "conditions": [ + { + "type": "regex", + "regex": "(?i)\\b(bye|goodbye|farewell|see you later|take care)\\b", + "target": { "position": -1, "role": "user" }, + "negate": true } - } + ] } - ] - } - ] + } + ] + } } ``` - ## Tips - Escape backslashes in regex patterns: write `\\b` in JSON to mean `\b` in the regex engine. - `position: -1` targets the most recent message. Omit `role` to target regardless of role. -- Prefer a `group` with `operator: "OR"` for disjunctive logic at the top level. - -See also: [Swagger](https://api.vapi.ai/api), [OpenAPI JSON](https://api.vapi.ai/api-json). \ No newline at end of file +- Prefer a `group` with `operator: "OR"` for disjunctive logic at the top level. \ No newline at end of file From 6c3fab2ee0236143905ea7e263a65e3ee283e057 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 14 Aug 2025 03:05:13 +0000 Subject: [PATCH 3/3] Add example of transferCall tool with rejection plan configuration Co-authored-by: sahil --- fern/tools/tool-rejection-plan.mdx | 41 ++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/fern/tools/tool-rejection-plan.mdx b/fern/tools/tool-rejection-plan.mdx index 6bece8940..ca2a8d41e 100644 --- a/fern/tools/tool-rejection-plan.mdx +++ b/fern/tools/tool-rejection-plan.mdx @@ -142,6 +142,47 @@ Attach `rejectionPlan` directly on a tool in your assistant configuration (`mode } } ``` + +### Another example: transferCall with rejection + +```json +{ + "model": { + "provider": "openai", + "model": "gpt-4o", + "messages": [ + { "role": "system", "content": "Transfer only if the user clearly asks to be connected." } + ], + "tools": [ + { + "type": "transferCall", + "destinations": [ + { "type": "number", "number": "+1234567890" } + ], + "rejectionPlan": { + "conditions": [ + { + "type": "group", + "operator": "OR", + "conditions": [ + { "type": "regex", "regex": "(?i)\\bconnect\\b", "target": { "role": "user" } }, + { "type": "regex", "regex": "(?i)\\btransfer\\b", "target": { "role": "user" } } + ] + }, + { + "type": "regex", + "regex": "\\?", + "target": { "position": -1, "role": "user" }, + "negate": true + } + ] + } + } + ] + } +} +``` + ## Tips - Escape backslashes in regex patterns: write `\\b` in JSON to mean `\b` in the regex engine.