fix(langfuse): log custom_tool_call items in Responses API output

This commit is contained in:
Muspi Merol 2026-08-14 18:59:15 +08:00
parent c877e0f055
commit 329f61460c
2 changed files with 64 additions and 0 deletions

View file

@ -211,6 +211,17 @@ class LangfuseOtelLogger(OpenTelemetry):
"arguments": arguments_obj,
}
output_items_data.append(langfuse_tool_call)
elif item_type == "custom_tool_call":
# Custom (freeform) tools carry a raw string `input` instead of JSON `arguments`
output_items_data.append(
{
"id": getattr(item, "id", ""),
"name": getattr(item, "name", ""),
"call_id": getattr(item, "call_id", ""),
"type": "custom_tool_call",
"input": getattr(item, "input", ""),
}
)
if output_items_data:
safe_set_attribute(
span,

View file

@ -1005,6 +1005,59 @@ class TestLangfuseOtelResponsesAPI:
assert output_data[0]["name"] == "get_weather"
assert output_data[0]["arguments"] == {}
def test_responses_api_with_custom_tool_calls(self):
"""Test Langfuse OTEL logger with Responses API custom_tool_call output."""
from litellm.types.integrations.langfuse_otel import LangfuseSpanAttributes
from litellm.types.responses.main import CustomToolCallOutputItem
# Create Responses API response with a custom (freeform) tool call
response_obj = ResponsesAPIResponse(
id="response-790",
created_at=1625247700,
output=[
CustomToolCallOutputItem(
id="ctc-123",
type="custom_tool_call",
name="exec",
call_id="call-def",
input="console.log('hello')",
status="completed",
)
],
)
kwargs = {
"call_type": "responses",
"messages": [{"role": "user", "content": "Print hello"}],
"model": "gpt-4o",
"optional_params": {},
}
mock_span = MagicMock()
with patch(
"litellm.integrations.arize._utils.safe_set_attribute"
) as mock_safe_set_attribute:
LangfuseOtelLogger._set_langfuse_specific_attributes(
mock_span, kwargs, response_obj
)
output_calls = [
call
for call in mock_safe_set_attribute.call_args_list
if call.args[1] == LangfuseSpanAttributes.OBSERVATION_OUTPUT.value
]
assert len(output_calls) > 0, "observation.output should be set"
output_data = json.loads(output_calls[0].args[2])
assert isinstance(output_data, list)
assert len(output_data) == 1
assert output_data[0]["type"] == "custom_tool_call"
assert output_data[0]["id"] == "ctc-123"
assert output_data[0]["name"] == "exec"
assert output_data[0]["call_id"] == "call-def"
assert output_data[0]["input"] == "console.log('hello')"
if __name__ == "__main__":
pytest.main([__file__])