Visitar URL original
Python: preserve sources for toolbox citations by sophia-ramsey · Pull Request #10327 · microsoft/agent-framework · GitHub
Skip to content

Python: preserve sources for toolbox citations - #10327

Open
sophia-ramsey wants to merge 6 commits into
mainfrom
sramsey/python-toolbox-citations
Open

sophia-ramsey wants to merge 6 commits into
mainfrom
sramsey/python-toolbox-citations

Conversation

@sophia-ramsey

Copy link
Copy Markdown
Member

Motivation & Context

Python hosted agents using Foundry Toolbox can complete web searches but return final answers without citations, even when the Toolbox returns citation metadata. The default MCP result handling also drops the search text, leaving the model with citation JSON instead.

Description & Review Guide

  • What are the major changes?
    Core MCP parsing now recognizes URL citations and, for cited results using the default structured_first mode, preserves the search text alongside the structured result. Foundry hosting collects those sources and attaches them to the final answer at its end, without claiming they support a specific passage.
  • What is the impact of these changes?
    Foundry Toolbox agents and generic MCP tools connected to Toolbox can return clickable sources in hosted responses, while native web search behavior remains unchanged. This applies to the default result mode; custom parsing or another result mode can opt out of the core handling.
  • What do you want reviewers to focus on?
    The solution approach to include cited sources grouped at the end of the text due to lack of start/end information for sources, compared to native web search which has access to that information
# Toolbox web search with this fix
Mars is red because of iron oxide dust. Its thin atmosphere
scatters red light, giving the sky a butterscotch tint.[1][2]

Sources
[1] Mars Facts – nasa.gov
[2] ESA Mars – esa.int

# Native web search
Mars is red because of iron oxide dust.[1] Its thin atmosphere
scatters red light, giving the sky a butterscotch tint.[2]

Sources
[1] Mars Facts – nasa.gov
[2] ESA Mars – esa.gov

Related Issue

Fixes #10325

AI Assistance

  • No material AI assistance was used.
  • This is an AI-assisted contribution. I reviewed, understood, and verified all submitted content and accept responsibility for it.

AI assistance details: planning, implementation, testing

Contribution Checklist

  • The code builds clean without any errors or warnings
  • All unit tests pass, and I have added new tests where possible
  • The PR follows the Contribution Guidelines
  • This PR links to an agreed issue with no competing open PR, or the Related Issue section documents a trivial-change or repository-automation exception.
  • This is not a breaking change. If it is a breaking change, add the breaking change label (or add "[BREAKING]" to the title prefix, before or after any language prefix) — a workflow keeps the label and title prefix in sync automatically.

Copilot AI balanced review requested due to automatic review settings November 13, 2026 00:39
@sophia-ramsey
sophia-ramsey deployed to github-app-auth November 13, 2026 00:39 — with GitHub Actions Active
@sophia-ramsey
sophia-ramsey deployed to github-app-auth November 13, 2026 00:40 — with GitHub Actions Active
@agent-framework-automation agent-framework-automation Bot added documentation Usage: [Issues, PRs], Target: documentation in the code base and learn docs python Usage: [Issues, PRs], Target: Python labels Nov 13, 2026
@github-actions github-actions Bot changed the title Sramsey/python toolbox citations Python: Sramsey/python toolbox citations Nov 13, 2026
@sophia-ramsey sophia-ramsey changed the title Python: Sramsey/python toolbox citations Python: preserve sources for toolbox citations Nov 13, 2026
@sophia-ramsey
sophia-ramsey deployed to github-app-auth November 13, 2026 00:42 — with GitHub Actions Active
@github-code-quality

github-code-quality Bot commented Nov 13, 2026 •

Copy link
Copy Markdown
Contributor

Code coverage overview

Languages: Python

Python / code-coverage/python

main 4f2d146 sramsey/python-toolb... 8223cd1 +/-
92% 92% 0%
View coverage of the 3 most impacted files.
File main 4f2d146 sramsey/python-toolb... 8223cd1 +/-
packages/foundr...g/_responses.py 89% 89% 0%
packages/core/a...amework/_mcp.py 92% 92% 0%
packages/foundr...ow_responses.py 83% 84% +1%

ℹ️ Calculations represent line coverage. Results may change as calculations are updated.


Updated November 13, 2026 05:17 UTC

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.

🟡 Changes recommended

Completed resilient workflows lose URL citations, and deduplication checks only the last text part, allowing duplicate citations when earlier parts already cite the source.

2 open issues
What changed in this PR

This PR addresses #10325 by preserving MCP search text and citation metadata, then exposing retrieved sources on final Foundry-hosted answers.

Changes:

  • Preserve cited search text alongside structured results in the default MCP parsing mode.
  • Collect tool-result URLs and attach them as deduplicated, end-of-answer citations.
  • Add parsing, hosting, streaming, and recovery tests, plus documentation of citation behavior and opt-outs.
File Description
python/​packages/​foundry_hosting/​tests/​test_toolbox.py Tests citation parsing, result modes, and persistence through agent execution.
python/​packages/​foundry_hosting/​tests/​test_responses.py Tests hosted citations across background responses, streaming, and recovery.
python/​packages/​foundry_hosting/​README.md Documents citation handling and end-of-answer source placement.
python/​packages/​foundry_hosting/​agent_framework_foundry_hosting/​_toolbox.py Adds docstring guidance on core citation handling.
python/​packages/​foundry_hosting/​agent_framework_foundry_hosting/​_responses.py Collects, persists, and attaches tool-result sources to final answers.
python/​packages/​core/​tests/​core/​test_mcp.py Tests default citation projection and error-result exclusion.
python/​packages/​core/​agent_framework/​_mcp.py Preserves cited text and converts MCP citations into framework annotations.

