mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-04 02:31:27 +00:00
fix: capture MCP tool responses in standard logging
Signed-off-by: Ritwij Aryan Parmar <ritwij.aryan.parmar@gmail.com>
This commit is contained in:
parent
5699a06413
commit
df08547450
2 changed files with 107 additions and 0 deletions
|
|
@ -5343,9 +5343,18 @@ def _extract_response_obj_and_hidden_params(
|
|||
hidden_params = getattr(init_response_obj, "_hidden_params", None)
|
||||
elif isinstance(init_response_obj, dict):
|
||||
response_obj = init_response_obj
|
||||
elif callable(getattr(init_response_obj, "model_dump", None)):
|
||||
response_obj = init_response_obj.model_dump()
|
||||
hidden_params = getattr(init_response_obj, "_hidden_params", None)
|
||||
elif callable(getattr(init_response_obj, "dict", None)):
|
||||
response_obj = init_response_obj.dict()
|
||||
hidden_params = getattr(init_response_obj, "_hidden_params", None)
|
||||
else:
|
||||
response_obj = {}
|
||||
|
||||
if not isinstance(response_obj, dict):
|
||||
response_obj = {}
|
||||
|
||||
if original_exception is not None and hidden_params is None:
|
||||
response_headers = _get_response_headers(original_exception)
|
||||
if response_headers is not None:
|
||||
|
|
|
|||
|
|
@ -2689,6 +2689,104 @@ def test_get_standard_logging_object_payload_includes_litellm_call_id(logging_ob
|
|||
assert payload["litellm_call_id"] == call_id
|
||||
|
||||
|
||||
def test_mcp_tool_model_dump_response_is_captured_in_standard_logging_payload(
|
||||
logging_obj,
|
||||
):
|
||||
"""MCP SDK result objects should retain tool output in standard logging."""
|
||||
import datetime
|
||||
|
||||
from litellm.litellm_core_utils.litellm_logging import (
|
||||
get_standard_logging_object_payload,
|
||||
)
|
||||
from litellm.types.utils import CallTypes
|
||||
|
||||
class FakeMCPCallToolResult:
|
||||
def model_dump(self):
|
||||
return {
|
||||
"content": [{"type": "text", "text": "42"}],
|
||||
"structuredContent": {"answer": 42},
|
||||
"isError": False,
|
||||
}
|
||||
|
||||
now = datetime.datetime.now()
|
||||
payload = get_standard_logging_object_payload(
|
||||
kwargs={
|
||||
"litellm_call_id": "mcp-response-call-id",
|
||||
"model": "MCP: math/answer",
|
||||
"messages": [],
|
||||
"call_type": CallTypes.call_mcp_tool.value,
|
||||
"mcp_tool_call_metadata": {
|
||||
"name": "answer",
|
||||
"arguments": {"question": "life"},
|
||||
"mcp_server_name": "math",
|
||||
"namespaced_tool_name": "math/answer",
|
||||
},
|
||||
},
|
||||
init_response_obj=FakeMCPCallToolResult(),
|
||||
start_time=now,
|
||||
end_time=now,
|
||||
logging_obj=logging_obj,
|
||||
status="success",
|
||||
)
|
||||
|
||||
assert payload is not None
|
||||
assert payload["call_type"] == CallTypes.call_mcp_tool.value
|
||||
assert payload["status"] == "success"
|
||||
assert payload["response"] == {
|
||||
"content": [{"type": "text", "text": "42"}],
|
||||
"structuredContent": {"answer": 42},
|
||||
"isError": False,
|
||||
}
|
||||
assert payload["metadata"]["mcp_tool_call_metadata"] is not None
|
||||
assert (
|
||||
payload["metadata"]["mcp_tool_call_metadata"]["namespaced_tool_name"]
|
||||
== "math/answer"
|
||||
)
|
||||
|
||||
|
||||
def test_mcp_tool_dict_response_is_captured_in_standard_logging_payload(logging_obj):
|
||||
import datetime
|
||||
|
||||
from litellm.litellm_core_utils.litellm_logging import (
|
||||
get_standard_logging_object_payload,
|
||||
)
|
||||
from litellm.types.utils import CallTypes
|
||||
|
||||
now = datetime.datetime.now()
|
||||
payload = get_standard_logging_object_payload(
|
||||
kwargs={
|
||||
"litellm_call_id": "mcp-json-rpc-response-call-id",
|
||||
"model": "MCP: search/query",
|
||||
"messages": [],
|
||||
"call_type": CallTypes.call_mcp_tool.value,
|
||||
},
|
||||
init_response_obj={
|
||||
"jsonrpc": "2.0",
|
||||
"id": 1,
|
||||
"result": {
|
||||
"content": [{"type": "text", "text": "search result"}],
|
||||
"structuredContent": {"ids": ["doc-1"]},
|
||||
"isError": False,
|
||||
},
|
||||
},
|
||||
start_time=now,
|
||||
end_time=now,
|
||||
logging_obj=logging_obj,
|
||||
status="success",
|
||||
)
|
||||
|
||||
assert payload is not None
|
||||
assert payload["response"] == {
|
||||
"jsonrpc": "2.0",
|
||||
"id": 1,
|
||||
"result": {
|
||||
"content": [{"type": "text", "text": "search result"}],
|
||||
"structuredContent": {"ids": ["doc-1"]},
|
||||
"isError": False,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def _make_dict_logging_obj():
|
||||
"""Build a Logging instance configured for a non-streaming dict result."""
|
||||
obj = LitellmLogging(
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue