mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-05 07:37:50 +00:00
fix(langfuse otel): drop prompt variables, MCP tool I/O and vector store queries from exported request metadata
This commit is contained in:
parent
c12102dd92
commit
acb7e42bdb
3 changed files with 137 additions and 1 deletions
|
|
@ -60,6 +60,9 @@ if TYPE_CHECKING:
|
|||
_INTERNAL_CALL_ORIGINS: Final[frozenset[str]] = frozenset(get_args(InternalCallOrigin))
|
||||
_AUTH_ATTRIBUTE: Final = TypeAdapter(object)
|
||||
_REQUEST_METADATA_MAPPING_ADAPTER: Final[TypeAdapter[Mapping[str, object]]] = TypeAdapter(Mapping[str, object])
|
||||
_PROMPT_MANAGEMENT_CONTENT_FIELDS: Final[frozenset[str]] = frozenset({"prompt_variables"})
|
||||
_MCP_TOOL_CALL_CONTENT_FIELDS: Final[frozenset[str]] = frozenset({"arguments", "result"})
|
||||
_VECTOR_STORE_CONTENT_FIELDS: Final[frozenset[str]] = frozenset({"query", "vector_store_search_response"})
|
||||
|
||||
REQUESTER_METADATA_KEY: Final = "requester_metadata"
|
||||
REQUESTER_METADATA_PATH: Final = f"{REQUESTER_METADATA_KEY}."
|
||||
|
|
@ -484,7 +487,39 @@ def exported_request_metadata(payload: Mapping[str, object]) -> Mapping[str, obj
|
|||
)
|
||||
redacted: Final = _REQUEST_METADATA_MAPPING_ADAPTER.validate_python(redact_metadata(dict(metadata)))
|
||||
exported: Final = _REQUEST_METADATA_MAPPING_ADAPTER.validate_python(log_requester_metadata(redacted))
|
||||
return MappingProxyType({key: value for key, value in exported.items() if value is not None})
|
||||
null_filtered: Final = MappingProxyType({key: value for key, value in exported.items() if value is not None})
|
||||
return _without_request_metadata_content(null_filtered)
|
||||
|
||||
|
||||
def _without_mapping_content_fields(value: object, content_fields: frozenset[str]) -> object:
|
||||
if not isinstance(value, dict):
|
||||
return value
|
||||
mapping: Final = _REQUEST_METADATA_MAPPING_ADAPTER.validate_python(value)
|
||||
return {key: item for key, item in mapping.items() if key not in content_fields}
|
||||
|
||||
|
||||
def _without_vector_store_content_fields(value: object) -> object:
|
||||
if not isinstance(value, list):
|
||||
return value
|
||||
return tuple(_without_mapping_content_fields(item, _VECTOR_STORE_CONTENT_FIELDS) for item in value)
|
||||
|
||||
|
||||
def _request_metadata_value_without_content(key: str, value: object) -> object:
|
||||
match key:
|
||||
case "prompt_management_metadata":
|
||||
return _without_mapping_content_fields(value, _PROMPT_MANAGEMENT_CONTENT_FIELDS)
|
||||
case "mcp_tool_call_metadata":
|
||||
return _without_mapping_content_fields(value, _MCP_TOOL_CALL_CONTENT_FIELDS)
|
||||
case "vector_store_request_metadata":
|
||||
return _without_vector_store_content_fields(value)
|
||||
case _:
|
||||
return value
|
||||
|
||||
|
||||
def _without_request_metadata_content(metadata: Mapping[str, object]) -> Mapping[str, object]:
|
||||
return MappingProxyType(
|
||||
{key: _request_metadata_value_without_content(key, value) for key, value in metadata.items()}
|
||||
)
|
||||
|
||||
|
||||
def _team_metadata_dict(value: object) -> Mapping[str, object] | None:
|
||||
|
|
|
|||
|
|
@ -815,6 +815,56 @@ def test_langfuse_mapper_emits_request_metadata_and_identity():
|
|||
assert attrs["langfuse.observation.metadata.provider"] == "openai"
|
||||
|
||||
|
||||
def test_langfuse_mapper_omits_prompt_mcp_and_vector_store_content():
|
||||
payload: Final[dict[str, object]] = {
|
||||
"call_type": "acompletion",
|
||||
"custom_llm_provider": "openai",
|
||||
"model": "gpt-4o",
|
||||
"metadata": {
|
||||
"prompt_management_metadata": {
|
||||
"prompt_id": "prompt-1",
|
||||
"prompt_variables": {"input": "prompt-variables-secret"},
|
||||
"prompt_integration": "prompt-service",
|
||||
},
|
||||
"mcp_tool_call_metadata": {
|
||||
"name": "lookup_weather",
|
||||
"mcp_server_name": "weather-server",
|
||||
"arguments": {"location": "mcp-arguments-secret"},
|
||||
"result": {"forecast": "mcp-result-secret"},
|
||||
},
|
||||
"vector_store_request_metadata": [
|
||||
{
|
||||
"vector_store_id": "vs-1",
|
||||
"custom_llm_provider": "openai",
|
||||
"query": "vector-query-secret",
|
||||
"vector_store_search_response": {"text": "vector-response-secret"},
|
||||
"start_time": 1.0,
|
||||
"end_time": 2.0,
|
||||
}
|
||||
],
|
||||
},
|
||||
"response": {},
|
||||
}
|
||||
attrs: Final = LangfuseMapper().map(LLMCallSpanData.from_standard_logging_payload(payload))
|
||||
observation_metadata: Final = attrs["langfuse.observation.metadata"]
|
||||
serialized: Final = json.dumps(observation_metadata)
|
||||
parsed: Final = json.loads(observation_metadata)
|
||||
|
||||
assert "prompt-variables-secret" not in serialized
|
||||
assert "mcp-arguments-secret" not in serialized
|
||||
assert "mcp-result-secret" not in serialized
|
||||
assert "vector-query-secret" not in serialized
|
||||
assert "vector-response-secret" not in serialized
|
||||
assert parsed["prompt_management_metadata"]["prompt_id"] == "prompt-1"
|
||||
assert parsed["prompt_management_metadata"]["prompt_integration"] == "prompt-service"
|
||||
assert parsed["mcp_tool_call_metadata"]["name"] == "lookup_weather"
|
||||
assert parsed["mcp_tool_call_metadata"]["mcp_server_name"] == "weather-server"
|
||||
assert parsed["vector_store_request_metadata"][0]["vector_store_id"] == "vs-1"
|
||||
assert parsed["vector_store_request_metadata"][0]["custom_llm_provider"] == "openai"
|
||||
assert parsed["vector_store_request_metadata"][0]["start_time"] == 1.0
|
||||
assert parsed["vector_store_request_metadata"][0]["end_time"] == 2.0
|
||||
|
||||
|
||||
def test_langfuse_mapper_skips_empty_identity_values():
|
||||
payload: Final[dict[str, object]] = {
|
||||
"call_type": "acompletion",
|
||||
|
|
|
|||
|
|
@ -549,6 +549,57 @@ class TestLangfuseOtelIntegration:
|
|||
}
|
||||
assert not [key for key in actual if key.startswith("langfuse.trace.metadata.")]
|
||||
|
||||
def test_request_metadata_omits_prompt_mcp_and_vector_store_content(self) -> None:
|
||||
request_metadata: Final = {
|
||||
"prompt_management_metadata": {
|
||||
"prompt_id": "prompt-1",
|
||||
"prompt_variables": {"input": "prompt-variables-secret"},
|
||||
"prompt_integration": "prompt-service",
|
||||
},
|
||||
"mcp_tool_call_metadata": {
|
||||
"name": "lookup_weather",
|
||||
"mcp_server_name": "weather-server",
|
||||
"arguments": {"location": "mcp-arguments-secret"},
|
||||
"result": {"forecast": "mcp-result-secret"},
|
||||
},
|
||||
"vector_store_request_metadata": [
|
||||
{
|
||||
"vector_store_id": "vs-1",
|
||||
"custom_llm_provider": "openai",
|
||||
"query": "vector-query-secret",
|
||||
"vector_store_search_response": {"text": "vector-response-secret"},
|
||||
"start_time": 1.0,
|
||||
"end_time": 2.0,
|
||||
}
|
||||
],
|
||||
}
|
||||
kwargs: Final[dict[str, object]] = {
|
||||
"litellm_params": {"metadata": {}},
|
||||
"standard_logging_object": {"metadata": request_metadata},
|
||||
}
|
||||
|
||||
with patch("litellm.integrations.arize._utils.safe_set_attribute") as mock_safe_set_attribute:
|
||||
LangfuseOtelLogger._set_langfuse_specific_attributes(MagicMock(), kwargs, None)
|
||||
|
||||
actual: Final = {call.args[1]: call.args[2] for call in mock_safe_set_attribute.call_args_list}
|
||||
observation_metadata: Final = actual["langfuse.observation.metadata"]
|
||||
serialized: Final = json.dumps(observation_metadata)
|
||||
parsed: Final = json.loads(observation_metadata)
|
||||
|
||||
assert "prompt-variables-secret" not in serialized
|
||||
assert "mcp-arguments-secret" not in serialized
|
||||
assert "mcp-result-secret" not in serialized
|
||||
assert "vector-query-secret" not in serialized
|
||||
assert "vector-response-secret" not in serialized
|
||||
assert parsed["prompt_management_metadata"]["prompt_id"] == "prompt-1"
|
||||
assert parsed["prompt_management_metadata"]["prompt_integration"] == "prompt-service"
|
||||
assert parsed["mcp_tool_call_metadata"]["name"] == "lookup_weather"
|
||||
assert parsed["mcp_tool_call_metadata"]["mcp_server_name"] == "weather-server"
|
||||
assert parsed["vector_store_request_metadata"][0]["vector_store_id"] == "vs-1"
|
||||
assert parsed["vector_store_request_metadata"][0]["custom_llm_provider"] == "openai"
|
||||
assert parsed["vector_store_request_metadata"][0]["start_time"] == 1.0
|
||||
assert parsed["vector_store_request_metadata"][0]["end_time"] == 2.0
|
||||
|
||||
def test_request_metadata_drops_null_fields_and_skips_empty_trace_identities(self) -> None:
|
||||
request_metadata: Final = {
|
||||
"user_api_key_alias": "prod-key",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue