-
Notifications
You must be signed in to change notification settings - Fork 269
refactor: Upgrade agent-framework and azure-ai-projects libraries with API changes #927
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
bf1906a
chore: upgrade agent-framework to 1.3.0 and azure-ai-projects to 2.1.0
Harsh-Microsoft d3071eb
chore: pin agent-framework-openai==1.3.0 explicitly
Harsh-Microsoft e7801ad
Merge branch 'dev' into psl-hb-us-43549
Harsh-Microsoft e76b205
Merge branch 'dev' into psl-hb-us-43549
Akhileswara-Microsoft File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,3 @@ | ||
| aiohttp==3.13.4 | ||
| azure-identity==1.25.2 | ||
| azure-ai-projects==2.0.0b3 | ||
| azure-ai-projects==2.1.0 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| """Runtime patches applied to third-party packages used by this app.""" |
175 changes: 175 additions & 0 deletions
175
src/api/services/_patches/agent_framework_search_citations.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,175 @@ | ||
| """Restore Azure AI Search ``get_url`` enrichment on streaming citations. | ||
|
|
||
| Pre-GA ``agent-framework-azure-ai==1.0.0rc2`` enriched ``url_citation`` | ||
| streaming annotations with a per-document REST URL exposed under | ||
| ``additional_properties.get_url``. That subclass was removed when the | ||
| ``azure-ai`` package was retired at GA. | ||
|
|
||
| In the GA ``agent_framework_openai._chat_client._parse_chunk_from_openai``: | ||
|
|
||
| 1. ``response.azure_ai_search_call_output.done`` events fall through to | ||
| the default ``case _:`` debug log, so the ``output.get_urls[]`` array | ||
| carrying per-document REST URLs is silently dropped. | ||
| 2. ``url_citation`` annotations (added by upstream PR #5071) emit only | ||
| ``title`` and ``url`` (the search-service root URL). | ||
|
|
||
| This patch wraps that method to: | ||
|
|
||
| 1. Cache ``get_urls`` per-stream when seeing the search-call-output event. | ||
| 2. Inject ``additional_properties.get_url`` on ``url_citation`` annotations | ||
| using ``annotation_index`` as the lookup key, matching the pre-GA | ||
| contract that the citation extraction in ``chat_service.py`` reads. | ||
|
|
||
| Tracking upstream: https://github.com/microsoft/agent-framework/issues/5995 | ||
| Safe to remove once upstream ports the ``get_url`` enrichment into | ||
| ``agent_framework_openai`` or ``agent_framework_foundry``. | ||
| """ | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| import logging | ||
| import re | ||
| from typing import Any | ||
|
|
||
| _DOC_INDEX_RE = re.compile(r"^doc_(\d+)$") | ||
|
|
||
| logger = logging.getLogger(__name__) | ||
|
|
||
| _PATCH_APPLIED = False | ||
| _CACHE_ATTR = "_kmsa_search_get_urls_cache" | ||
| _TARGET_METHOD = "_parse_chunk_from_openai" | ||
| _TARGET_CLASS = "RawOpenAIChatClient" | ||
| _UPSTREAM_ISSUE = "https://github.com/microsoft/agent-framework/issues/5995" | ||
|
|
||
|
|
||
| def apply() -> None: | ||
| """Idempotently patch RawOpenAIChatClient._parse_chunk_from_openai. | ||
|
|
||
| Safe to call multiple times; the second call is a no-op. | ||
| Logs a warning (does not raise) if upstream has renamed the target, | ||
| so app startup still succeeds with degraded citations. | ||
| """ | ||
| global _PATCH_APPLIED | ||
| if _PATCH_APPLIED: | ||
| return | ||
|
|
||
| try: | ||
| from agent_framework_openai import _chat_client as _cc | ||
| except ImportError: | ||
| logger.warning( | ||
| "agent_framework_openai not installed; " | ||
| "Azure AI Search citation patch skipped" | ||
| ) | ||
| return | ||
|
|
||
| target_cls = getattr(_cc, _TARGET_CLASS, None) | ||
| if target_cls is None or not hasattr(target_cls, _TARGET_METHOD): | ||
| logger.warning( | ||
| "agent-framework upgrade broke citation patch: %s.%s no longer exists. " | ||
| "Per-document URLs (get_url) will be missing on Azure AI Search " | ||
| "citations. See %s", | ||
| _TARGET_CLASS, _TARGET_METHOD, _UPSTREAM_ISSUE, | ||
| ) | ||
| return | ||
|
|
||
| _original = getattr(target_cls, _TARGET_METHOD) | ||
|
|
||
| def _patched(self: Any, event: Any, *args: Any, **kwargs: Any) -> Any: | ||
| event_type = getattr(event, "type", None) | ||
|
|
||
| # Reset per-stream cache so back-to-back requests on the same client | ||
| # instance don't cross-pollute citation enrichment. | ||
| if event_type in ("response.created", "response.in_progress"): | ||
| setattr(self, _CACHE_ATTR, []) | ||
|
|
||
| # Capture get_urls from azure_ai_search_call_output items on .done. | ||
| # The .added event for this item has output='[]'; the .done event | ||
| # carries the actual documents + get_urls. | ||
| if event_type == "response.output_item.done": | ||
| try: | ||
| done_item = getattr(event, "item", None) | ||
| if getattr(done_item, "type", None) == "azure_ai_search_call_output": | ||
| output = getattr(done_item, "output", None) | ||
| get_urls = getattr(output, "get_urls", None) | ||
| if get_urls is None and isinstance(output, dict): | ||
| get_urls = output.get("get_urls") | ||
| if get_urls is None and isinstance(output, str): | ||
| # Some SDK versions deliver `output` as a JSON string. | ||
| import json as _json | ||
| try: | ||
| parsed = _json.loads(output) | ||
| if isinstance(parsed, dict): | ||
| get_urls = parsed.get("get_urls") | ||
| except Exception: # noqa: BLE001 | ||
| pass | ||
| if get_urls: | ||
| cache = getattr(self, _CACHE_ATTR, None) | ||
| if cache is None: | ||
| cache = [] | ||
| setattr(self, _CACHE_ATTR, cache) | ||
| cache.extend(get_urls) | ||
| except Exception: # noqa: BLE001 - defensive: never break streaming | ||
| logger.debug( | ||
| "search-citation patch: failed to capture get_urls", | ||
| exc_info=True, | ||
| ) | ||
|
|
||
| result = _original(self, event, *args, **kwargs) | ||
|
|
||
| # Enrich url_citation annotations emitted by the base method's | ||
| # response.output_text.annotation.added branch. | ||
| if event_type == "response.output_text.annotation.added": | ||
| try: | ||
| cache = getattr(self, _CACHE_ATTR, None) or [] | ||
| if cache: | ||
| for content in (getattr(result, "contents", None) or []): | ||
| for ann in (getattr(content, "annotations", None) or []): | ||
| if not isinstance(ann, dict): | ||
| continue | ||
| if ann.get("type") != "citation": | ||
| continue | ||
|
Akhileswara-Microsoft marked this conversation as resolved.
|
||
| add_props = ann.get("additional_properties") or {} | ||
| # Idempotent: do not overwrite if upstream ever ships | ||
| # the fix and starts populating get_url itself. | ||
| if add_props.get("get_url"): | ||
| continue | ||
| # Map by title "doc_<N>" where N is the index into | ||
| # the search results (and thus into get_urls). The | ||
| # model can cite the same doc multiple times, so | ||
| # annotation_index (a running counter) is unreliable. | ||
| title = ann.get("title") or "" | ||
| m = _DOC_INDEX_RE.match(str(title)) | ||
| doc_idx = int(m.group(1)) if m else None | ||
| if doc_idx is None: | ||
| doc_idx = add_props.get("annotation_index") | ||
| if isinstance(doc_idx, int) and 0 <= doc_idx < len(cache): | ||
| add_props["get_url"] = cache[doc_idx] | ||
| ann["additional_properties"] = add_props | ||
| except Exception: # noqa: BLE001 | ||
| logger.debug( | ||
| "search-citation patch: failed to enrich annotation", | ||
| exc_info=True, | ||
| ) | ||
|
|
||
| # Release per-stream state once the response completes. | ||
| if event_type == "response.completed": | ||
| try: | ||
| if hasattr(self, _CACHE_ATTR): | ||
| delattr(self, _CACHE_ATTR) | ||
| except Exception: # noqa: BLE001 | ||
| pass | ||
|
|
||
| return result | ||
|
|
||
| setattr(target_cls, _TARGET_METHOD, _patched) | ||
| _PATCH_APPLIED = True | ||
| logger.info( | ||
| "Applied Azure AI Search citation patch on %s.%s (workaround for %s)", | ||
| _TARGET_CLASS, _TARGET_METHOD, _UPSTREAM_ISSUE, | ||
| ) | ||
|
|
||
|
|
||
| # Apply on import so a single | ||
| # `import services._patches.agent_framework_search_citations` | ||
| # from chat_service.py is enough. | ||
| apply() | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.