mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-17 23:51:30 +00:00
refactor(mcp): extract _has_client_side_tool helper to clear LIT001 type gate
This commit is contained in:
parent
243ee552a5
commit
9a4ef077d3
2 changed files with 26 additions and 18 deletions
|
|
@ -60,6 +60,25 @@ def _build_tool_result_message(tool_results: Sequence[Mapping[str, object]]) ->
|
|||
)
|
||||
|
||||
|
||||
def _has_client_side_tool(
|
||||
tool_use_blocks: Sequence[Mapping[str, object]],
|
||||
other_tools: Sequence[Mapping[str, object]] | None,
|
||||
tool_server_map: Mapping[str, str],
|
||||
) -> bool: # kwargs-ok: helper function inspecting tool blocks for client passthrough
|
||||
client_tool_names = {
|
||||
t.get("name") # kwargs-ok: extract client tool name
|
||||
for t in (other_tools or ())
|
||||
if isinstance(t, dict) and t.get("name") # kwargs-ok: extract client tool name
|
||||
}
|
||||
for block in tool_use_blocks:
|
||||
name = block.get("name") # kwargs-ok: extract block tool name
|
||||
if name in client_tool_names or (
|
||||
bool(tool_server_map) and name not in tool_server_map
|
||||
): # kwargs-ok: check map membership
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
async def anthropic_messages_with_mcp(
|
||||
max_tokens: int,
|
||||
messages: Sequence[Mapping[str, object]],
|
||||
|
|
@ -85,11 +104,12 @@ async def anthropic_messages_with_mcp(
|
|||
mcp_references, other_tools = LiteLLM_Proxy_MCP_Handler._parse_mcp_tools(tools)
|
||||
|
||||
if not mcp_references:
|
||||
formatted_tools: Final = list(tools) if tools is not None else None
|
||||
return await _AnthropicMessagesCall(fn=litellm.anthropic_messages).fn(
|
||||
max_tokens=max_tokens,
|
||||
messages=list(messages),
|
||||
model=model,
|
||||
tools=list(tools) if tools else None, # kwargs-ok: param pass-through
|
||||
tools=formatted_tools,
|
||||
_skip_mcp_handler=True,
|
||||
**kwargs,
|
||||
)
|
||||
|
|
@ -136,12 +156,6 @@ async def anthropic_messages_with_mcp(
|
|||
messages=list(working_messages), stream=False, **base_call_args
|
||||
)
|
||||
|
||||
client_tool_names = set()
|
||||
if other_tools:
|
||||
for tool_item in other_tools:
|
||||
if isinstance(tool_item, dict) and "name" in tool_item:
|
||||
client_tool_names.add(tool_item["name"]) # kwargs-ok: extract client tool name
|
||||
|
||||
for _ in range(MAX_MCP_TOOL_USE_ITERATIONS):
|
||||
if _get_stop_reason(response) != "tool_use":
|
||||
break
|
||||
|
|
@ -150,14 +164,7 @@ async def anthropic_messages_with_mcp(
|
|||
if not tool_use_blocks:
|
||||
break
|
||||
|
||||
has_client_side_tool = False
|
||||
for block in tool_use_blocks:
|
||||
tool_name = block.get("name") # kwargs-ok: extract block tool name
|
||||
if tool_name in client_tool_names or (bool(tool_server_map) and tool_name not in tool_server_map):
|
||||
has_client_side_tool = True
|
||||
break
|
||||
|
||||
if has_client_side_tool:
|
||||
if _has_client_side_tool(tool_use_blocks, other_tools, tool_server_map):
|
||||
break
|
||||
|
||||
tool_results = await LiteLLM_Proxy_MCP_Handler._execute_tool_calls(
|
||||
|
|
@ -176,7 +183,7 @@ async def anthropic_messages_with_mcp(
|
|||
if not tool_results:
|
||||
break
|
||||
|
||||
working_messages = (
|
||||
working_messages = ( # rebind-ok: append assistant and tool_result turns to working context
|
||||
*working_messages,
|
||||
{"role": "assistant", "content": list(_get_response_content(response))},
|
||||
_build_tool_result_message(tool_results),
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ async def test_mcp_auto_execute_bypasses_client_side_tools():
|
|||
the auto-execution loop breaks early and passes the response back to the client.
|
||||
"""
|
||||
mock_mcp_references = [{"type": "mcp", "server_url": "http://localhost/mcp", "require_approval": "never"}]
|
||||
client_tools = [{"name": "Read", "description": "Client Read tool"}]
|
||||
|
||||
mock_mcp_tools = [SimpleNamespace(name="mcp_tool_1", description="MCP Tool", inputSchema={"type": "object"})]
|
||||
mock_tool_server_map = {"mcp_tool_1": "http://localhost/mcp"}
|
||||
|
|
@ -57,7 +58,7 @@ async def test_mcp_auto_execute_bypasses_client_side_tools():
|
|||
|
||||
with (
|
||||
patch(path_resolve, return_value=mock_context),
|
||||
patch(path_parse, return_value=(mock_mcp_references, [])),
|
||||
patch(path_parse, return_value=(mock_mcp_references, client_tools)),
|
||||
patch(path_process, new_callable=AsyncMock) as mock_process,
|
||||
patch(path_auto, return_value=True),
|
||||
patch(path_exec, new_callable=AsyncMock) as mock_execute,
|
||||
|
|
@ -71,7 +72,7 @@ async def test_mcp_auto_execute_bypasses_client_side_tools():
|
|||
max_tokens=100,
|
||||
messages=[{"role": "user", "content": "Read test.txt and run image_understand"}],
|
||||
model="claude-3-5-sonnet-20241022",
|
||||
tools=mock_mcp_references,
|
||||
tools=[*mock_mcp_references, *client_tools],
|
||||
)
|
||||
|
||||
mock_execute.assert_not_called()
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue