Skip to content

telemetry: add deterministic tool-call receipt contract#4503

Open
davidahmann wants to merge 1 commit intogoogle:mainfrom
davidahmann:codex/issue-4502-tool-receipt-schema
Open

telemetry: add deterministic tool-call receipt contract#4503
davidahmann wants to merge 1 commit intogoogle:mainfrom
davidahmann:codex/issue-4502-tool-receipt-schema

Conversation

@davidahmann
Copy link

Problem

Tool-call tracing lacked a deterministic machine-readable receipt payload suitable for stable audit/replay linkage.

What changed

  • Added deterministic tool_call_receipt serialization to trace_tool_call.
  • Receipt includes schema version, tool identifiers, function call ID, args SHA-256 digest, and outcome.
  • Added/updated telemetry tests to verify schema presence and deterministic serialization.

Validation

  • uv sync --extra test
  • uv run ruff check src/google/adk/telemetry/tracing.py tests/unittests/telemetry/test_spans.py
  • uv run python -m pytest tests/unittests/telemetry/test_spans.py -k "trace_tool_call"

Refs #4502

@google-cla
Copy link

google-cla bot commented Feb 15, 2026

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@davidahmann
Copy link
Author

Implemented a deterministic tool-call receipt payload in tracing with schema-backed tests that verify required fields and stable serialization for equivalent argument payloads.

This contribution was informed by patterns from Gait: https://github.com/davidahmann/gait

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @davidahmann, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request addresses a limitation in tool-call tracing by implementing a deterministic, machine-readable receipt. This new receipt provides a stable payload for auditing and replaying tool calls, enhancing the reliability and traceability of agent interactions. The changes involve adding new serialization logic and integrating the receipt into the existing tracing mechanism, alongside comprehensive test updates.

Highlights

  • Tool-call receipt contract: Introduced a deterministic, machine-readable "tool_call_receipt" payload for improved audit and replay linkage in tool-call tracing.
  • Receipt content: The receipt now includes the schema version, tool identifiers (name, type), function call ID, a SHA-256 digest of the arguments, and the outcome of the tool call.
  • Testing: Added and updated telemetry tests to validate the presence of the new schema and ensure the deterministic serialization of the receipt.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • src/google/adk/telemetry/tracing.py
    • Imported the hashlib module for cryptographic hashing.
    • Implemented _stable_json_serialize to ensure consistent JSON output by sorting keys.
    • Created _build_tool_call_receipt to generate a structured dictionary containing tool call metadata.
    • Updated trace_tool_call to serialize and set the gcp.vertex.agent.tool_call_receipt attribute using the newly defined functions.
  • tests/unittests/telemetry/test_spans.py
    • Modified test_trace_tool_call_with_scalar_response and test_trace_tool_call_with_dict_response to include assertions for the new tool_call_receipt attribute, verifying its structure and content.
    • Added test_trace_tool_call_receipt_is_deterministic to specifically confirm that the receipt generation produces identical outputs for equivalent inputs, regardless of argument order.
    • Updated test_trace_tool_call_disabling_request_response_content to assert the presence of the tool_call_receipt.
Activity
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@adk-bot
Copy link
Collaborator

adk-bot commented Feb 15, 2026

Response from ADK Triaging Agent

Hello @davidahmann, thank you for creating this PR!

Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA). You can visit https://cla.developers.google.com/ to see your current agreements or to sign a new one.

This information will help reviewers to review your PR more efficiently. Thanks!

@adk-bot adk-bot added the tracing [Component] This issue is related to OpenTelemetry tracing label Feb 15, 2026
Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a deterministic tool-call receipt to telemetry, which is a valuable addition for audit and replay purposes. The implementation in src/google/adk/telemetry/tracing.py correctly uses stable JSON serialization and hashing to create a deterministic receipt. The tests in tests/unittests/telemetry/test_spans.py are comprehensive, covering the new functionality well, including the deterministic nature of the receipt. My feedback includes a couple of suggestions to enhance code readability and maintainability.

Comment on lines +139 to +149
if (
function_response_event is not None
and function_response_event.content is not None
and function_response_event.content.parts
):
function_response = function_response_event.content.parts[0].function_response
if function_response is not None:
if function_response.id is not None:
tool_call_id = function_response.id
if function_response.response is not None:
outcome = 'success'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To improve readability, you can simplify the nested if statements. The current logic is correct, but flattening the structure slightly by removing redundant is not None checks and combining conditions can make it easier to follow.

Suggested change
if (
function_response_event is not None
and function_response_event.content is not None
and function_response_event.content.parts
):
function_response = function_response_event.content.parts[0].function_response
if function_response is not None:
if function_response.id is not None:
tool_call_id = function_response.id
if function_response.response is not None:
outcome = 'success'
if (
function_response_event
and function_response_event.content
and function_response_event.content.parts
):
function_response = function_response_event.content.parts[0].function_response
if function_response:
if function_response.id is not None:
tool_call_id = function_response.id
if function_response.response is not None:
outcome = 'success'

Comment on lines +401 to +407
receipt_calls = [
call_obj
for call_obj in mock_span_fixture.set_attribute.call_args_list
if call_obj.args[0] == 'gcp.vertex.agent.tool_call_receipt'
]
assert len(receipt_calls) == 1
receipt = json.loads(receipt_calls[0].args[1])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This logic for extracting the tool call receipt is repeated in several tests (test_trace_tool_call_with_dict_response, test_trace_tool_call_disabling_request_response_content). To improve maintainability and reduce code duplication, consider extracting this into a helper function within the test module.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

tracing [Component] This issue is related to OpenTelemetry tracing

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants