Skip to content

Commit 817e258

Browse files
cursoragentsahil
andcommitted
Add tool rejection plan documentation with examples and usage guide
Co-authored-by: sahil <sahil@vapi.ai>
1 parent 8c7caa8 commit 817e258

2 files changed

Lines changed: 156 additions & 0 deletions

File tree

fern/docs.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,9 @@ navigation:
175175
- page: Custom tools
176176
path: tools/custom-tools.mdx
177177
icon: fa-light fa-screwdriver-wrench
178+
- page: Tool rejection plan
179+
path: tools/tool-rejection-plan.mdx
180+
icon: fa-light fa-shield-xmark
178181
- page: Custom tools troubleshooting
179182
path: tools/custom-tools-troubleshooting.mdx
180183
icon: fa-light fa-wrench

fern/tools/tool-rejection-plan.mdx

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
---
2+
title: Tool rejection plan
3+
subtitle: Prevent unintended tool calls using conditions based on conversation state
4+
slug: tools/tool-rejection-plan
5+
---
6+
7+
## Overview
8+
9+
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.
10+
11+
- If all conditions match (AND logic), the tool call is rejected.
12+
- To express OR at the top level, use a single group condition with `operator: "OR"`.
13+
- If `conditions` is empty or omitted, the tool always executes.
14+
15+
<Note>
16+
Use on any tool call, e.g., `Assistant.hooks.do[type=tool].tool.rejectionPlan`.
17+
</Note>
18+
19+
## Schema
20+
21+
- **conditions**: Array of condition objects. Defaults to `[]`.
22+
- Types:
23+
- **RegexCondition**: Match message content with a regex
24+
- `type`: "regex"
25+
- `regex`: String pattern. RegExp.test-style substring matching. Escape backslashes in JSON (e.g., `"\\bhello\\b"`). Supports inline flags like `(?i)` for case-insensitive.
26+
- `target` (optional): Which message to inspect
27+
- `role`: `user` | `assistant`
28+
- `position`: Integer index in history (default `-1` for the most recent). Negative counts from the end; `0` is the first message
29+
- `negate` (optional): When `true`, the condition matches if the regex does NOT match (default `false`)
30+
- **LiquidCondition**: Evaluate a [Liquid](https://liquidjs.com/) template that must output exactly `"true"` or `"false"`
31+
- `type`: "liquid"
32+
- `liquid`: The template. You can access `messages` (recent chat messages), `now`, and assistant variables. Useful filters include `last`, `where`, and `reverse`
33+
- **GroupCondition**: Combine multiple conditions
34+
- `type`: "group"
35+
- `operator`: `AND` | `OR`
36+
- `conditions`: Nested list of conditions (can recursively nest groups)
37+
38+
## Examples
39+
40+
### 1) Reject endCall unless the user says goodbye
41+
42+
```json
43+
{
44+
"conditions": [
45+
{
46+
"type": "regex",
47+
"regex": "(?i)\\b(bye|goodbye|farewell|see you later|take care)\\b",
48+
"target": { "position": -1, "role": "user" },
49+
"negate": true
50+
}
51+
]
52+
}
53+
```
54+
55+
### 2) Reject transfer if the user is actually asking a question
56+
57+
```json
58+
{
59+
"conditions": [
60+
{
61+
"type": "regex",
62+
"regex": "\\?",
63+
"target": { "position": -1, "role": "user" }
64+
}
65+
]
66+
}
67+
```
68+
69+
### 3) Reject transfer if the user hasn't mentioned transfer recently (Liquid)
70+
71+
Liquid template for readability:
72+
73+
```liquid
74+
{% assign recentMessages = messages | last: 5 %}
75+
{% assign userMessages = recentMessages | where: 'role', 'user' %}
76+
{% assign mentioned = false %}
77+
{% for msg in userMessages %}
78+
{% if msg.content contains 'transfer' or msg.content contains 'connect' or msg.content contains 'representative' %}
79+
{% assign mentioned = true %}
80+
{% endif %}
81+
{% endfor %}
82+
{% if mentioned %}false{% else %}true{% endif %}
83+
```
84+
85+
Wired into a rejection plan:
86+
87+
```json
88+
{
89+
"conditions": [
90+
{
91+
"type": "liquid",
92+
"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 %}"
93+
}
94+
]
95+
}
96+
```
97+
98+
### 4) Top-level OR using a group
99+
100+
```json
101+
{
102+
"conditions": [
103+
{
104+
"type": "group",
105+
"operator": "OR",
106+
"conditions": [
107+
{ "type": "regex", "regex": "(?i)\\bcancel\\b", "target": { "role": "user" } },
108+
{ "type": "regex", "regex": "(?i)\\bstop\\b", "target": { "role": "user" } }
109+
]
110+
}
111+
]
112+
}
113+
```
114+
115+
## Using it in assistant hooks
116+
117+
Attach `rejectionPlan` to any tool call inside a hook:
118+
119+
```json
120+
{
121+
"hooks": [
122+
{
123+
"on": "call.ending",
124+
"do": [
125+
{
126+
"type": "tool",
127+
"tool": {
128+
"type": "endCall",
129+
"rejectionPlan": {
130+
"conditions": [
131+
{
132+
"type": "regex",
133+
"regex": "(?i)\\b(bye|goodbye|farewell|see you later|take care)\\b",
134+
"target": { "position": -1, "role": "user" },
135+
"negate": true
136+
}
137+
]
138+
}
139+
}
140+
}
141+
]
142+
}
143+
]
144+
}
145+
```
146+
147+
## Tips
148+
149+
- Escape backslashes in regex patterns: write `\\b` in JSON to mean `\b` in the regex engine.
150+
- `position: -1` targets the most recent message. Omit `role` to target regardless of role.
151+
- Prefer a `group` with `operator: "OR"` for disjunctive logic at the top level.
152+
153+
See also: [Swagger](https://api.vapi.ai/api), [OpenAPI JSON](https://api.vapi.ai/api-json).

0 commit comments

Comments
 (0)