Review effort: Balanced

return

for event in tracker.close():
for event in tracker.close(final_answer=True):

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Addressed in ade86492f1669c25f670daa315e03051358caff1

@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.

MAF Automated Review — Iteration 1

Result: Findings reported
Scope: full PR (3 commit(s)): 18470bd88764, 30a008494587, bd305cd2d728
Model: gpt-5.6-sol

Overview

The PR preserves cited MCP prose, validates citation shapes and spans, and carries deduplicated HTTP(S) sources through hosted-response recovery to the final answer. Its tests cover block ownership, UTF-16 offsets, interim answers, provider-background completion, recovery, and stream consistency. Residual risks are unbounded citation accumulation, quadratic span conversion, and a native-workflow finalization path that never opts into source attachment.

Reviewed the supplied pull-request change set across correctness, security/reliability, architecture, and failure behavior.
3 verified findings remained after source verification (1 high, 2 medium) across 2 files. Details are attached to the affected lines below.

Affected areas: python/packages/core/agent_framework/_mcp.py, python/packages/foundry_hosting/agent_framework_foundry_hosting/_responses.py

url_citations = _url_citations_from_function_result(content)
if url_citations:
for url, title in url_citations.items():
self._pending_url_citations.setdefault(url, title)

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.

A function-result producer can supply arbitrarily many unique URLs and arbitrarily long titles here. Each entry is retained in memory, copied into durable metadata outside the public metadata quota, and later emitted as its own annotation event; cancellation and workflow paths can also preserve that state. This lets untrusted tool output exhaust process or shared backing-store capacity and affect unrelated responses. Please enforce per-result and per-response count/byte budgets before adding or persisting citations, with deterministic overflow behavior.

@sophia-ramsey sophia-ramsey Nov 13, 2026 •

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Addressed in ade86492f1669c25f670daa315e03051358caff1: Added an upper limit for sources -- currently capped at 100 but can easily decrease this, if anyone has any thoughts. Added a limit for title length too.

@sophia-ramsey
sophia-ramsey deployed to github-app-auth November 13, 2026 01:15 — with GitHub Actions Active
@sophia-ramsey
sophia-ramsey force-pushed the sramsey/python-toolbox-citations branch from 69cdbf0 to 98fc4a8 Compare November 13, 2026 02:51
@sophia-ramsey
sophia-ramsey deployed to github-app-auth November 13, 2026 02:51 — with GitHub Actions Active
@sophia-ramsey
sophia-ramsey deployed to github-app-auth November 13, 2026 03:44 — with GitHub Actions Active
@sophia-ramsey
sophia-ramsey deployed to github-app-auth November 13, 2026 06:29 — with GitHub Actions Active

@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.

MAF Automated Review — Iteration 2

Result: Findings reported
Scope: 3 net-new commit(s): 98fc4a83d5fd, ade86492f166, 8223cd1d2fc1
Model: gpt-5.6-sol

Overview

The PR preserves MCP citation provenance through core parsing and Foundry hosting, with strong validation, UTF-16 conversion, persisted-state limits, and coverage for ordinary, streaming, recovery, and workflow paths. Two residual gaps remain: native workflow source attachment depends on the final buffered event being an output, and core MCP citation projection has no per-result allocation budget before hosting applies its limits. Both can cause meaningful but bounded failures under specific event ordering or malicious MCP responses.

Reviewed the supplied incremental change set across correctness, security/reliability, architecture, and failure behavior.
2 verified findings remained after source verification (2 medium) across 2 files. Details are attached to the affected lines below.

Affected areas: python/packages/core/agent_framework/_mcp.py, python/packages/foundry_hosting/agent_framework_foundry_hosting/_workflow_responses.py

for event in buffered:
events.extend(await _project(event, draft, next_tracker, approval_projection))
events.extend(next_tracker.close())
is_final_output = final_answer and bool(buffered) and buffered[-1].type == "output"

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.

If a terminal superstep emits the final output and then a legacy data event, this predicate is false. _project() can already close the answer while processing that trailing data, and the next line then closes without attaching pending sources before they are discarded, so a successful final answer loses its Toolbox citations. Please attach sources to the final output message even when later non-answer events are buffered.

*structured_annotations,
*(value for values in block_annotations for value in values),
]
for value in all_annotations:

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.

An MCP server can put arbitrarily many unique URL citations in structured or block annotations. This loop materializes each into Annotation, set, and list state before Foundry hosting's later 100-source/64-KiB cap, and generic MCP users never reach that cap, so one tool response can consume excessive CPU and process memory. Please enforce per-result count and byte budgets while scanning annotations, before constructing citation objects or flattened lists.

This branch was successfully deployed

1 active deployment
github-app-auth — 8223cd1d Deployed Nov 13, 2026 by sophia-ramsey via team_check #9418
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Usage: [Issues, PRs], Target: documentation in the code base and learn docs python Usage: [Issues, PRs], Target: Python

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Python: [Bug]: Foundry Toolbox web search citations are missing from hosted agent responses

3 participants