Skip to content

Python: Fix user agent prefix#5455

Merged
TaoChenOSU merged 5 commits intomainfrom
feature/python-fix-user-agent-prefix
Apr 23, 2026
Merged

Python: Fix user agent prefix#5455
TaoChenOSU merged 5 commits intomainfrom
feature/python-fix-user-agent-prefix

Conversation

@TaoChenOSU
Copy link
Copy Markdown
Contributor

@TaoChenOSU TaoChenOSU commented Apr 23, 2026

Motivation and Context

Automatically add user agent prefix according to the environment the code runs in.

Contribution Checklist

  • The code builds clean without any errors or warnings
  • The PR follows the Contribution Guidelines
  • All unit tests pass, and I have added new tests where possible
  • Is this a breaking change? If yes, add "[BREAKING]" prefix to the title of the PR.

@TaoChenOSU TaoChenOSU self-assigned this Apr 23, 2026
Copilot AI review requested due to automatic review settings April 23, 2026 21:33
@github-actions github-actions Bot changed the title Fix user agent prefix Python: Fix user agent prefix Apr 23, 2026
@moonbox3
Copy link
Copy Markdown
Contributor

moonbox3 commented Apr 23, 2026

Python Test Coverage

Python Test Coverage Report •
FileStmtsMissCoverMissing
packages/anthropic/agent_framework_anthropic
   _bedrock_client.py31196%153
   _chat_client.py4383492%446, 449, 530, 623, 625, 768, 795–796, 874, 904–905, 950, 966–967, 974–976, 980–982, 986–989, 1103, 1113, 1165, 1313–1314, 1331, 1344, 1357, 1382–1383
   _foundry_client.py35197%153
   _vertex_client.py29196%146
packages/azure-ai-search/agent_framework_azure_ai_search
   _context_provider.py3571097%120–121, 524, 575–576, 701–702, 880–881, 928
packages/azure-cosmos/agent_framework_azure_cosmos
   _checkpoint_storage.py135199%427
   _history_provider.py126596%163–165, 262, 269
packages/bedrock/agent_framework_bedrock
   _chat_client.py38110073%298–299, 315–324, 329, 345–351, 354–355, 363, 380, 389, 400, 402, 404, 422–423, 444, 457, 469, 472, 480–481, 484–485, 487–488, 493–495, 497, 507–508, 530, 537, 546–547, 549–550, 552–554, 556, 558–559, 565–567, 570–571, 577–580, 586–596, 599, 618, 623, 665–666, 679, 705, 717, 722, 731, 735, 743–744, 748, 750–757
   _embedding_client.py801383%129–140, 152
packages/core/agent_framework
   _telemetry.py53492%78–80, 127
packages/foundry/agent_framework_foundry
   _agent.py1473675%183–184, 188–190, 195–198, 293, 323–324, 336–337, 349–351, 353–354, 356–362, 364–365, 367, 369, 373–374, 561–562, 565, 607
   _chat_client.py1521987%84, 86–88, 92–93, 97, 191, 222, 315, 376, 378, 476, 480–481, 483–486
   _memory_provider.py930100% 
packages/foundry_hosting/agent_framework_foundry_hosting
   _invocations.py322425%31, 33–34, 36–38, 42–43, 45–51, 53, 55, 57–60, 62, 68–69
   _responses.py43214067%128–129, 138–139, 143, 148, 167, 195–197, 212–214, 225–227, 246–247, 249–251, 253–255, 259–262, 265–270, 277, 282, 285, 291–292, 294–295, 297, 299, 301–304, 306–308, 311, 315, 317–324, 327–328, 330–332, 340–345, 417–418, 523–524, 546, 810–812, 814, 832–861, 863, 881, 884, 919–923, 927–933, 940–944, 950–956, 963, 969, 972
packages/gemini/agent_framework_gemini
   _chat_client.py332199%375
packages/purview/agent_framework_purview
   _client.py128298%74–75
TOTAL29087346688% 

Python Unit Test Overview

Tests Skipped Failures Errors Time
5805 30 💤 0 ❌ 0 🔥 1m 36s ⏱️

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Update Python connector/hosting packages to emit a User-Agent that can automatically include an environment-derived prefix (notably for Foundry hosted environments), so telemetry/SDK calls can be attributed correctly.

