diff --git a/fern/openapi.json b/fern/openapi.json
index 625cf1db..f0c6b88c 100644
--- a/fern/openapi.json
+++ b/fern/openapi.json
@@ -12342,14 +12342,6 @@
],
"nullable": true
},
- "requires_force_tool_call": {
- "oneOf": [
- {
- "type": "boolean"
- },
- {}
- ]
- },
"children": {
"type": "array",
"items": {
@@ -12402,14 +12394,6 @@
{}
],
"nullable": true
- },
- "requires_force_tool_call": {
- "oneOf": [
- {
- "type": "boolean"
- },
- {}
- ]
}
},
"required": ["tool_name"]
@@ -12457,14 +12441,6 @@
{}
],
"nullable": true
- },
- "requires_force_tool_call": {
- "oneOf": [
- {
- "type": "boolean"
- },
- {}
- ]
}
},
"required": ["tool_name"]
@@ -12513,14 +12489,6 @@
],
"nullable": true
},
- "requires_force_tool_call": {
- "oneOf": [
- {
- "type": "boolean"
- },
- {}
- ]
- },
"default_child": {
"oneOf": [
{
@@ -12608,14 +12576,6 @@
{}
],
"nullable": true
- },
- "requires_force_tool_call": {
- "oneOf": [
- {
- "type": "boolean"
- },
- {}
- ]
}
},
"required": ["tool_name"]
@@ -12663,14 +12623,6 @@
{}
],
"nullable": true
- },
- "requires_force_tool_call": {
- "oneOf": [
- {
- "type": "boolean"
- },
- {}
- ]
}
},
"required": ["tool_name"]
@@ -12719,14 +12671,6 @@
],
"nullable": true
},
- "requires_force_tool_call": {
- "oneOf": [
- {
- "type": "boolean"
- },
- {}
- ]
- },
"max_count_limit": {
"type": "number"
}
@@ -12780,14 +12724,6 @@
],
"nullable": true
},
- "requires_force_tool_call": {
- "oneOf": [
- {
- "type": "boolean"
- },
- {}
- ]
- },
"children": {
"type": "array",
"items": {
@@ -12840,14 +12776,6 @@
{}
],
"nullable": true
- },
- "requires_force_tool_call": {
- "oneOf": [
- {
- "type": "boolean"
- },
- {}
- ]
}
},
"required": ["tool_name"]
@@ -12912,14 +12840,6 @@
],
"nullable": true
},
- "requires_force_tool_call": {
- "oneOf": [
- {
- "type": "boolean"
- },
- {}
- ]
- },
"children": {
"type": "array",
"items": {
@@ -12975,14 +12895,6 @@
{}
],
"nullable": true
- },
- "requires_force_tool_call": {
- "oneOf": [
- {
- "type": "boolean"
- },
- {}
- ]
}
},
"required": ["tool_name"]
@@ -13030,14 +12942,6 @@
{}
],
"nullable": true
- },
- "requires_force_tool_call": {
- "oneOf": [
- {
- "type": "boolean"
- },
- {}
- ]
}
},
"required": ["tool_name"]
@@ -13086,14 +12990,6 @@
],
"nullable": true
},
- "requires_force_tool_call": {
- "oneOf": [
- {
- "type": "boolean"
- },
- {}
- ]
- },
"default_child": {
"oneOf": [
{
@@ -13181,14 +13077,6 @@
{}
],
"nullable": true
- },
- "requires_force_tool_call": {
- "oneOf": [
- {
- "type": "boolean"
- },
- {}
- ]
}
},
"required": ["tool_name"]
@@ -13236,14 +13124,6 @@
{}
],
"nullable": true
- },
- "requires_force_tool_call": {
- "oneOf": [
- {
- "type": "boolean"
- },
- {}
- ]
}
},
"required": ["tool_name"]
@@ -13292,14 +13172,6 @@
],
"nullable": true
},
- "requires_force_tool_call": {
- "oneOf": [
- {
- "type": "boolean"
- },
- {}
- ]
- },
"max_count_limit": {
"type": "number"
}
@@ -13353,14 +13225,6 @@
],
"nullable": true
},
- "requires_force_tool_call": {
- "oneOf": [
- {
- "type": "boolean"
- },
- {}
- ]
- },
"children": {
"type": "array",
"items": {
@@ -13416,14 +13280,6 @@
{}
],
"nullable": true
- },
- "requires_force_tool_call": {
- "oneOf": [
- {
- "type": "boolean"
- },
- {}
- ]
}
},
"required": ["tool_name"]
@@ -23545,11 +23401,6 @@
"title": "Prompt Template",
"description": "Optional template string (ignored)."
},
- "requires_force_tool_call": {
- "type": "boolean",
- "title": "Requires Force Tool Call",
- "default": true
- },
"children": {
"items": {
"type": "string"
@@ -23637,11 +23488,6 @@
"title": "Prompt Template",
"description": "Optional template string (ignored)."
},
- "requires_force_tool_call": {
- "type": "boolean",
- "title": "Requires Force Tool Call",
- "default": true
- },
"default_child": {
"anyOf": [
{
@@ -23872,11 +23718,6 @@
],
"title": "Prompt Template",
"description": "Optional template string (ignored)."
- },
- "requires_force_tool_call": {
- "type": "boolean",
- "title": "Requires Force Tool Call",
- "default": false
}
},
"additionalProperties": false,
@@ -27131,11 +26972,6 @@
],
"title": "Prompt Template",
"description": "Optional template string (ignored). Rendering uses fast built-in formatting for performance."
- },
- "requires_force_tool_call": {
- "type": "boolean",
- "title": "Requires Force Tool Call",
- "default": true
}
},
"additionalProperties": false,
@@ -29252,11 +29088,6 @@
"title": "Prompt Template",
"description": "Optional template string (ignored)."
},
- "requires_force_tool_call": {
- "type": "boolean",
- "title": "Requires Force Tool Call",
- "default": false
- },
"max_count_limit": {
"type": "integer",
"title": "Max Count Limit",
@@ -30285,11 +30116,6 @@
"title": "Prompt Template",
"description": "Optional template string (ignored)."
},
- "requires_force_tool_call": {
- "type": "boolean",
- "title": "Requires Force Tool Call",
- "default": true
- },
"children": {
"items": {
"type": "string"
@@ -31118,11 +30944,6 @@
],
"title": "Prompt Template",
"description": "Optional template string (ignored)."
- },
- "requires_force_tool_call": {
- "type": "boolean",
- "title": "Requires Force Tool Call",
- "default": false
}
},
"additionalProperties": false,
@@ -31155,11 +30976,6 @@
],
"title": "Prompt Template",
"description": "Optional template string (ignored). Rendering uses fast built-in formatting for performance."
- },
- "requires_force_tool_call": {
- "type": "boolean",
- "title": "Requires Force Tool Call",
- "default": false
}
},
"additionalProperties": false,
@@ -33054,11 +32870,6 @@
],
"title": "Prompt Template",
"description": "Optional template string (ignored)."
- },
- "requires_force_tool_call": {
- "type": "boolean",
- "title": "Requires Force Tool Call",
- "default": false
}
},
"additionalProperties": false,
diff --git a/letta/schemas/tool_rule.py b/letta/schemas/tool_rule.py
index c8bc5c09..94116e55 100644
--- a/letta/schemas/tool_rule.py
+++ b/letta/schemas/tool_rule.py
@@ -18,7 +18,6 @@ class BaseToolRule(LettaBase):
None,
description="Optional template string (ignored). Rendering uses fast built-in formatting for performance.",
)
- requires_force_tool_call: bool = False
def __hash__(self):
"""Base hash using tool_name and type."""
@@ -37,6 +36,13 @@ class BaseToolRule(LettaBase):
"""Default implementation returns None. Subclasses provide optimized strings."""
return None
+ @property
+ def requires_force_tool_call(self) -> bool:
+ """Whether this tool rule requires forcing a tool call in the LLM request when active.
+ When True, the LLM must use a tool; when False, tool use is optional.
+ Default is False for most rules."""
+ return False
+
class ChildToolRule(BaseToolRule):
"""
@@ -49,7 +55,11 @@ class ChildToolRule(BaseToolRule):
default=None,
description="Optional template string (ignored).",
)
- requires_force_tool_call: bool = True
+
+ @property
+ def requires_force_tool_call(self) -> bool:
+ """Child tool rules require forcing tool calls."""
+ return True
def __hash__(self):
"""Hash including children list (sorted for consistency)."""
@@ -78,7 +88,11 @@ class ParentToolRule(BaseToolRule):
type: Literal[ToolRuleType.parent_last_tool] = ToolRuleType.parent_last_tool
children: List[str] = Field(..., description="The children tools that can be invoked.")
prompt_template: Optional[str] = Field(default=None, description="Optional template string (ignored).")
- requires_force_tool_call: bool = True
+
+ @property
+ def requires_force_tool_call(self) -> bool:
+ """Parent tool rules require forcing tool calls."""
+ return True
def __hash__(self):
"""Hash including children list (sorted for consistency)."""
@@ -109,7 +123,11 @@ class ConditionalToolRule(BaseToolRule):
child_output_mapping: Dict[Any, str] = Field(..., description="The output case to check for mapping")
require_output_mapping: bool = Field(default=False, description="Whether to throw an error when output doesn't match any case")
prompt_template: Optional[str] = Field(default=None, description="Optional template string (ignored).")
- requires_force_tool_call: bool = True
+
+ @property
+ def requires_force_tool_call(self) -> bool:
+ """Conditional tool rules require forcing tool calls."""
+ return True
def __hash__(self):
"""Hash including all configuration fields."""
@@ -191,7 +209,11 @@ class InitToolRule(BaseToolRule):
"""
type: Literal[ToolRuleType.run_first] = ToolRuleType.run_first
- requires_force_tool_call: bool = True
+
+ @property
+ def requires_force_tool_call(self) -> bool:
+ """Initial tool rules require forcing tool calls."""
+ return True
class TerminalToolRule(BaseToolRule):
@@ -201,7 +223,6 @@ class TerminalToolRule(BaseToolRule):
type: Literal[ToolRuleType.exit_loop] = ToolRuleType.exit_loop
prompt_template: Optional[str] = Field(default=None, description="Optional template string (ignored).")
- requires_force_tool_call: bool = False
def render_prompt(self) -> str | None:
return f"\n{self.tool_name} ends your response (yields control) when called\n"
@@ -214,7 +235,6 @@ class ContinueToolRule(BaseToolRule):
type: Literal[ToolRuleType.continue_loop] = ToolRuleType.continue_loop
prompt_template: Optional[str] = Field(default=None, description="Optional template string (ignored).")
- requires_force_tool_call: bool = False
def render_prompt(self) -> str | None:
return f"\n{self.tool_name} requires continuing your response when called\n"
@@ -227,7 +247,6 @@ class RequiredBeforeExitToolRule(BaseToolRule):
type: Literal[ToolRuleType.required_before_exit] = ToolRuleType.required_before_exit
prompt_template: Optional[str] = Field(default=None, description="Optional template string (ignored).")
- requires_force_tool_call: bool = False
def get_valid_tools(self, tool_call_history: List[str], available_tools: Set[str], last_function_response: Optional[str]) -> Set[str]:
"""Returns all available tools - the logic for preventing exit is handled elsewhere."""
@@ -245,7 +264,6 @@ class MaxCountPerStepToolRule(BaseToolRule):
type: Literal[ToolRuleType.max_count_per_step] = ToolRuleType.max_count_per_step
max_count_limit: int = Field(..., description="The max limit for the total number of times this tool can be invoked in a single step.")
prompt_template: Optional[str] = Field(default=None, description="Optional template string (ignored).")
- requires_force_tool_call: bool = False
def __hash__(self):
"""Hash including max_count_limit."""
@@ -277,7 +295,6 @@ class RequiresApprovalToolRule(BaseToolRule):
"""
type: Literal[ToolRuleType.requires_approval] = ToolRuleType.requires_approval
- requires_force_tool_call: bool = False
def get_valid_tools(self, tool_call_history: List[str], available_tools: Set[str], last_function_response: Optional[str]) -> Set[str]:
"""Does not enforce any restrictions on which tools are valid"""