mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-03 02:22:24 +00:00
fix(proxy): populate tool registry for anthropic_messages traffic
LiteLLM_ToolTable and LiteLLM_SpendLogToolIndex stayed empty for /v1/messages requests. _enqueue_tool_registry_upsert only read tools[].function.name from the request, missing the Anthropic format where the name is top-level, and gated response extraction on hasattr(completion_response, "choices"), which is False for AnthropicMessagesResponse since it is a TypedDict. Also extract top-level request tool names and tool_use content blocks from dict-shaped responses. Fixes #27840.
This commit is contained in:
parent
8f54ab2493
commit
4bf46cb18e
2 changed files with 69 additions and 5 deletions
|
|
@ -245,12 +245,15 @@ class DBSpendUpdateWriter:
|
|||
Extract tool names from the LLM request and response and enqueue them
|
||||
for upsert into LiteLLM_ToolTable via ToolDiscoveryQueue.
|
||||
|
||||
Handles four sources:
|
||||
Handles five sources:
|
||||
- MCP tools: standard_logging_object.mcp_tool_call_metadata.namespaced_tool_name
|
||||
- Response tool_calls (OpenAI / Anthropic pass-through converted to OpenAI format):
|
||||
completion_response.choices[].message.tool_calls[].function.name
|
||||
- Request tools array (OpenAI format): kwargs["tools"][].function.name
|
||||
- Request tools array (Anthropic /messages format): kwargs["passthrough_logging_payload"]
|
||||
- Response tool_use blocks (anthropic_messages route, dict response):
|
||||
completion_response["content"][].name where type == "tool_use"
|
||||
- Request tools array (OpenAI format: tools[].function.name,
|
||||
Anthropic /messages format: tools[].name)
|
||||
- Request tools array (Anthropic pass-through): kwargs["passthrough_logging_payload"]
|
||||
["request_body"]["tools"][].name
|
||||
"""
|
||||
try:
|
||||
|
|
@ -290,13 +293,16 @@ class DBSpendUpdateWriter:
|
|||
if tool_name:
|
||||
_enqueue(tool_name, origin=mcp_server_name or "user_defined")
|
||||
|
||||
# --- Tools from request body (OpenAI format: tools[].function.name) ---
|
||||
# --- Tools from request body (OpenAI format: tools[].function.name,
|
||||
# Anthropic /messages format: tools[].name) ---
|
||||
request_tools = kwargs.get("tools") or []
|
||||
for tool_def in request_tools:
|
||||
if not isinstance(tool_def, dict):
|
||||
continue
|
||||
fn = tool_def.get("function") or {}
|
||||
name = fn.get("name") if isinstance(fn, dict) else None
|
||||
name = (
|
||||
fn.get("name") if isinstance(fn, dict) else None
|
||||
) or tool_def.get("name")
|
||||
if name:
|
||||
_enqueue(name)
|
||||
|
||||
|
|
@ -333,6 +339,15 @@ class DBSpendUpdateWriter:
|
|||
tool_name = getattr(fn, "name", None)
|
||||
if tool_name:
|
||||
_enqueue(tool_name)
|
||||
|
||||
# --- Response tool_use blocks (anthropic_messages route returns
|
||||
# AnthropicMessagesResponse, a TypedDict / plain dict) ---
|
||||
elif isinstance(completion_response, dict):
|
||||
for block in completion_response.get("content") or []:
|
||||
if isinstance(block, dict) and block.get("type") == "tool_use":
|
||||
name = block.get("name")
|
||||
if name:
|
||||
_enqueue(name)
|
||||
except Exception as e:
|
||||
verbose_proxy_logger.debug(
|
||||
"_enqueue_tool_registry_upsert error (non-blocking): %s", e
|
||||
|
|
|
|||
|
|
@ -1656,3 +1656,52 @@ async def test_commit_spend_updates_iterates_in_sorted_order(
|
|||
)
|
||||
|
||||
assert captured_where_values == expected_order
|
||||
|
||||
|
||||
def test_enqueue_tool_registry_upsert_anthropic_format_request_tools():
|
||||
writer = DBSpendUpdateWriter()
|
||||
writer.tool_discovery_queue.add_update = MagicMock()
|
||||
|
||||
writer._enqueue_tool_registry_upsert(
|
||||
kwargs={
|
||||
"tools": [
|
||||
{"name": "get_weather", "input_schema": {"type": "object"}},
|
||||
{"name": "search_docs", "input_schema": {"type": "object"}},
|
||||
]
|
||||
},
|
||||
completion_response=None,
|
||||
)
|
||||
|
||||
enqueued = [
|
||||
c.args[0]["tool_name"]
|
||||
for c in writer.tool_discovery_queue.add_update.call_args_list
|
||||
]
|
||||
assert enqueued == ["get_weather", "search_docs"]
|
||||
|
||||
|
||||
def test_enqueue_tool_registry_upsert_dict_response_tool_use_blocks():
|
||||
writer = DBSpendUpdateWriter()
|
||||
writer.tool_discovery_queue.add_update = MagicMock()
|
||||
|
||||
writer._enqueue_tool_registry_upsert(
|
||||
kwargs={},
|
||||
completion_response={
|
||||
"id": "msg_123",
|
||||
"role": "assistant",
|
||||
"content": [
|
||||
{"type": "text", "text": "Let me check the weather."},
|
||||
{
|
||||
"type": "tool_use",
|
||||
"id": "toolu_01",
|
||||
"name": "get_weather",
|
||||
"input": {"city": "San Francisco"},
|
||||
},
|
||||
],
|
||||
},
|
||||
)
|
||||
|
||||
enqueued = [
|
||||
c.args[0]["tool_name"]
|
||||
for c in writer.tool_discovery_queue.add_update.call_args_list
|
||||
]
|
||||
assert enqueued == ["get_weather"]
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue