fix: validate parallel tool calls with tool rules at create/update time (#8060)
* fix: validate parallel tool calls with tool rules at create/update time Move validation from runtime to agent create/update time for better UX. Add client-side enforcement to truncate parallel tool calls when disabled (handles providers like Gemini that ignore the setting). 🤖 Generated with [Letta Code](https://letta.com) Co-Authored-By: Letta <noreply@letta.com> * update apis * undo --------- Co-authored-by: Letta <noreply@letta.com>
This commit is contained in:
committed by
Caren Thomas
parent
28b2f8b03f
commit
0722877423
@@ -726,6 +726,15 @@ class LettaAgentV3(LettaAgentV2):
|
||||
else:
|
||||
tool_calls = []
|
||||
|
||||
# Enforce parallel_tool_calls=false by truncating to first tool call
|
||||
# Some providers (e.g. Gemini) don't respect this setting via API, so we enforce it client-side
|
||||
if len(tool_calls) > 1 and not self.agent_state.llm_config.parallel_tool_calls:
|
||||
self.logger.warning(
|
||||
f"LLM returned {len(tool_calls)} tool calls but parallel_tool_calls=false. "
|
||||
f"Truncating to first tool call: {tool_calls[0].function.name}"
|
||||
)
|
||||
tool_calls = [tool_calls[0]]
|
||||
|
||||
# get the new generated `Message` objects from handling the LLM response
|
||||
new_messages, self.should_continue, self.stop_reason = await self._handle_ai_response(
|
||||
tool_calls=tool_calls,
|
||||
@@ -1037,15 +1046,11 @@ class LettaAgentV3(LettaAgentV2):
|
||||
|
||||
# 5. Unified tool execution path (works for both single and multiple tools)
|
||||
|
||||
# 5a. Validate parallel tool calling constraints
|
||||
if len(tool_calls) > 1:
|
||||
# No parallel tool calls with tool rules
|
||||
if self.agent_state.tool_rules and len([r for r in self.agent_state.tool_rules if r.type != "requires_approval"]) > 0:
|
||||
raise ValueError(
|
||||
"Parallel tool calling is not allowed when tool rules are present. Disable tool rules to use parallel tool calls."
|
||||
)
|
||||
# 5. Unified tool execution path (works for both single and multiple tools)
|
||||
# Note: Parallel tool calling with tool rules is validated at agent create/update time.
|
||||
# At runtime, we trust that if tool_rules exist, parallel_tool_calls=false is enforced earlier.
|
||||
|
||||
# 5b. Prepare execution specs for all tools
|
||||
# 5a. Prepare execution specs for all tools
|
||||
exec_specs = []
|
||||
for tc in tool_calls:
|
||||
call_id = tc.id or f"call_{uuid.uuid4().hex[:8]}"
|
||||
|
||||
@@ -25,7 +25,7 @@ from letta.constants import (
|
||||
INCLUDE_MODEL_KEYWORDS_BASE_TOOL_RULES,
|
||||
RETRIEVAL_QUERY_DEFAULT_PAGE_SIZE,
|
||||
)
|
||||
from letta.errors import LettaAgentNotFoundError
|
||||
from letta.errors import LettaAgentNotFoundError, LettaInvalidArgumentError
|
||||
from letta.helpers import ToolRulesSolver
|
||||
from letta.helpers.datetime_helpers import get_utc_time
|
||||
from letta.llm_api.llm_client import LLMClient
|
||||
@@ -481,6 +481,15 @@ class AgentManager:
|
||||
if tool_rules:
|
||||
check_supports_structured_output(model=agent_create.llm_config.model, tool_rules=tool_rules)
|
||||
|
||||
# Validate parallel tool calling with tool rules
|
||||
# Tool rules (excluding requires_approval) require sequential execution
|
||||
non_approval_rules = [r for r in tool_rules if r.type != "requires_approval"]
|
||||
if non_approval_rules and agent_create.llm_config.parallel_tool_calls:
|
||||
raise LettaInvalidArgumentError(
|
||||
"Parallel tool calling is not allowed when tool rules are present. "
|
||||
"Set `parallel_tool_calls=False` in llm_config or remove tool rules."
|
||||
)
|
||||
|
||||
new_agent = AgentModel(
|
||||
name=agent_create.name,
|
||||
system=derive_system_message(
|
||||
@@ -772,6 +781,16 @@ class AgentManager:
|
||||
if val is not None:
|
||||
setattr(agent, col, val)
|
||||
|
||||
# Validate parallel tool calling with tool rules after updates
|
||||
# Tool rules (excluding requires_approval) require sequential execution
|
||||
if agent.tool_rules and agent.llm_config.parallel_tool_calls:
|
||||
non_approval_rules = [r for r in agent.tool_rules if r.type != "requires_approval"]
|
||||
if non_approval_rules:
|
||||
raise LettaInvalidArgumentError(
|
||||
"Parallel tool calling is not allowed when tool rules are present. "
|
||||
"Set `parallel_tool_calls=False` in llm_config or remove tool rules."
|
||||
)
|
||||
|
||||
if agent_update.metadata is not None:
|
||||
agent.metadata_ = agent_update.metadata
|
||||
|
||||
|
||||
Reference in New Issue
Block a user