Description
Summary
We integrate Microsoft.Extensions.AI (currently 10.5.x) behind Microsoft.Agents.AI ChatClientAgent for a host-based copilot. We use dynamic tooling: only a bootstrap tool subset is sent first; the model calls activate_tools to add more tools in the same agent run. We hit friction around ChatOptions.Clone copying the Tools list, stale snapshots vs FunctionInvokingChatClient, and tool failures surfaced as FunctionInvocationResult instead of exceptions.
Related upstream discussions: #7217, #7218.
Environment
Microsoft.Extensions.AI: 10.5.x (upgraded from 10.4 while investigating tool resolution)
Microsoft.Agents.AI: 1.0.0
Scenario: same streaming ChatClientAgent session; tools expanded after activate_tools without restarting the agent.
- ChatOptions.Tools and Clone / snapshot consistency
Observed: After activate_tools succeeds, a subsequent model tool_calls for a newly activated function sometimes fails with tool not found (FunctionInvocationStatus.NotFound), while logs show ChatOptions.Tools count still matching an older snapshot.
Our understanding: ChatOptions.Clone() copies the Tools collection. If the app mutates the live tool list bound to the current turn but outer layers still hold a cloned ChatOptions with an old Tools reference, FunctionInvokingChatClient may still resolve against the stale list.
Our workaround: A thin IChatClient decorator immediately inside FunctionInvokingChatClient that, on every GetResponseAsync / GetStreamingResponseAsync, patches options.Tools from the current authoritative tool list for that pass (same mutable list the app updates after activation).
Ask: Please document (or formalize) the contract for:
When ChatOptions is cloned along the pipeline, and whether Tools is shared or snapshotted.
How FunctionInvokingChatClient is expected to behave if Tools is updated in place during a single multi-step run.
If the intended pattern is “always replace ChatOptions entirely,” say so; if “in-place list mutation is supported,” clarify how layers should observe updates.
- FunctionInvocationResult vs thrown exceptions
Observed: Tool failures (including NotFound) may not propagate as thrown exceptions; they appear as FunctionInvokingChatClient.FunctionInvocationResult with Status / Exception.
Our workaround: Middleware inspects FunctionInvocationResult and maps NotFound, Exception, etc., to user-visible / telemetry-friendly messages—try/catch alone is insufficient.
Ask: Document prominently that middleware must handle the envelope type, not only exceptions; optionally provide a small helper or guideline for middleware authors.
Minimal repro sketch (conceptual)
Register bootstrap tools + an activate_tools tool that adds entries to the same List used as ChatOptions.Tools (or replaces the list).
In one run: model calls activate_tools, then calls a newly activated tool by name.
If any layer still uses a cloned ChatOptions from before activation, NotFound can reproduce.
We can trim this to a standalone public console sample if maintainers want a repro (our application repo is private; we won’t link it in the issue).
Code Sample
Error Messages / Stack Traces
Package Versions
Microsoft.Extensions.AI (currently 10.5.x)
.NET Version
net10.0
Additional Context
No response
Description
Summary
We integrate Microsoft.Extensions.AI (currently 10.5.x) behind Microsoft.Agents.AI ChatClientAgent for a host-based copilot. We use dynamic tooling: only a bootstrap tool subset is sent first; the model calls activate_tools to add more tools in the same agent run. We hit friction around ChatOptions.Clone copying the Tools list, stale snapshots vs FunctionInvokingChatClient, and tool failures surfaced as FunctionInvocationResult instead of exceptions.
Related upstream discussions: #7217, #7218.
Environment
Microsoft.Extensions.AI: 10.5.x (upgraded from 10.4 while investigating tool resolution)
Microsoft.Agents.AI: 1.0.0
Scenario: same streaming ChatClientAgent session; tools expanded after activate_tools without restarting the agent.
Observed: After activate_tools succeeds, a subsequent model tool_calls for a newly activated function sometimes fails with tool not found (FunctionInvocationStatus.NotFound), while logs show ChatOptions.Tools count still matching an older snapshot.
Our understanding: ChatOptions.Clone() copies the Tools collection. If the app mutates the live tool list bound to the current turn but outer layers still hold a cloned ChatOptions with an old Tools reference, FunctionInvokingChatClient may still resolve against the stale list.
Our workaround: A thin IChatClient decorator immediately inside FunctionInvokingChatClient that, on every GetResponseAsync / GetStreamingResponseAsync, patches options.Tools from the current authoritative tool list for that pass (same mutable list the app updates after activation).
Ask: Please document (or formalize) the contract for:
When ChatOptions is cloned along the pipeline, and whether Tools is shared or snapshotted.
How FunctionInvokingChatClient is expected to behave if Tools is updated in place during a single multi-step run.
If the intended pattern is “always replace ChatOptions entirely,” say so; if “in-place list mutation is supported,” clarify how layers should observe updates.
Observed: Tool failures (including NotFound) may not propagate as thrown exceptions; they appear as FunctionInvokingChatClient.FunctionInvocationResult with Status / Exception.
Our workaround: Middleware inspects FunctionInvocationResult and maps NotFound, Exception, etc., to user-visible / telemetry-friendly messages—try/catch alone is insufficient.
Ask: Document prominently that middleware must handle the envelope type, not only exceptions; optionally provide a small helper or guideline for middleware authors.
Minimal repro sketch (conceptual)
Register bootstrap tools + an activate_tools tool that adds entries to the same List used as ChatOptions.Tools (or replaces the list).
In one run: model calls activate_tools, then calls a newly activated tool by name.
If any layer still uses a cloned ChatOptions from before activation, NotFound can reproduce.
We can trim this to a standalone public console sample if maintainers want a repro (our application repo is private; we won’t link it in the issue).
Code Sample
Error Messages / Stack Traces
Package Versions
Microsoft.Extensions.AI (currently 10.5.x)
.NET Version
net10.0
Additional Context
No response