Changes:

  • Introduce agent_framework._telemetry.get_user_agent() with hosted-environment detection and prefixing logic.
  • Replace direct uses of AGENT_FRAMEWORK_USER_AGENT across multiple provider clients/tests with get_user_agent().
  • Remove Foundry-hosting-specific user_agent_prefix(...) scoping in hosting servers in favor of automatic environment detection.

Reviewed changes

Copilot reviewed 21 out of 21 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
python/packages/core/agent_framework/_telemetry.py Adds hosted-environment detection and get_user_agent() prefix composition.
python/packages/core/tests/core/test_telemetry.py Updates tests to cover new prefix registration + hosted detection paths.
python/packages/purview/agent_framework_purview/_client.py Switches HTTP User-Agent header to get_user_agent().
python/packages/gemini/agent_framework_gemini/_chat_client.py Switches Google client header to get_user_agent().
python/packages/bedrock/agent_framework_bedrock/_embedding_client.py Switches boto user_agent_extra to get_user_agent().
python/packages/bedrock/agent_framework_bedrock/_chat_client.py Switches boto user_agent_extra to get_user_agent().
python/packages/azure-cosmos/agent_framework_azure_cosmos/_history_provider.py Switches Cosmos user_agent_suffix to get_user_agent().
python/packages/azure-cosmos/agent_framework_azure_cosmos/_checkpoint_storage.py Switches Cosmos user_agent_suffix to get_user_agent().
python/packages/azure-ai-search/agent_framework_azure_ai_search/_context_provider.py Switches Azure Search clients’ user_agent to get_user_agent().
python/packages/foundry/agent_framework_foundry/_memory_provider.py Switches Foundry project client user_agent to get_user_agent().
python/packages/foundry/agent_framework_foundry/_chat_client.py Switches Foundry project client user_agent to get_user_agent().
python/packages/foundry/agent_framework_foundry/_agent.py Switches Foundry project client user_agent to get_user_agent().
python/packages/foundry/tests/foundry/test_foundry_memory_provider.py Updates assertions to expect get_user_agent().
python/packages/foundry/tests/foundry/test_foundry_chat_client.py Updates assertions to expect get_user_agent().
python/packages/foundry_hosting/agent_framework_foundry_hosting/_invocations.py Removes per-request UA prefix context usage.
python/packages/foundry_hosting/agent_framework_foundry_hosting/_responses.py Refactors handler flow and removes per-request UA prefix context usage.
python/packages/anthropic/agent_framework_anthropic/_vertex_client.py Switches Anthropic client default headers to get_user_agent().
python/packages/anthropic/agent_framework_anthropic/_foundry_client.py Switches Anthropic client default headers to get_user_agent().
python/packages/anthropic/agent_framework_anthropic/_bedrock_client.py Switches Anthropic client default headers to get_user_agent().
python/packages/anthropic/agent_framework_anthropic/_chat_client.py Switches extra headers/default headers to get_user_agent().
python/packages/anthropic/tests/test_anthropic_provider_clients.py Updates test expectations to get_user_agent().

Comment thread python/packages/core/agent_framework/_telemetry.py Outdated
Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

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

Automated Code Review

Reviewers: 4 | Confidence: 89% | Result: All clear

Reviewed: Correctness, Security Reliability, Test Coverage, Design Approach


Automated review by TaoChenOSU's agents

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 21 out of 21 changed files in this pull request and generated 4 comments.

Comments suppressed due to low confidence (1)

python/packages/core/tests/core/test_telemetry.py:98

  • These assertions expect the User-Agent to equal AGENT_FRAMEWORK_USER_AGENT, but get_user_agent() can now prepend a hosted prefix based on process environment (e.g., FOUNDRY_HOSTING_ENVIRONMENT). That makes this test suite environment-dependent. Patch/unset FOUNDRY_HOSTING_ENVIRONMENT (and reset _hosted_env_detected) in these tests before calling prepend_agent_framework_to_user_agent() to keep them deterministic.
def test_prepend_to_empty_headers():
    """Test prepending to headers without User-Agent."""
    headers = {"Content-Type": "application/json"}
    result = prepend_agent_framework_to_user_agent(headers)

    assert "User-Agent" in result
    assert result["User-Agent"] == AGENT_FRAMEWORK_USER_AGENT
    assert "Content-Type" in result


def test_prepend_to_empty_dict():
    """Test prepending to empty headers dict."""
    headers: dict[str, str] = {}
    result = prepend_agent_framework_to_user_agent(headers)

    assert "User-Agent" in result
    assert result["User-Agent"] == AGENT_FRAMEWORK_USER_AGENT

Comment thread python/packages/core/tests/core/test_telemetry.py
Comment thread python/packages/core/tests/core/test_telemetry.py Outdated
Comment thread python/packages/core/agent_framework/_telemetry.py
@TaoChenOSU TaoChenOSU marked this pull request as ready for review April 23, 2026 22:12
Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

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

Automated Code Review

Reviewers: 3 | Confidence: 88%

✓ Correctness

The PR replaces the static AGENT_FRAMEWORK_USER_AGENT constant with a dynamic get_user_agent() function that auto-detects hosted environments. The refactoring is clean and consistent across all client packages. One correctness issue: _detect_hosted_environment() catches ModuleNotFoundError from find_spec but not the broader ImportError that CPython's find_spec can raise when parent packages exist but a sub-package has broken imports. Since get_user_agent() is now called on the critical path (client init), an uncaught ImportError would prevent application startup.

✗ Security Reliability

The refactoring from ContextVar-based user_agent_prefix to global _user_agent_prefixes with lazy hosted-environment detection is generally sound. One reliability gap: importlib.util.find_spec can raise a bare ImportError (not just ModuleNotFoundError) when a parent package exists but fails to import (e.g., broken native extension). The current except clause only catches ModuleNotFoundError, so an ImportError would propagate through get_user_agent() and crash client initialization at every call site in this diff.

✗ Design Approach

I found two design-level problems. First, the telemetry change moves Foundry-hosting detection into the core agent_framework layer by probing hosting-specific environment and SDK state, which is a leaky abstraction and changes semantics from an explicit hosting-layer tag to an inferred process-wide tag. Second, _responses.py regresses cancellation handling: the new dispatcher returns inner streams without honoring cancellation_signal, so disconnected clients can still drive long-running agent/workflow work to completion.

Flagged Issues

  • Response streaming in _handle_response no longer honors the provided cancellation_signal, so client disconnect or shutdown will continue consuming the underlying agent/workflow stream until completion, wasting resources.

Suggestions

  • Widen the find_spec exception handler from ModuleNotFoundError to ImportError (its parent class, so both are caught): except (ImportError, ValueError):.
  • Wrap the selected response stream with cancellation checks (or pass the signal through to the inner handlers) so hosting can stop work promptly when the request is cancelled.

Automated review by TaoChenOSU's agents

Copy link
Copy Markdown
Contributor

@moonbox3 moonbox3 left a comment

Choose a reason for hiding this comment

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

Automated Code Review

Reviewers: 3 | Confidence: 85%

✓ Correctness

This PR replaces the per-request ContextVar-based user_agent_prefix context manager with a process-global set and lazy _detect_hosted_environment() detection, and updates all user-agent construction sites to call get_user_agent() instead of reading a static constant. The _handle_response change in _responses.py (from async generator to sync method returning AsyncIterable) is compatible with the framework's CreateHandlerFn type and _normalize_handler_result dispatch logic — the returned AsyncGenerator from _handle_workflow_agent/_handle_regular_agent correctly passes through the isinstance/iscoroutine/hasattr checks and is consumed as an AsyncIterator. The unresolved review comment about cancellation_signal being silently dropped remains active — both _handle_regular_agent and _handle_workflow_agent have had the parameter removed in this diff and the framework's orchestrator still passes the signal to the registered handler, but the handler no longer threads it through. No new correctness bugs were found beyond that pre-existing concern.

✗ Security Reliability

