fix: backup provider failover bugs and tool input type safety (#392)
* fix: backup provider failover bugs and tool input type safety * fix: Add maximum boundaries to agent_tools --------- Co-authored-by: Vineeth Voruganti <13438633+VVoruganti@users.noreply.github.com>
This commit is contained in:
parent
233df802f1
commit
046935a6bb
|
|
@ -28,6 +28,18 @@ from src.utils.types import get_current_iteration
|
|||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _safe_int(value: Any, default: int) -> int:
|
||||
"""Coerce a tool input value to int, returning default on failure.
|
||||
|
||||
LLMs sometimes pass non-numeric strings (e.g. 'Infinity') for integer
|
||||
parameters which would crash ``min()`` comparisons.
|
||||
"""
|
||||
try:
|
||||
return int(value)
|
||||
except (TypeError, ValueError, OverflowError):
|
||||
return default
|
||||
|
||||
|
||||
# Module-level lock registry for thread-safe observation creation.
|
||||
# Keyed by (workspace_name, observer, observed) to ensure all tool executors
|
||||
# operating on the same data share the same lock.
|
||||
|
|
@ -1163,7 +1175,7 @@ async def _handle_get_recent_history(
|
|||
|
||||
async def _handle_search_memory(ctx: ToolContext, tool_input: dict[str, Any]) -> str:
|
||||
"""Handle search_memory tool."""
|
||||
top_k = min(tool_input.get("top_k", 20), 40)
|
||||
top_k = min(_safe_int(tool_input.get("top_k"), 20), 40)
|
||||
query = tool_input["query"]
|
||||
try:
|
||||
query_embedding = await embedding_client.embed(query)
|
||||
|
|
@ -1188,7 +1200,7 @@ async def _handle_search_memory(ctx: ToolContext, tool_input: dict[str, Any]) ->
|
|||
# doesn't short-circuit and think there's nothing here, we
|
||||
# automatically search the message history for relevant information.
|
||||
if ctx.agent_type == "dialectic":
|
||||
limit = min(tool_input.get("top_k", 20), 20)
|
||||
limit = min(_safe_int(tool_input.get("top_k"), 20), 20)
|
||||
snippets = await crud.search_messages(
|
||||
ctx.db,
|
||||
workspace_name=ctx.workspace_name,
|
||||
|
|
@ -1243,7 +1255,7 @@ async def _handle_get_observation_context(
|
|||
async def _handle_search_messages(ctx: ToolContext, tool_input: dict[str, Any]) -> str:
|
||||
"""Handle search_messages tool."""
|
||||
query = tool_input["query"]
|
||||
limit = min(tool_input.get("limit", 10), 20) # Cap at 20
|
||||
limit = min(_safe_int(tool_input.get("limit"), 10), 20) # Cap at 20
|
||||
snippets = await crud.search_messages(
|
||||
ctx.db,
|
||||
workspace_name=ctx.workspace_name,
|
||||
|
|
@ -1263,8 +1275,10 @@ async def _handle_grep_messages(ctx: ToolContext, tool_input: dict[str, Any]) ->
|
|||
text = tool_input.get("text", "")
|
||||
if not text:
|
||||
return "ERROR: 'text' parameter is required"
|
||||
limit = min(tool_input.get("limit", 10), 30) # Cap at 30
|
||||
context_window = min(tool_input.get("context_window", 2), 2) # Cap context
|
||||
limit = min(_safe_int(tool_input.get("limit"), 10), 30) # Cap at 30
|
||||
context_window = min(
|
||||
_safe_int(tool_input.get("context_window"), 2), 2
|
||||
) # Cap context
|
||||
|
||||
snippets = await crud.grep_messages(
|
||||
ctx.db,
|
||||
|
|
@ -1316,7 +1330,7 @@ async def _handle_get_messages_by_date_range(
|
|||
"""Handle get_messages_by_date_range tool."""
|
||||
after_date_str = tool_input.get("after_date")
|
||||
before_date_str = tool_input.get("before_date")
|
||||
limit = min(tool_input.get("limit", 20), 20)
|
||||
limit = min(_safe_int(tool_input.get("limit"), 20), 20)
|
||||
order = tool_input.get("order", "desc")
|
||||
|
||||
after_date = _parse_date(after_date_str, "after_date")
|
||||
|
|
@ -1373,8 +1387,8 @@ async def _handle_search_messages_temporal(
|
|||
|
||||
after_date_str = tool_input.get("after_date")
|
||||
before_date_str = tool_input.get("before_date")
|
||||
limit = min(tool_input.get("limit", 10), 10)
|
||||
context_window = min(tool_input.get("context_window", 2), 2)
|
||||
limit = min(_safe_int(tool_input.get("limit"), 10), 10)
|
||||
context_window = min(_safe_int(tool_input.get("context_window"), 2), 2)
|
||||
|
||||
after_date = _parse_date(after_date_str, "after_date")
|
||||
if isinstance(after_date, str):
|
||||
|
|
@ -1418,7 +1432,7 @@ async def _handle_get_recent_observations(
|
|||
workspace_name=ctx.workspace_name,
|
||||
observer=ctx.observer,
|
||||
observed=ctx.observed,
|
||||
limit=tool_input.get("limit", 10),
|
||||
limit=min(_safe_int(tool_input.get("limit"), 10), 100),
|
||||
session_name=ctx.session_name if session_only else None,
|
||||
)
|
||||
representation = Representation.from_documents(documents)
|
||||
|
|
@ -1443,7 +1457,7 @@ async def _handle_get_most_derived_observations(
|
|||
workspace_name=ctx.workspace_name,
|
||||
observer=ctx.observer,
|
||||
observed=ctx.observed,
|
||||
limit=tool_input.get("limit", 10),
|
||||
limit=min(_safe_int(tool_input.get("limit"), 10), 100),
|
||||
)
|
||||
representation = Representation.from_documents(documents)
|
||||
total_count = representation.len()
|
||||
|
|
|
|||
|
|
@ -711,6 +711,8 @@ async def _execute_tool_loop(
|
|||
effective_tool_choice = tool_choice
|
||||
|
||||
while iteration < max_tool_iterations:
|
||||
# Reset attempt counter so each iteration starts with the primary provider
|
||||
_current_attempt.set(1)
|
||||
logger.debug(f"Tool execution iteration {iteration + 1}/{max_tool_iterations}")
|
||||
|
||||
# Truncate BEFORE making the API call to avoid context length errors
|
||||
|
|
@ -961,8 +963,10 @@ async def _execute_tool_loop(
|
|||
_current_attempt.set(1) # Reset attempt counter
|
||||
|
||||
async def _final_call() -> HonchoLLMCallResponse[Any]:
|
||||
provider = llm_settings.PROVIDER
|
||||
model = llm_settings.MODEL
|
||||
# Use shared provider selection helper for backup failover support
|
||||
provider, model, thinking_budget, gpt5_reasoning_effort, gpt5_verbosity = (
|
||||
get_provider_and_model()
|
||||
)
|
||||
|
||||
client = CLIENTS.get(provider)
|
||||
if not client:
|
||||
|
|
@ -978,9 +982,9 @@ async def _execute_tool_loop(
|
|||
json_mode,
|
||||
_get_effective_temperature(temperature),
|
||||
stop_seqs,
|
||||
reasoning_effort,
|
||||
verbosity,
|
||||
thinking_budget_tokens,
|
||||
gpt5_reasoning_effort,
|
||||
gpt5_verbosity,
|
||||
thinking_budget,
|
||||
False,
|
||||
None, # No tools
|
||||
None, # No tool_choice
|
||||
|
|
|
|||
Loading…
Reference in New Issue