fix advisor interception mixed-call handling and call id propagation

Ensure mixed advisor/non-advisor tool-call batches are detected and skipped safely, and prevent follow-up executor calls from reusing the parent litellm_call_id.

Made-with: Cursor
This commit is contained in:
Sameer Kankute 2026-04-14 19:33:44 +05:30
parent abf1510dec
commit 4f31a0cd6b
No known key found for this signature in database
2 changed files with 78 additions and 1 deletions

View file

@ -238,6 +238,11 @@ class AdvisorInterceptionLogger(CustomLogger):
response=current_response, response_cost=total_response_cost
)
return current_response
if len(advisor_calls) != len(raw_tool_calls):
verbose_logger.debug(
"AdvisorInterception: Mixed tool calls detected inside advisor loop, stopping interception"
)
return current_response
assistant_content = self._extract_message_content(current_response)
assistant_message: Dict[str, Any] = {
@ -410,6 +415,7 @@ class AdvisorInterceptionLogger(CustomLogger):
raw_tool_calls: List[Dict] = []
for tool_call in tool_calls:
raw_tool_calls.append(tool_call)
function = tool_call.get("function", {})
function_name = function.get("name")
if function_name not in {"advisor", LITELLM_ADVISOR_TOOL_NAME}:
@ -432,7 +438,6 @@ class AdvisorInterceptionLogger(CustomLogger):
"question": parsed_args.get("question"),
}
)
raw_tool_calls.append(tool_call)
# Some providers (e.g. Gemini in certain modes) can return legacy function_call.
if not advisor_calls and function_call is not None:
@ -633,6 +638,7 @@ class AdvisorInterceptionLogger(CustomLogger):
"safety_identifier",
"service_tier",
"stream",
"litellm_call_id",
}
return {
k: v

View file

@ -323,3 +323,74 @@ async def test_post_call_hook_cleans_up_config_when_should_run_is_false():
assert result is None
assert "cleanup-call-2" not in logger._advisor_config_by_call_id
@pytest.mark.asyncio
async def test_should_run_chat_completion_agentic_loop_skips_mixed_tool_calls():
logger = AdvisorInterceptionLogger(enabled_providers=["openai"])
logger._advisor_config_by_call_id["mixed-call-1"] = {
"advisor_model": "claude-opus-4-6",
"max_uses": 3,
}
mock_response = ModelResponse(
id="test-mixed",
choices=[
Choices(
finish_reason="tool_calls",
index=0,
message=Message(
role="assistant",
content=None,
tool_calls=[
ChatCompletionMessageToolCall(
id="call_advisor",
type="function",
function=Function(
name=LITELLM_ADVISOR_TOOL_NAME,
arguments='{"question":"Need advisor guidance"}',
),
),
ChatCompletionMessageToolCall(
id="call_weather",
type="function",
function=Function(
name="get_weather",
arguments='{"city":"San Francisco"}',
),
),
],
),
)
],
model="gpt-4o-mini",
object="chat.completion",
created=123,
)
should_run, tools_dict = await logger.async_should_run_chat_completion_agentic_loop(
response=mock_response,
model="gpt-4o-mini",
messages=[{"role": "user", "content": "Help"}],
tools=[get_litellm_advisor_tool_openai()],
stream=False,
custom_llm_provider="openai",
kwargs={"litellm_call_id": "mixed-call-1"},
)
assert should_run is False
assert tools_dict == {}
assert "mixed-call-1" not in logger._advisor_config_by_call_id
def test_prepare_followup_kwargs_removes_litellm_call_id():
kwargs = {
"litellm_call_id": "original-call-id",
"metadata": {"k": "v"},
"user_defined_key": "should_remain",
}
filtered_kwargs = AdvisorInterceptionLogger._prepare_followup_kwargs(kwargs)
assert "litellm_call_id" not in filtered_kwargs
assert "metadata" not in filtered_kwargs
assert filtered_kwargs["user_defined_key"] == "should_remain"