This PR replaces the per-request ContextVar-based user_agent_prefix mechanism with a process-global mutable set detected lazily at startup, and removes the user_agent_prefix context-manager from the hosting layers. Two reliability issues warrant attention: (1) the unresolved carry-over concern that cancellation_signal is silently dropped in _handle_response — it is accepted in the signature but never forwarded to either inner handler — meaning client disconnects and server shutdowns will not stop the agent stream; (2) the new global _user_agent_prefixes: set[str] is mutated via _add_user_agent_prefix while concurrently iterated by sorted(_user_agent_prefixes) in get_user_agent(), which can raise RuntimeError: Set changed size during iteration in multi-threaded server scenarios (this was the subject of a previously-resolved review comment that is reintroduced by this diff).

✗ Test Coverage

The new _detect_hosted_environment tests are well-structured and cover the main paths, but test_detect_hosted_fallback_import_error is environment-dependent: it patches builtins.__import__ without also mocking importlib.util.find_spec, so in environments without azure.ai.agentserver.core installed the function exits early and the except (ImportError, AttributeError) branch is never reached. Additionally, the foundry-package assertions use get_user_agent() on both sides of the equality check, which means they cannot catch a regression to the static AGENT_FRAMEWORK_USER_AGENT constant in non-hosted environments.


Automated review by moonbox3's agents

Comment thread python/packages/foundry/tests/foundry/test_foundry_chat_client.py
@TaoChenOSU TaoChenOSU enabled auto-merge April 23, 2026 23:38
@TaoChenOSU TaoChenOSU added this pull request to the merge queue Apr 23, 2026
Merged via the queue into main with commit 0989e68 Apr 23, 2026
34 checks passed
moonbox3 added a commit to moonbox3/agent-framework that referenced this pull request Apr 24, 2026
Previous commit incorrectly renamed the [1.1.1] header to [1.2.0], which
wiped the historical 1.1.1 entries and wrongly attributed them to 1.2.0.
This restores [1.1.1] to its origin/main content and adds a new [1.2.0]
section above containing only the commits in python-1.1.1..HEAD:

- microsoft#4238 functional workflow API
- microsoft#5142 GitHub Copilot OpenTelemetry
- microsoft#2403 A2A bridge support
- microsoft#5070 oauth_consent_request events in Foundry clients
- microsoft#5447 FoundryAgent hosted agent sessions
- microsoft#5459 hosting server dependency upgrade + types
- microsoft#5389 AG-UI reasoning/multimodal parsing fix
- microsoft#5440 stop [TOOLBOXES] warning spam
- microsoft#5455 user agent prefix fix

Also corrects the [1.2.0] compare base to python-1.1.1 (not 1.1.0) and
adds the missing [1.1.1] reference link.
moonbox3 added a commit that referenced this pull request Apr 24, 2026
* Bump Python package versions for 1.2.0 release

Released tier bumps 1.1.1 -> 1.2.0 (core, openai, foundry, root) to
reflect additive public APIs landed since 1.1.0: functional workflow API
(#4238) and FunctionTool SKIP_PARSING sentinel (#5424). All beta packages
stamped 1.0.0b260424, alpha packages 1.0.0a260424. All 26 non-core
agent-framework-core floors raised to >=1.2.0,<2. CHANGELOG consolidates
the never-tagged 1.1.1 entries with the post-merge additions into [1.2.0].

* Update CHANGELOG footer links for 1.2.0

Advance [Unreleased] comparison base from python-1.1.0 to python-1.2.0
and add a [1.2.0] reference link comparing python-1.1.0...python-1.2.0
so the heading links resolve correctly.

* Fix CHANGELOG: restore [1.1.1] section and add proper [1.2.0]

Previous commit incorrectly renamed the [1.1.1] header to [1.2.0], which
wiped the historical 1.1.1 entries and wrongly attributed them to 1.2.0.
This restores [1.1.1] to its origin/main content and adds a new [1.2.0]
section above containing only the commits in python-1.1.1..HEAD:

- #4238 functional workflow API
- #5142 GitHub Copilot OpenTelemetry
- #2403 A2A bridge support
- #5070 oauth_consent_request events in Foundry clients
- #5447 FoundryAgent hosted agent sessions
- #5459 hosting server dependency upgrade + types
- #5389 AG-UI reasoning/multimodal parsing fix
- #5440 stop [TOOLBOXES] warning spam
- #5455 user agent prefix fix

Also corrects the [1.2.0] compare base to python-1.1.1 (not 1.1.0) and
adds the missing [1.1.1] reference link.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

4 participants