Skip to content

Fix generated artifact paging, truncation disclosure, and guidance carry-forward - #1317

Merged
Paul Lizer (paullizer) merged 2 commits into
microsoft:Developmentfrom
paullizer:fix/generated-artifact-paging-and-guidance-carryforward
Aug 19, 2026
Merged

Fix generated artifact paging, truncation disclosure, and guidance carry-forward#1317
Paul Lizer (paullizer) merged 2 commits into
microsoft:Developmentfrom
paullizer:fix/generated-artifact-paging-and-guidance-carryforward

Conversation

@paullizer

Copy link
Copy Markdown
Contributor

Summary

A deployment test of the generated-file work from 0.260.004 through 0.260.010 surfaced three defects in agent conversations that produced CSV artifacts from a truncating telemetry action.

The deployment log confirmed all three causes:

list_parameter_history  start=20:40:57.445  stop=20:55:57.445  row_count=500  truncated=True
list_parameter_history  start=20:40:57.445  stop=20:49:17.445  row_count=500  truncated=True

Both calls share a start time. The agent did not page forward, it re-requested the same window with an earlier stop, and both responses were capped at the action's own max_rows.

1. The model denied it could create a file, then the file appeared anyway

After answering a schema clarification with "yes and all columns", the assistant replied "I cannot create or attach a CSV file in this interface" and listed the columns as prose. The server published the CSV regardless.

0.260.008 added resolve_pending_generated_file_format so a reply that only answers a clarification still publishes the originally requested artifact. Guidance injection was never updated to match. Both chat paths resolved the guidance format from the current user message alone:

requested_format=get_tabular_generated_output_format(user_message)

"yes and all columns" contains no format keyword, so build_generated_file_output_guidance returned an empty string and no system message was added. The model was never told the server attaches the file, so it fell back to its default belief that it cannot produce attachments, while the finalizer, which did carry the format forward, published the CSV.

Both paths now resolve through a new helper:

def _resolve_generated_file_guidance_format(user_question, conversation_id, user_id):
    requested_format = str(get_tabular_generated_output_format(user_question) or '').strip().lower()
    if requested_format or not conversation_id:
        return requested_format
    pending_format = _resolve_pending_generated_file_format(user_question, conversation_id, user_id)
    return str(pending_format or '').strip().lower()

In the streaming path it also drives payload suppression and the file status banner, so the answer turn behaves like the turn that made the original request.

2. A 1,000-row file for a window holding roughly 500 distinct samples

0.260.008 grouped rows by action so paged calls stayed one dataset, using .extend(). Because the second call re-read the same window, concatenation doubled the data. The same pattern explains the earlier "901 acquired samples" claim (500 truncated + 401 overlapping).

_collect_authorized_function_row_groups now tracks a row signature per action label. Repeats inside one response are kept, because the action counted them as distinct records. Rows an earlier page of the same action already contributed are dropped.

3. A partial file published with no indication it was partial

Action payloads carried truncated: true, but nothing read it. _assistant_content_disclaims_complete_file existed for JSON and XML only and matched assistant prose rather than the action's own report.

function_results_report_truncated_rows now scans row-contributing payloads for truncated, is_truncated, was_truncated, results_truncated, and rows_truncated. The flag flows into the export payload, summary text, and artifact metadata, and is captured separately for reach-back rows so a file built from an earlier turn keeps that turn's signal. The completed-artifact card layout hides the summary, so partial coverage also renders a Partial badge.

CSV, DOCX, and PDF guidance now adds:

When an action reports that its results were truncated, request the remaining data with a window that starts after the last row you already have instead of repeating the original range, and say plainly that the data is partial if you cannot retrieve the rest.

JSON and XML are excluded because their guidance requires a payload-only reply.

The 500-row cap is the action's configured max_rows, not a SimpleChat limit. This change makes the cap visible and teaches the model to page past it rather than silently altering plugin behavior.

Fixture correction

TELEMETRY_ROWS in test_generated_csv_uses_authorized_action_rows.py derived timestamps from index % 60, so its 900 "samples" were only 60 distinct rows repeated fifteen times. That made genuine continuation pages indistinguishable from re-reads and would have masked this class of bug. Timestamps now roll over into minutes, matching the 15-minute high-granularity window the fixture's question asks for.

Behavior

Scenario Before After
Answering a schema clarification No file guidance injected, model denies it can attach files, server publishes anyway Publication contract injected, reply and artifact agree
Two calls re-reading one window 1,000 rows, roughly half duplicated Distinct rows only
Two calls paging forward Both pages kept Both pages kept (unchanged)
Action reports truncated=True Silent, file looks complete Partial badge, summary note, rows_truncated on the artifact
Repeated identical rows in one response All kept All kept (unchanged)

Validation

Suite Result
test_generated_artifact_paging_and_guidance.py 11/11
test_generated_csv_uses_authorized_action_rows.py 15/15
test_generated_structured_artifact_parity.py 8/8
test_assistant_table_csv_artifact.py 36/36
test_generated_json_xml_exports.py 7/7
test_tabular_passthrough_heterogeneous_rows.py 4/4
test_tabular_background_generated_exports.py 8/8
test_tabular_row_orchestration_scale.py 15/15
route_tests/test_route_blueprint_policy_inventory.py 6/6
route_tests/test_route_unauthenticated_policy_contract.py 4/4

Known limitations

  • De-duplication compares full row values. A continuation page whose rows differ only in a field the action omits from its response would still be treated as a repeat.
  • The paging instruction is model guidance, not enforcement. An agent that ignores it still produces a truncated dataset, but the file is now labeled partial.
  • The per-call row cap belongs to the action's own configuration. Raising it is a plugin configuration change, not an application change.

Version

0.260.010 -> 0.260.011

Documentation: docs/explanation/fixes/GENERATED_ARTIFACT_PAGING_AND_GUIDANCE_FIX.md

…rry-forward

A deployment test surfaced three defects in agent conversations producing CSV
artifacts from a truncating telemetry action.

The model replied "I cannot create or attach a CSV file in this interface" on
the turn that answered a schema clarification, yet the server published the CSV.
Version 0.260.008 carried the pending format forward for publication but not for
guidance, which both chat paths still resolved from the current user message
alone. A clarification answer names no format, so no publication contract ever
reached the model. Both paths now resolve guidance through
_resolve_generated_file_guidance_format, which falls back to the pending format.

A 1,000-row file was produced for a window holding roughly 500 distinct samples.
The agent re-requested the same window with an earlier stop time rather than
paging forward, and action grouping concatenated both responses. Rows an earlier
page of the same action already contributed are now dropped; repeats inside a
single response are kept, because the action counted them as distinct records.

The source action reported truncated results and the file gave no indication it
was partial. Truncation is now detected from the action payload, propagated
through the export payload, summary, and artifact metadata for every format, and
surfaced as a Partial badge on the artifact card. CSV, DOCX, and PDF guidance now
tells the model to page from the last row it holds instead of repeating the range.

The telemetry fixture in test_generated_csv_uses_authorized_action_rows derived
timestamps from index % 60, so its 900 samples were 60 distinct rows repeated
fifteen times, making continuation pages indistinguishable from re-reads.
Timestamps now roll over into minutes, matching the fixture's 15-minute window.

The per-call row cap belongs to the action's own configuration and is unchanged.
@paullizer
Paul Lizer (paullizer) merged commit 76b3059 into microsoft:Development Aug 19, 2026
9 of 10 checks passed
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.

1 participant