Skip to content

[dotnet-port-fixes] Emit execute_tool spans for auto-called tools - #446

Merged
George Adams (gdams) merged 11 commits into
mainfrom
dotnet-port-fixes-autocall-otel-spans-20260706202207-fb066c86f4752aa5
Jul 16, 2026
Merged

George Adams (gdams) merged 11 commits into
mainfrom
dotnet-port-fixes-autocall-otel-spans-20260706202207-fb066c86f4752aa5

Conversation

@michelle-clayton-work

@michelle-clayton-work Michelle Clayton (michelle-clayton-work) commented Jul 7, 2026 •

Copy link
Copy Markdown
Contributor

Port microsoft/agent-framework#6667 so toolautocall emits execute_tool spans under the existing invoke_agent span when otelprovider is active. Adds cross-SDK parity with Python and .NET by including the following GenAI semantic-convention attributes on every execute_tool span:

  • gen_ai.tool.call.id — from funcCall.CallID, with "unknown" fallback when the LLM omits the call ID (matching Python's tool_call_id or "unknown" behavior)
  • gen_ai.tool.type — always "function"
  • gen_ai.tool.description — conditionally included when the tool has a non-empty description (matching Python's get_function_span_attributes behavior)
  • error.type — set on both execute_tool and invoke_agent spans when an error occurs, matching Python's capture_exception pattern (type(exception).__name__ → short unqualified type name, e.g. "errorString" rather than "*errors.errorString")

The error.type value uses a shared otelx.ErrorTypeName helper (in internal/otelx/error.go) that strips the package path and pointer sigil from fmt.Sprintf("%T", err), aligning the Go output with Python's type(exception).__name__ format for consistent cross-SDK correlation in dashboards and alert rules.

Closes #435

Port microsoft/agent-framework#6667 so toolautocall emits execute_tool spans under the existing invoke_agent span when otelprovider is active.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Ports the OpenTelemetry tracing parity fix so that when otelprovider and toolautocall are used together, auto-called tool executions emit nested execute_tool spans under the existing invoke_agent span (using the same instrumentation scope).

Changes:

  • Share the otelprovider tracer via context to enable nested instrumentation.
  • Instrument toolautocall tool execution with execute_tool spans and record tool errors on those spans.
  • Add an OpenTelemetry integration test asserting execute_tool spans are emitted and parented correctly.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

File Description
agent/harness/toolautocall/autocall.go Starts execute_tool spans around tool execution when a tracer is present in context, and records tool errors on the span.
internal/otelx/context.go Adds internal helpers to store/retrieve an OTel tracer on context.Context.
provider/otelprovider/otel.go Stores the middleware’s tracer into context during Run so downstream middleware/tools can use it.
provider/otelprovider/otel_test.go Adds an integration-style test that drives toolautocall and asserts execute_tool span scope + parentage under invoke_agent.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread agent/harness/toolautocall/autocall.go
@github-actions

This comment has been minimized.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Generated by Go API Consistency Review Agent for #446 · 207.8 AIC · ⌖ 12.8 AIC · ⊞ 28.8K

Comment thread agent/harness/toolautocall/autocall.go
@michelle-clayton-work

Copy link
Copy Markdown
Contributor Author

Copilot address the parity comment on this PR -- ensuring parity with .NET framework

…ol spans for cross-SDK parity

Co-authored-by: michelle-clayton-work <262183035+michelle-clayton-work@users.noreply.github.com>

Copilot AI commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Copilot address the parity comment on this PR -- ensuring parity with .NET framework

Added gen_ai.tool.call.id (from funcCall.CallID) and gen_ai.tool.type ("function") to every execute_tool span in startToolSpan, matching the Python SDK's get_function_span_attributes and the .NET FunctionInvokingChatClient behavior. The test is updated to assert both attributes. Commit: the latest push to this branch.

@github-actions

This comment has been minimized.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Generated by Go API Consistency Review Agent for #446 · 228.8 AIC · ⌖ 12.7 AIC · ⊞ 27.1K

Comment thread agent/harness/toolautocall/autocall.go Outdated
Comment thread agent/harness/toolautocall/autocall.go Outdated
@michelle-clayton-work

Copy link
Copy Markdown
Contributor Author

Copilot address PR feedback

…cross-SDK parity

Co-authored-by: michelle-clayton-work <262183035+michelle-clayton-work@users.noreply.github.com>

Copilot AI commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Copilot address PR feedback

Addressed both inline parity gaps in the latest commit:

  • gen_ai.tool.call.id now uses cmp.Or(funcCall.CallID, "unknown") to match Python's fallback
  • gen_ai.tool.description is conditionally added when the tool has a non-empty description (matching Python's get_function_span_attributes behavior)
  • startToolSpan now accepts the tool.Tool (passed as declaration from processFunctionCall) to access the description without any interface changes

@michelle-clayton-work

Copy link
Copy Markdown
Contributor Author

Copilot fix lint errors

Co-authored-by: michelle-clayton-work <262183035+michelle-clayton-work@users.noreply.github.com>

Copilot AI commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Copilot fix lint errors

Fixed in the latest commit — removed the extra alignment spaces in the const block that gofumpt flagged.

@github-actions

This comment has been minimized.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Generated by Go API Consistency Review Agent for #446 · 242.3 AIC · ⌖ 13 AIC · ⊞ 27.1K

Comment thread agent/harness/toolautocall/autocall.go
@github-actions

This comment has been minimized.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Generated by Go API Consistency Review Agent for #446 · 456.5 AIC · ⌖ 12.8 AIC · ⊞ 27.1K

Comment thread agent/harness/toolautocall/autocall.go
Co-authored-by: michelle-clayton-work <262183035+michelle-clayton-work@users.noreply.github.com>

Copilot AI commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Copilot address open parity feedback on this PR

The remaining parity gap was in the invoke_agent span: it recorded errors via RecordError/SetStatus but omitted error.type, which Python's capture_exception always sets. Fixed in the latest commit by adding span.SetAttributes(attribute.String("error.type", fmt.Sprintf("%T", err))) before RecordError in otelprovider/otel.go, matching the pattern already in place for execute_tool spans. TestOtel_Run_RecordsError is updated to assert the attribute.

@michelle-clayton-work

Copy link
Copy Markdown
Contributor Author

Copilot address open parity feedback and resolve any comments that were already addressed

…ts/result)

Co-authored-by: michelle-clayton-work <262183035+michelle-clayton-work@users.noreply.github.com>

Copilot AI commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Copilot address open parity feedback and resolve any comments that were already addressed

All previously addressed feedback threads are already marked resolved. The one remaining open thread — the sensitive-data span attributes (gen_ai.tool.call.arguments / gen_ai.tool.call.result) — was flagged as "fine to ship as-is" by the reviewer, so I've added a TODO comment in startToolSpan to explicitly track the gap pending an EnableSensitiveData config flag (parity with Python's get_function_span_attributes and .NET's OpenTelemetryAgent.EnableSensitiveData). Done in the latest commit.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Generated by Go API Consistency Review Agent for issue #446 · 225.8 AIC · ⌖ 11.7 AIC · ⊞ 28.4K

Comment thread agent/harness/toolautocall/autocall.go Outdated
}()
if err != nil {
if span != nil {
span.SetAttributes(attribute.String(attrKeyErrorType, fmt.Sprintf("%T", err)))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Cross-SDK parity: error.type value format diverges from Python

The upstream Python capture_exception helper sets the attribute as:

span.set_attribute(OtelAttr.ERROR_TYPE, type(exception).__name__)
# e.g. → "ValueError", "KeyError", "RuntimeError"

Go here uses fmt.Sprintf("%T", err), which produces the fully-qualified type name including the package path and pointer sigil:

fmt.Sprintf("%T", err)   // → "*errors.errorString", "*os.PathError"

A consumer correlating error.type values across the Python and Go SDKs (e.g. in a dashboard or alert rule) would see completely different strings for the same logical error kind.

Suggested alignment — use only the short type name to match Python's type(exception).__name__:

// reflect.TypeOf(err).String() → "*errors.errorString"  (still includes pointer)
// reflect.TypeOf(err).Elem().Name() is empty for non-struct types
// A simple helper that strips the package path and pointer:
func errorTypeName(err error) string {
    t := fmt.Sprintf("%T", err)
    if i := strings.LastIndexByte(t, '.'); i >= 0 {
        t = t[i+1:]
    }
    return t
}
// → "errorString", "PathError"

(Python's __name__ also omits the pointer sigil, so "errorString" rather than "*errorString" is the closest match.)

The same pattern is used in both autocall.go (this file) and otelprovider/otel.go; both need to be updated together.

Upstream reference: python/packages/core/agent_framework/observability.py → capture_exception

@michelle-clayton-work

Copy link
Copy Markdown
Contributor Author

Copilot address PR feedback

Copilot AI and others added 2 commits July 14, 2026 21:31
…xception).__name__

Co-authored-by: michelle-clayton-work <262183035+michelle-clayton-work@users.noreply.github.com>
Co-authored-by: michelle-clayton-work <262183035+michelle-clayton-work@users.noreply.github.com>

Copilot AI commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Copilot address PR feedback

Fixed the open parity gap in the latest two commits. error.type now uses otelx.ErrorTypeName(err) which strips the package path and pointer sigil — e.g., "*errors.errorString" → "errorString" — matching Python's type(exception).__name__ behavior. The helper is extracted to internal/otelx/error.go so both autocall.go and otelprovider/otel.go share the same implementation with no duplication.

@github-actions

Copy link
Copy Markdown
Contributor

Cross-SDK Parity Review: ✅ Aligned

This PR ports microsoft/agent-framework#6667 and is well-aligned with both the Python and .NET upstream implementations.

Python parity (python/packages/core/agent_framework/observability.py):

  • get_function_span_attributes — attribute set matches exactly: gen_ai.operation.name, gen_ai.tool.name, gen_ai.tool.call.id (with "unknown" fallback), gen_ai.tool.type: "function", and conditional gen_ai.tool.description
  • capture_exception — uses type(exception).__name__ → Go's otelx.ErrorTypeName strips package path and pointer sigil from fmt.Sprintf("%T", err), producing the same short type name (e.g. "errorString")

.NET parity (dotnet/src/Microsoft.Agents.AI/OpenTelemetryAgent.cs):

  • execute_tool spans are emitted by FunctionInvokingChatClient under the invoke_agent span, matching the parent-child span hierarchy implemented here
  • EnableSensitiveData controls opt-in capture of gen_ai.tool.call.arguments and gen_ai.tool.call.result — the TODO comment in startToolSpan correctly identifies this remaining gap as a future addition

Scope: All new files are under internal/otelx/; no exported API signatures are added or changed. The behavioral change (new span attributes) is intentional and tracks the upstream spec.

Generated by Go API Consistency Review Agent for issue #446 · 240.9 AIC · ⌖ 11.5 AIC · ⊞ 26.7K · ◷

@gdams
George Adams (gdams) added this pull request to the merge queue Jul 16, 2026
Merged via the queue into main with commit 11fbfc6 Jul 16, 2026
19 checks passed
@gdams
George Adams (gdams) deleted the dotnet-port-fixes-autocall-otel-spans-20260706202207-fb066c86f4752aa5 branch July 16, 2026 09:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[dotnet-port-fixes] Emit execute_tool spans for auto-called tools

4 participants