mirror of
https://github.com/PrefectHQ/fastmcp.git
synced 2026-08-22 05:24:18 +02:00
fix(proxy): fall back to live identifier for backend_* span attributes (#4109)
🤖 Generated with Claude Code (Opus 4.7) on behalf of @ringerc
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
310314cf14
commit
89b99ecfb9
2 changed files with 72 additions and 4 deletions
|
|
@ -178,7 +178,7 @@ class ProxyTool(Tool):
|
|||
def get_span_attributes(self) -> dict[str, Any]:
|
||||
return super().get_span_attributes() | {
|
||||
"fastmcp.provider.type": "ProxyProvider",
|
||||
"fastmcp.proxy.backend_name": self._backend_name,
|
||||
"fastmcp.proxy.backend_name": self._backend_name or self.name,
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -285,7 +285,7 @@ class ProxyResource(Resource):
|
|||
def get_span_attributes(self) -> dict[str, Any]:
|
||||
return super().get_span_attributes() | {
|
||||
"fastmcp.provider.type": "ProxyProvider",
|
||||
"fastmcp.proxy.backend_uri": self._backend_uri,
|
||||
"fastmcp.proxy.backend_uri": self._backend_uri or str(self.uri),
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -400,7 +400,9 @@ class ProxyTemplate(ResourceTemplate):
|
|||
def get_span_attributes(self) -> dict[str, Any]:
|
||||
return super().get_span_attributes() | {
|
||||
"fastmcp.provider.type": "ProxyProvider",
|
||||
"fastmcp.proxy.backend_uri_template": self._backend_uri_template,
|
||||
"fastmcp.proxy.backend_uri_template": (
|
||||
self._backend_uri_template or self.uri_template
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -483,7 +485,7 @@ class ProxyPrompt(Prompt):
|
|||
def get_span_attributes(self) -> dict[str, Any]:
|
||||
return super().get_span_attributes() | {
|
||||
"fastmcp.provider.type": "ProxyProvider",
|
||||
"fastmcp.proxy.backend_name": self._backend_name,
|
||||
"fastmcp.proxy.backend_name": self._backend_name or self.name,
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -915,3 +915,69 @@ class TestProxyProviderCache:
|
|||
result = await proxy.call_tool("greet", {"name": "Alice"})
|
||||
mock_list.assert_not_called()
|
||||
assert result.content[0].text == "Hello, Alice!" # type: ignore[union-attr] # ty:ignore[unresolved-attribute]
|
||||
|
||||
|
||||
class TestProxySpanAttributes:
|
||||
"""Regression tests for span attributes on un-renamed proxy components.
|
||||
|
||||
A proxy component's private ``_backend_*`` field is only populated when
|
||||
the component is renamed via ``model_copy``. For un-renamed components it
|
||||
stays ``None``, so ``get_span_attributes()`` would emit ``None`` for the
|
||||
``fastmcp.proxy.backend_*`` keys — which OpenTelemetry rejects with
|
||||
``Invalid type NoneType for attribute ... value`` and drops, producing
|
||||
log spam on every proxied call.
|
||||
"""
|
||||
|
||||
async def test_proxy_tool_span_attributes_fall_back_to_name(self, proxy_server):
|
||||
proxy_provider = next(
|
||||
p for p in proxy_server.providers if isinstance(p, ProxyProvider)
|
||||
)
|
||||
tools = await proxy_provider._list_tools()
|
||||
assert tools, "expected the fixture to expose at least one tool"
|
||||
for tool in tools:
|
||||
attrs = tool.get_span_attributes()
|
||||
assert attrs["fastmcp.proxy.backend_name"] == tool.name
|
||||
assert all(v is not None for v in attrs.values()), (
|
||||
f"OpenTelemetry rejects None attribute values; got {attrs!r}"
|
||||
)
|
||||
|
||||
async def test_proxy_resource_span_attributes_fall_back_to_uri(self, proxy_server):
|
||||
proxy_provider = next(
|
||||
p for p in proxy_server.providers if isinstance(p, ProxyProvider)
|
||||
)
|
||||
resources = await proxy_provider._list_resources()
|
||||
assert resources, "expected the fixture to expose at least one resource"
|
||||
for resource in resources:
|
||||
attrs = resource.get_span_attributes()
|
||||
assert attrs["fastmcp.proxy.backend_uri"] == str(resource.uri)
|
||||
assert all(v is not None for v in attrs.values()), (
|
||||
f"OpenTelemetry rejects None attribute values; got {attrs!r}"
|
||||
)
|
||||
|
||||
async def test_proxy_template_span_attributes_fall_back_to_uri_template(
|
||||
self, proxy_server
|
||||
):
|
||||
proxy_provider = next(
|
||||
p for p in proxy_server.providers if isinstance(p, ProxyProvider)
|
||||
)
|
||||
templates = await proxy_provider._list_resource_templates()
|
||||
assert templates, "expected the fixture to expose at least one template"
|
||||
for template in templates:
|
||||
attrs = template.get_span_attributes()
|
||||
assert attrs["fastmcp.proxy.backend_uri_template"] == template.uri_template
|
||||
assert all(v is not None for v in attrs.values()), (
|
||||
f"OpenTelemetry rejects None attribute values; got {attrs!r}"
|
||||
)
|
||||
|
||||
async def test_proxy_prompt_span_attributes_fall_back_to_name(self, proxy_server):
|
||||
proxy_provider = next(
|
||||
p for p in proxy_server.providers if isinstance(p, ProxyProvider)
|
||||
)
|
||||
prompts = await proxy_provider._list_prompts()
|
||||
assert prompts, "expected the fixture to expose at least one prompt"
|
||||
for prompt in prompts:
|
||||
attrs = prompt.get_span_attributes()
|
||||
assert attrs["fastmcp.proxy.backend_name"] == prompt.name
|
||||
assert all(v is not None for v in attrs.values()), (
|
||||
f"OpenTelemetry rejects None attribute values; got {attrs!r}"
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue