Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
6f6c400
opentelementry-instrumentation-google-genai: add gen_ai.tool.definiti…
wikaaaaa Jan 23, 2026
befc106
Add _to_tool_definition
wikaaaaa Jan 26, 2026
e724d13
Remove unused import
wikaaaaa Jan 26, 2026
238580a
Add GEN_AI_TOOL_DEFINITIONS to tests.
wikaaaaa Jan 26, 2026
c786752
Remove uneccesary space.
wikaaaaa Jan 26, 2026
35f0c68
Merge branch 'main' into tooldefinitions
wikaaaaa Jan 26, 2026
daec2f6
Merge branch 'main' into tooldefinitions
wikaaaaa Jan 26, 2026
f0b7ecc
Merge branch 'main' into tooldefinitions
wikaaaaa Jan 27, 2026
4fa7323
address comments: add exclude_none to model_dump and tool type to err…
wikaaaaa Jan 28, 2026
aea8374
address comment: add if/else statement on tool types and add tests fo…
wikaaaaa Jan 29, 2026
74b343e
Merge branch 'main' into tooldefinitions
wikaaaaa Jan 29, 2026
270ae8b
dont serilize mcp client sessions in case of synchronous methods
wikaaaaa Jan 30, 2026
9acaa71
Refactor _to_tool_definition_common to be more clear
wikaaaaa Feb 2, 2026
60f66ae
remove uncessary 'function' key
wikaaaaa Feb 2, 2026
c19cd9c
fix failing tests: make mcp import conditional
wikaaaaa Feb 3, 2026
364aed2
Update changelog
wikaaaaa Feb 3, 2026
bafdbe1
Merge branch 'main' into tooldefinitions
wikaaaaa Feb 3, 2026
d679473
Update uv.lock
wikaaaaa Feb 3, 2026
f14cfb9
address comment: remove undecessary typing.Type
wikaaaaa Feb 4, 2026
1b07dcf
address comment: make mcp import conditional
wikaaaaa Feb 4, 2026
9371484
Merge branch 'main' into tooldefinitions
wikaaaaa Feb 4, 2026
7588cc1
Merge branch 'main' into tooldefinitions
wikaaaaa Feb 4, 2026
2d86bd5
Merge branch 'main' into tooldefinitions
wikaaaaa Feb 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ dependencies = [
"opentelemetry-instrumentation >=0.58b0, <2",
"opentelemetry-semantic-conventions >=0.58b0, <2",
"opentelemetry-util-genai >= 0.2b0, <0.3b0",
"mcp"
]

[project.optional-dependencies]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,12 @@
GenerateContentConfig,
GenerateContentConfigOrDict,
GenerateContentResponse,
Tool,
ToolListUnionDict,
ToolUnionDict,
)
from mcp import ClientSession as McpClientSession
from mcp import Tool as McpTool

from opentelemetry import context as context_api
from opentelemetry import trace
Expand Down Expand Up @@ -185,6 +190,58 @@ def _to_dict(value: object):
return json.loads(json.dumps(value))


def _to_tool_definition_common(tool: ToolUnionDict) -> MessagePart:
if isinstance(tool, dict):
return tool

Comment thread
DylanRussell marked this conversation as resolved.
if isinstance(tool, Tool):
if hasattr(tool, "model_dump"):
return tool.model_dump(exclude_none=True)

return str(tool)

if callable(tool):
doc = getattr(tool, "__doc__", "") or ""
return {
"function": {
"name": getattr(tool, "__name__", type(tool).__name__),
"description": doc.strip(),
}
}

if isinstance(tool, McpTool):
if hasattr(tool, "model_dump"):
return tool.model_dump(exclude_none=True)

return {
"name": getattr(tool, "name", type(tool).__name__),
"description": getattr(tool, "description", "") or "",
"input_schema": getattr(tool, "input_schema", {}),
}

try:
return {"raw_definition": json.loads(json.dumps(tool))}
except Exception:
return {
"error": f"failed to serialize tool definition, tool type={type(tool).__name__}"
}


def _to_tool_definition(tool: ToolUnionDict) -> MessagePart:
if isinstance(tool, McpClientSession):
Comment thread
DylanRussell marked this conversation as resolved.
Outdated
return None

return _to_tool_definition_common(tool)


async def _to_tool_definition_async(tool: ToolUnionDict) -> MessagePart:
if isinstance(tool, McpClientSession):
result = await tool.list_tools()
return [t.model_dump(exclude_none=True) for t in result.tools]

return _to_tool_definition_common(tool)


def _create_request_attributes(
config: Optional[GenerateContentConfigOrDict],
allow_list: AllowList,
Expand Down Expand Up @@ -285,10 +342,22 @@ def _config_to_system_instruction(
return config.system_instruction


def _config_to_tools(
config: Union[GenerateContentConfigOrDict, None],
) -> Union[ToolListUnionDict, None]:
if not config:
return None

if isinstance(config, dict):
return GenerateContentConfig.model_validate(config).tools
return config.tools


def _create_completion_details_attributes(
input_messages: list[InputMessage],
output_messages: list[OutputMessage],
system_instructions: list[MessagePart],
tool_definitions: list[MessagePart],
as_str: bool = False,
) -> dict[str, AttributeValue]:
attributes: dict[str, AttributeValue] = {
Expand All @@ -306,6 +375,11 @@ def _create_completion_details_attributes(
dataclasses.asdict(sys_instr) for sys_instr in system_instructions
]

if tool_definitions:
attributes[gen_ai_attributes.GEN_AI_TOOL_DEFINITIONS] = (
tool_definitions
)

return attributes


Expand All @@ -324,6 +398,7 @@ def __init__(
model: str,
completion_hook: CompletionHook,
generate_content_config_key_allowlist: Optional[AllowList] = None,
is_async: bool = False,
):
self._start_time = time.time_ns()
self._otel_wrapper = otel_wrapper
Expand All @@ -345,6 +420,7 @@ def __init__(
self._generate_content_config_key_allowlist = (
generate_content_config_key_allowlist or AllowList()
)
self._is_async = is_async

def wrapped_config(
self, config: Optional[GenerateContentConfigOrDict]
Expand Down Expand Up @@ -461,6 +537,44 @@ def _maybe_update_error_type(self, response: GenerateContentResponse):
block_reason = response.prompt_feedback.block_reason.name.upper()
self._error_type = f"BLOCKED_{block_reason}"

def _maybe_get_tool_definitions(self, config):
if (
self.sem_conv_opt_in_mode
!= _StabilityMode.GEN_AI_LATEST_EXPERIMENTAL
):
return None

tool_definitions = []
if tools := _config_to_tools(config):
for tool in tools:
definition = _to_tool_definition(tool)
if definition is None:
continue
if isinstance(definition, list):
tool_definitions.extend(definition)
else:
tool_definitions.append(definition)
return tool_definitions

async def _maybe_get_tool_definitions_async(self, config):
if (
self.sem_conv_opt_in_mode
!= _StabilityMode.GEN_AI_LATEST_EXPERIMENTAL
):
return None

tool_definitions = []
if tools := _config_to_tools(config):
for tool in tools:
definition = await _to_tool_definition_async(tool)
if definition is None:
continue
if isinstance(definition, list):
tool_definitions.extend(definition)
else:
tool_definitions.append(definition)
return tool_definitions

def _maybe_log_completion_details(
self,
extra_attributes: dict[str, AttributeValue],
Expand All @@ -469,6 +583,7 @@ def _maybe_log_completion_details(
request: Union[ContentListUnion, ContentListUnionDict],
candidates: list[Candidate],
config: Optional[GenerateContentConfigOrDict] = None,
tool_definitions: list[MessagePart] = None,
):
if (
self.sem_conv_opt_in_mode
Expand Down Expand Up @@ -503,6 +618,7 @@ def _maybe_log_completion_details(
input_messages,
output_messages,
system_instructions,
tool_definitions,
Comment thread
DylanRussell marked this conversation as resolved.
)
if self._content_recording_enabled in [
ContentCapturingMode.EVENT_ONLY,
Expand Down Expand Up @@ -737,6 +853,7 @@ def instrumented_generate_content(
model,
completion_hook,
generate_content_config_key_allowlist=generate_content_config_key_allowlist,
is_async=False,
)
request_attributes = _create_request_attributes(
config,
Expand Down Expand Up @@ -774,13 +891,17 @@ def instrumented_generate_content(
finally:
final_attributes = helper.create_final_attributes()
span.set_attributes(final_attributes)
maybe_tool_definitions = helper._maybe_get_tool_definitions(
config
)
helper._maybe_log_completion_details(
extra_attributes,
request_attributes,
final_attributes,
contents,
candidates,
config,
maybe_tool_definitions,
)
helper._record_token_usage_metric()
helper._record_duration_metric()
Expand Down Expand Up @@ -812,6 +933,7 @@ def instrumented_generate_content_stream(
model,
completion_hook,
generate_content_config_key_allowlist=generate_content_config_key_allowlist,
is_async=False,
)
request_attributes = _create_request_attributes(
config,
Expand Down Expand Up @@ -849,13 +971,17 @@ def instrumented_generate_content_stream(
finally:
final_attributes = helper.create_final_attributes()
span.set_attributes(final_attributes)
maybe_tool_definitions = helper._maybe_get_tool_definitions(
config
)
helper._maybe_log_completion_details(
extra_attributes,
request_attributes,
final_attributes,
contents,
candidates,
config,
maybe_tool_definitions,
)
helper._record_token_usage_metric()
helper._record_duration_metric()
Expand Down Expand Up @@ -886,6 +1012,7 @@ async def instrumented_generate_content(
model,
completion_hook,
generate_content_config_key_allowlist=generate_content_config_key_allowlist,
is_async=True,
)
request_attributes = _create_request_attributes(
config,
Expand Down Expand Up @@ -923,13 +1050,17 @@ async def instrumented_generate_content(
finally:
final_attributes = helper.create_final_attributes()
span.set_attributes(final_attributes)
maybe_tool_definitions = (
await helper._maybe_get_tool_definitions_async(config)
)
helper._maybe_log_completion_details(
extra_attributes,
request_attributes,
final_attributes,
contents,
candidates,
config,
maybe_tool_definitions,
)
helper._record_token_usage_metric()
helper._record_duration_metric()
Expand Down Expand Up @@ -961,6 +1092,7 @@ async def instrumented_generate_content_stream(
model,
completion_hook,
generate_content_config_key_allowlist=generate_content_config_key_allowlist,
is_async=True,
)
request_attributes = _create_request_attributes(
config,
Expand Down Expand Up @@ -991,13 +1123,17 @@ async def instrumented_generate_content_stream(
helper._record_token_usage_metric()
final_attributes = helper.create_final_attributes()
span.set_attributes(final_attributes)
maybe_tool_definitions = (
await helper._maybe_get_tool_definitions_async(config)
)
helper._maybe_log_completion_details(
extra_attributes,
request_attributes,
final_attributes,
contents,
[],
config,
maybe_tool_definitions,
)
helper._record_duration_metric()
with trace.use_span(span, end_on_exit=True):
Expand Down Expand Up @@ -1025,13 +1161,19 @@ async def _response_async_generator_wrapper():
finally:
final_attributes = helper.create_final_attributes()
span.set_attributes(final_attributes)
maybe_tool_definitions = (
await helper._maybe_get_tool_definitions_async(
config
)
)
helper._maybe_log_completion_details(
extra_attributes,
request_attributes,
final_attributes,
contents,
candidates,
config,
maybe_tool_definitions,
)
helper._record_token_usage_metric()
helper._record_duration_metric()
Expand Down
Loading
Loading