fix(telemetry): MCP tools capture spans using Open Telemetry

Calls to custom span capture tooling is replaced with calls to the open
telemetry library 1:1. No additional modifications were made, and
formatting changes can be addressed in follow up PRs.
This commit is contained in:
Emilio Garcia 2025-11-11 13:54:21 -05:00
parent 89b0c69a07
commit b049b29ba3
2 changed files with 21 additions and 11 deletions

View file

@ -8,7 +8,8 @@ import uuid
from collections.abc import AsyncIterator from collections.abc import AsyncIterator
from typing import Any from typing import Any
from llama_stack.core.telemetry import tracing from opentelemetry import trace
from llama_stack.log import get_logger from llama_stack.log import get_logger
from llama_stack.providers.utils.inference.prompt_adapter import interleaved_content_as_str from llama_stack.providers.utils.inference.prompt_adapter import interleaved_content_as_str
from llama_stack_api import ( from llama_stack_api import (
@ -77,6 +78,7 @@ from .utils import (
) )
logger = get_logger(name=__name__, category="agents::meta_reference") logger = get_logger(name=__name__, category="agents::meta_reference")
tracer = trace.get_tracer(__name__)
def convert_tooldef_to_chat_tool(tool_def): def convert_tooldef_to_chat_tool(tool_def):
@ -1094,8 +1096,10 @@ class StreamingResponseOrchestrator:
"server_url": mcp_tool.server_url, "server_url": mcp_tool.server_url,
"mcp_list_tools_id": list_id, "mcp_list_tools_id": list_id,
} }
# List MCP tools with authorization from tool config
async with tracing.span("list_mcp_tools", attributes): # TODO: follow semantic conventions for Open Telemetry tool spans
# https://opentelemetry.io/docs/specs/semconv/gen-ai/gen-ai-spans/#execute-tool-span
with tracer.start_as_current_span("list_mcp_tools", attributes=attributes):
tool_defs = await list_mcp_tools( tool_defs = await list_mcp_tools(
endpoint=mcp_tool.server_url, endpoint=mcp_tool.server_url,
headers=mcp_tool.headers, headers=mcp_tool.headers,
@ -1171,9 +1175,9 @@ class StreamingResponseOrchestrator:
if mcp_server.require_approval == "never": if mcp_server.require_approval == "never":
return False return False
if isinstance(mcp_server, ApprovalFilter): if isinstance(mcp_server, ApprovalFilter):
if tool_name in mcp_server.always: if mcp_server.always and tool_name in mcp_server.always:
return True return True
if tool_name in mcp_server.never: if mcp_server.never and tool_name in mcp_server.never:
return False return False
return True return True

View file

@ -9,8 +9,8 @@ import json
from collections.abc import AsyncIterator from collections.abc import AsyncIterator
from typing import Any from typing import Any
from llama_stack.core.telemetry import tracing from opentelemetry import trace
from llama_stack.log import get_logger
from llama_stack_api import ( from llama_stack_api import (
ImageContentItem, ImageContentItem,
OpenAIChatCompletionContentPartImageParam, OpenAIChatCompletionContentPartImageParam,
@ -39,9 +39,12 @@ from llama_stack_api import (
VectorIO, VectorIO,
) )
from llama_stack.log import get_logger
from .types import ChatCompletionContext, ToolExecutionResult from .types import ChatCompletionContext, ToolExecutionResult
logger = get_logger(name=__name__, category="agents::meta_reference") logger = get_logger(name=__name__, category="agents::meta_reference")
tracer = trace.get_tracer(__name__)
class ToolExecutor: class ToolExecutor:
@ -296,8 +299,9 @@ class ToolExecutor:
"server_url": mcp_tool.server_url, "server_url": mcp_tool.server_url,
"tool_name": function_name, "tool_name": function_name,
} }
# Invoke MCP tool with authorization from tool config # TODO: follow semantic conventions for Open Telemetry tool spans
async with tracing.span("invoke_mcp_tool", attributes): # https://opentelemetry.io/docs/specs/semconv/gen-ai/gen-ai-spans/#execute-tool-span
with tracer.start_as_current_span("invoke_mcp_tool", attributes=attributes):
result = await invoke_mcp_tool( result = await invoke_mcp_tool(
endpoint=mcp_tool.server_url, endpoint=mcp_tool.server_url,
tool_name=function_name, tool_name=function_name,
@ -318,7 +322,7 @@ class ToolExecutor:
# Use vector_stores.search API instead of knowledge_search tool # Use vector_stores.search API instead of knowledge_search tool
# to support filters and ranking_options # to support filters and ranking_options
query = tool_kwargs.get("query", "") query = tool_kwargs.get("query", "")
async with tracing.span("knowledge_search", {}): with tracer.start_as_current_span("knowledge_search"):
result = await self._execute_knowledge_search_via_vector_store( result = await self._execute_knowledge_search_via_vector_store(
query=query, query=query,
response_file_search_tool=response_file_search_tool, response_file_search_tool=response_file_search_tool,
@ -327,7 +331,9 @@ class ToolExecutor:
attributes = { attributes = {
"tool_name": function_name, "tool_name": function_name,
} }
async with tracing.span("invoke_tool", attributes): # TODO: follow semantic conventions for Open Telemetry tool spans
# https://opentelemetry.io/docs/specs/semconv/gen-ai/gen-ai-spans/#execute-tool-span
with tracer.start_as_current_span("invoke_tool", attributes=attributes):
result = await self.tool_runtime_api.invoke_tool( result = await self.tool_runtime_api.invoke_tool(
tool_name=function_name, tool_name=function_name,
kwargs=tool_kwargs, kwargs=tool_kwargs,