Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion application/single_app/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@
EXECUTOR_TYPE = 'thread'
EXECUTOR_MAX_WORKERS = 30
SESSION_TYPE = 'filesystem'
VERSION = "0.260.024"
VERSION = "0.260.025"
IS_DEVELOPMENT = is_development_env_enabled()

SESSION_COOKIE_SAMESITE = os.getenv('SESSION_COOKIE_SAMESITE', 'Lax')
Expand Down
38 changes: 28 additions & 10 deletions application/single_app/functions_mixed_source_orchestration.py
Original file line number Diff line number Diff line change
Expand Up @@ -412,7 +412,15 @@


def should_run_tabular_evidence(user_question, has_narrative_sources=False):
"""Return whether a mixed-source question needs tabular data or schema evidence."""
"""Return whether an in-scope tabular source should be computed for this question.

Evidence gathering is additive. When an authorized tabular source is in scope the
tabular engine runs unless the question unambiguously targets a narrative artifact,
because deciding which evidence is relevant belongs to the synthesis step rather
than to this gate. Indexed tabular chunks carry only a truncated schema preview, so
skipping computation leaves the model with a handful of preview rows that can never

Check warning on line 421 in application/single_app/functions_mixed_source_orchestration.py

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains security control, sanitization, or audit marker. Recommendation%3A Confirm the change does not weaken auth, CSRF, CSP, XSS defenses, settings sanitization, redaction, audit logging, or tests.
support a numeric conclusion.
"""
normalized_question = " ".join(str(user_question or "").strip().lower().split())
if not normalized_question:
return True
Expand All @@ -430,24 +438,22 @@
"across the files", "across the documents", "across the sources",
"mixed sources",
)
narrative_markers = (
# Only artifact markers suppress computation. Topic words such as "report" or
# "policy" describe subject matter, not which engine can answer, and previously

Check warning on line 442 in application/single_app/functions_mixed_source_orchestration.py

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains security control, sanitization, or audit marker. Recommendation%3A Confirm the change does not weaken auth, CSRF, CSP, XSS defenses, settings sanitization, redaction, audit logging, or tests.
# suppressed computation over spreadsheets that held the requested values.
narrative_artifact_markers = (
"pdf", "docx", "word document", "presentation", "powerpoint",
"paragraph", "section", "policy", "procedure", "contract",
"agreement", "memo", "letter", "narrative", "prose", "report",
"paragraph", "section",

Check warning on line 446 in application/single_app/functions_mixed_source_orchestration.py

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains secret or sensitive data source marker. Recommendation%3A Pair this source with any nearby network, logging, serialization, or process execution sink before approving.
)

if any(marker in normalized_question for marker in tabular_markers):
return True
if any(marker in normalized_question for marker in collective_markers):
return True
if has_narrative_sources and any(
marker in normalized_question for marker in narrative_markers
marker in normalized_question for marker in narrative_artifact_markers
):
return False
if normalized_question in {"summarize", "summary", "summarize the selected sources"}:
return True
if has_narrative_sources:
return False
return True


Expand Down Expand Up @@ -1413,7 +1419,14 @@
source_kind=SOURCE_KIND_TABULAR,
engine=EVIDENCE_ENGINE_TABULAR_TOOLS,
status=EVIDENCE_STATUS_SKIPPED,
summary="Tabular processing was not needed for this narrative-only request.",
summary=(
"Tabular computation was not run for this source, so its full table was "
"never read. Any indexed excerpt from this source contains only a "
"truncated schema preview of the first few rows. Do not derive counts, "
"totals, averages, minimums, maximums, trends, or any other numeric "
"conclusion from those preview rows. Call the tabular analysis action if "
"values from this source are required."
),
coverage={
"selection_mode": normalized_selection_mode,
"terminal": True,
Expand Down Expand Up @@ -1921,6 +1934,11 @@
"and tabular tool citations; do not convert computed table facts into unsupported narrative claims. "
"When selection_mode is selected, current selected-source evidence supersedes prior document "
"grounding; do not use prior source claims to fill missing current coverage. "
"This handoff is your starting evidence, not your only means of gathering evidence: if you have "
"actions available and this handoff does not contain what the question needs, call the appropriate "
"action to obtain it and reason over the handoff and the action results together before answering. "
"Never derive numeric conclusions from an indexed preview of a tabular source whose evidence status "
"is not completed; obtain those values from a computed tabular result instead. "
f"{partial_coverage_instruction}\n\n{serialized_payload}"
),
"mixed_source_coverage": coverage,
Expand Down
4 changes: 3 additions & 1 deletion application/single_app/route_backend_chats.py
Original file line number Diff line number Diff line change
Expand Up @@ -4523,7 +4523,9 @@
Retrieved Excerpts:
{retrieved_content}

Base your answer only on information supported by the retrieved excerpts and any computed tool-backed results included elsewhere in this conversation context. If the answer is not supported by that information, say so.
These excerpts are your starting evidence, not your only means of gathering evidence. If you have actions or tools available and the excerpts do not contain what the question needs, call the appropriate action to obtain it, then reason over the retrieved excerpts and the action results together. Gather the evidence you are capable of gathering before declining to answer.

Check warning on line 4526 in application/single_app/route_backend_chats.py

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains AI, plugin, agent, or workspace boundary marker. Recommendation%3A Check whether prompts, chat history, uploaded documents, embeddings, citations, settings, or identity can cross a new boundary.
Ground every claim in a retrieved excerpt, in computed tool-backed results included elsewhere in this conversation context, or in a result you obtained by calling an action. Never estimate, infer, or fabricate values that none of those sources support; if the evidence is still missing after you have used the actions available to you, say so.

Check warning on line 4527 in application/single_app/route_backend_chats.py

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains AI, plugin, agent, or workspace boundary marker. Recommendation%3A Check whether prompts, chat history, uploaded documents, embeddings, citations, settings, or identity can cross a new boundary.
Excerpts drawn from a spreadsheet or other tabular source contain only a truncated schema preview of that file, not its data. Never derive counts, totals, averages, minimums, maximums, trends, or any other numeric conclusion from those preview rows; obtain such values from a computed tabular result instead.
If computed tabular results are provided in another system message, treat them as authoritative for row-level values, calculations, and numeric conclusions. Do not say that you lack direct access to the data when those computed results are present.

Example
Expand Down
175 changes: 175 additions & 0 deletions docs/explanation/fixes/AGENT_ACTIONS_WITH_WORKSPACE_EVIDENCE_FIX.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
# Agent Actions Ignored When Workspace Evidence Is Present

Check warning on line 1 in docs/explanation/fixes/AGENT_ACTIONS_WITH_WORKSPACE_EVIDENCE_FIX.md

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains AI, plugin, agent, or workspace boundary marker. Recommendation%3A Check whether prompts, chat history, uploaded documents, embeddings, citations, settings, or identity can cross a new boundary.

**Fixed in version: 0.260.025**

**Issue:** [#1332](https://github.com/microsoft/simplechat/issues/1332)

Check warning on line 5 in docs/explanation/fixes/AGENT_ACTIONS_WITH_WORKSPACE_EVIDENCE_FIX.md

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains external connection or remote asset marker. Recommendation%3A Review whether changed code can send prompts, files, credentials, cookies, settings, logs, or user data to a new sink.

**Related:** [#1021](https://github.com/microsoft/simplechat/issues/1021) — turn-level orchestration across chat capabilities, the strategic solution to this class of problem. This fix addresses the concrete symptom and does not close that initiative.

Check warning on line 7 in docs/explanation/fixes/AGENT_ACTIONS_WITH_WORKSPACE_EVIDENCE_FIX.md

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains external connection or remote asset marker. Recommendation%3A Review whether changed code can send prompts, files, credentials, cookies, settings, logs, or user data to a new sink.

## Issue

Selecting an agent that has actions, enabling a workspace, and asking a specific

Check warning on line 11 in docs/explanation/fixes/AGENT_ACTIONS_WITH_WORKSPACE_EVIDENCE_FIX.md

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains AI, plugin, agent, or workspace boundary marker. Recommendation%3A Check whether prompts, chat history, uploaded documents, embeddings, citations, settings, or identity can cross a new boundary.
quantitative question produced an answer that:

1. never invoked any of the agent's actions, and

Check warning on line 14 in docs/explanation/fixes/AGENT_ACTIONS_WITH_WORKSPACE_EVIDENCE_FIX.md

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains AI, plugin, agent, or workspace boundary marker. Recommendation%3A Check whether prompts, chat history, uploaded documents, embeddings, citations, settings, or identity can cross a new boundary.
2. reported numbers that were not actually present in the spreadsheet it cited.

The reported case was a telemetry question against a workspace containing an
Excel file. Workspace search retrieved a narrative document plus the spreadsheet,
and the assistant answered from retrieved text alone, fabricating values.

The turn behaved as "retrieval **or** actions" instead of "retrieval **and**
actions". Evidence gathering should be additive: gather everything the turn is
capable of gathering, then reason over the union and decide what is relevant.

## Root Cause

Three independent defects combined to produce the symptom.

### 1. Tabular computation was suppressed by the presence of any narrative source

`should_run_tabular_evidence()` in `functions_mixed_source_orchestration.py` was
a keyword heuristic that ended with a blanket rule:

```python
if has_narrative_sources:
return False
```

A single PDF landing in the relevance results suppressed computation over an
authorized spreadsheet. The heuristic also treated topic words — `report`,
`policy`, `procedure`, `contract`, `agreement`, `memo`, `letter`, `narrative`,
`prose` — as evidence-type signals. Those words describe subject matter, not
which engine can answer a question, so they misfired frequently.

When the gate returned `False`, `execute_tabular_evidence_sources(...,
execute=False)` emitted a `skipped` evidence envelope and the tabular engine
never ran.

Note that when `enable_mixed_source_chat_search` is disabled, the legacy path in
`route_backend_chats.py` computes workspace tabular sources unconditionally. The
mixed-source path had regressed that behavior; this fix restores parity.

### 2. Only a truncated preview of a spreadsheet is indexed

`_build_tabular_schema_summary()` in `functions_documents.py` indexes a single
schema chunk holding at most `TABULAR_SCHEMA_SUMMARY_MAX_PREVIEW_ROWS` (3) rows
per sheet. This is intentional — the full file lives in blob storage and the
tabular engine reads it directly.

However, when defect 1 skipped computation, that preview was still handed to the
model as ordinary retrieved text. The model then derived counts and averages from
three rows, which is the direct source of the incorrect values.

The indexed chunk even ends with "This file is available for detailed analysis
via the Tabular Processing plugin" — the model read the advertisement for the
tool while being instructed not to use it.

### 3. The retrieval augmentation prompt forbade using actions

`build_search_augmentation_system_prompt()` in `route_backend_chats.py`
instructed:

> Base your answer only on information supported by the retrieved excerpts and
> any computed tool-backed results included elsewhere in this conversation
> context.

The mixed-source evidence handoff built by
`build_mixed_source_evidence_handoff()` was likewise a closed "synthesize one
answer" instruction.

Agent actions were in fact available. Agents are constructed with
`FunctionChoiceBehavior.Auto()` in `semantic_kernel_loader.py`, and the agent is
invoked with the augmented history through `selected_agent.invoke_stream(...)`.
No code disables tools when documents are in scope. The model simply obeyed the
instruction not to look anywhere else, and the retrieved excerpts appeared
sufficient, so it never called an action.

## Files Modified

| File | Change |
|---|---|
| `application/single_app/functions_mixed_source_orchestration.py` | Inverted the tabular gate; narrowed narrative markers; rewrote the skipped-envelope summary; added action permission and a preview-row guard to the evidence handoff |
| `application/single_app/route_backend_chats.py` | Rewrote `build_search_augmentation_system_prompt()` |
| `application/single_app/config.py` | Version `0.260.024` -> `0.260.025` |
| `functional_tests/test_agent_actions_with_workspace_evidence.py` | New regression test |
| `functional_tests/test_mixed_source_chat_search_consistency.py` | Updated the gating contract a generic question now computes rather than skips |

## Code Changes

### Additive tabular gating

`should_run_tabular_evidence()` now defaults to running. Computation is skipped
only when narrative sources are present **and** the question unambiguously names
a narrative artifact:

```python
narrative_artifact_markers = (
"pdf", "docx", "word document", "presentation", "powerpoint",
"paragraph", "section",
)
```

Topic words no longer suppress computation. Explicit tabular intent and
collective phrasing still short-circuit to `True`.

### Skipped sources are now self-correcting

A skipped tabular envelope previously read "Tabular processing was not needed for
this narrative-only request", which told the model the source was irrelevant. It
now states that the full table was never read, that any indexed excerpt is a
truncated preview, that numeric conclusions must not be drawn from it, and that
the tabular analysis action should be called if values are required.

### Prompt contract permits and expects action use

The retrieval augmentation prompt now frames excerpts as starting evidence rather
than the only permitted evidence, directs the model to call an available action
when the excerpts lack what the question needs, keeps a hard no-fabrication rule,
and forbids deriving any numeric conclusion from tabular preview rows.

The mixed-source handoff carries the same permission plus a guard against
numeric conclusions drawn from an indexed preview of a source whose evidence
status is not `completed`.

No new setting was introduced. These are correctness fixes and apply
unconditionally.

## Validation

```powershell
python .\functional_tests\test_agent_actions_with_workspace_evidence.py
python -m pytest .\functional_tests\test_mixed_source_manifest_contracts.py .\functional_tests\test_tabular_computed_results_prompt_priority.py -q
```

The new test asserts:

- a quantitative question with narrative sources present now computes the
tabular source (the reported regression);
- an unambiguous narrative-artifact question still skips computation;
- topic words such as "report" no longer suppress computation;
- the skipped envelope warns against numeric conclusions from preview rows;
- the search prompt permits action invocation and no longer says "only";
- the handoff instruction permits action invocation.

Existing contracts in `test_tabular_computed_results_prompt_priority.py` and
`test_mixed_source_manifest_contracts.py` continue to pass unchanged.

### Known unrelated failures

Three tests in `test_mixed_source_chat_search_consistency.py` fail both before
and after this change, at identical assertions. Their harness builds a synthetic
namespace for `_execute_mixed_source_tabular_evidence` that is missing
`maybe_queue_search_tabular_generated_output`, so the stubbed tabular runner
raises and every source reports `failed`. A third failure originates in
`foundry_agent_runtime.py`. Both are pre-existing harness drift and are out of
scope for this fix.

## Before / After

| | Before | After |
|---|---|---|
| Spreadsheet + PDF in scope, quantitative question | Tabular engine skipped | Tabular engine runs |
| Model's view of a skipped spreadsheet | "not needed for this narrative-only request" | Explicit warning that the table was not read and the action should be called |
| Retrieved excerpts insufficient | Model answers from excerpts or declines | Model calls an available action, then reasons over both |
| Numbers from a 3-row preview | Permitted implicitly | Explicitly forbidden |
1 change: 1 addition & 0 deletions docs/explanation/fixes/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,4 @@ category: Version History
- [Generated Artifact Paging, Truncation, and Guidance Carry-Forward Fix](GENERATED_ARTIFACT_PAGING_AND_GUIDANCE_FIX.md)
- [Admin Settings Pane Variable Scope Fix](ADMIN_SETTINGS_PANE_VARIABLE_SCOPE_FIX.md)
- [Inline Media Cited-Only Gating Fix](INLINE_MEDIA_CITED_ONLY_GATING_FIX.md)
- [Agent Actions With Workspace Evidence Fix](AGENT_ACTIONS_WITH_WORKSPACE_EVIDENCE_FIX.md)
Loading
Loading