01Pre-check file type, size, and count before handing files to code interpreterVerified

Copilot Studio agents can generate and run Python with code interpreter to analyze CSV and Excel files (preview). Each file can be up to 16 MB, with a maximum of 10 files, and a code interpreter prompt cannot analyze several files at once. Check files locally before you hand them over.

Approach and steps
  1. For files attached in agent chat, check the type (.csv, .xlsx), the size of each file, and the number of files
  2. For a prompt built in prompt builder, pass one file per run
  3. Microsoft's extensively tested scenarios are lookups and aggregations over Excel and CSV, so verify results from other file types with extra care
  4. Split files, or drop columns, that do not meet the limits before sending them
PythonRuns locally
from pathlib import Path

# Limits from the Copilot Studio docs (structured data in agents, preview)
AGENT_MAX_MB = 16
AGENT_MAX_FILES = 10
TESTED_TYPES = {".csv", ".xlsx"}   # types in Microsoft's extensively tested scenarios


def precheck(paths, mode="agent"):
    paths = [Path(p) for p in paths]
    problems = []
    if mode == "prompt":
        # prompts: one uploaded file per run; no size limit is documented
        if len(paths) > 1:
            problems.append(f"prompt takes one file, got {len(paths)}")
        return problems
    if len(paths) > AGENT_MAX_FILES:
        problems.append(f"too many files: {len(paths)} > {AGENT_MAX_FILES}")
    for p in paths:
        size_mb = p.stat().st_size / (1024 * 1024)
        if size_mb > AGENT_MAX_MB:
            problems.append(f"{p.name}: {size_mb:.1f} MB > {AGENT_MAX_MB} MB")
        if p.suffix.lower() not in TESTED_TYPES:
            problems.append(f"{p.name}: {p.suffix} is outside the tested CSV/Excel scenarios")
    return problems


files = ["samples/ledger.csv", "samples/request_ledger.xlsx", "samples/doc_a.docx"]
for mode in ("agent", "prompt"):
    issues = precheck(files, mode)
    print(mode, "OK" if not issues else "issues:")
    for i in issues:
        print("  -", i)
Output(2026-09-12)
agent issues:
  - doc_a.docx: .docx is outside the tested CSV/Excel scenarios
prompt issues:
  - prompt takes one file, got 3
Example instruction for Copilot

Write a Python function that takes a list of files for Copilot Studio code interpreter and prints check results for agent use (per-file size limit, file count limit, warning for types other than CSV and xlsx) and for prompt use (one file only). Keep the limits in constants.

Caution

Structured-data analysis is in preview, and limits or behavior can change; the constants reflect the documentation on the day it was fetched, so recheck before use. The fetched pages give no size limit for prompts. Agents that use code interpreter must be configured for user authentication. Verify the analysis against the source data.

Availability
Requires Copilot Studio licensing; code generation and execution count as premium features. Public clouds only (sovereign clouds are not supported). Code interpreter must be enabled per environment and is off by default.
Tested
run and checked locally (2026-09-12, Python 3.12.10 (venv) / Windows 11)
Practical edition
Copilot Chat ── Supported file formats and splitting long files
Source
Microsoft Learn, "Use code interpreter to analyze structured data (preview)"
Microsoft Learn, "Use code interpreter in a prompt to generate and execute Python code"
Verified
2026-09-12 (v1)
Supporting passages from the sources
The size limit for each file that can be analyzed by code interpreter, irrespective of file type, is 16 MB. You can upload a maximum of 10 files.
Analyzing multiple files uploaded in a single prompt isn't supported.
By using code interpreter, Copilot Studio agents can generate and run Python code when they need to respond to user prompts.
Code interpreter must be enabled for each environment before you can use it. The default setting is Off.

02Call a code interpreter prompt through the Predict API and get the executed codeVerified

A prompt with code interpreter turned on can be called through the Dataverse Web API Predict action. Besides the generated content, predictionOutput in the response carries the Python code that ran (code) and its execution log (logs).

Approach and steps
  1. Create the prompt in Copilot Studio or Power Apps and turn on code interpreter (prompts cannot be created through the API)
  2. Query msdyn_aimodels by msdyn_name and note the row ID of the prompt
  3. POST to msdyn_aimodels(ID)/Microsoft.Dynamics.CRM.Predict with version "2.0" and the prompt inputs in requestv2
  4. If overrideHttpStatusCode is 202, poll by sending GET requests to overrideLocation
  5. Save code and logs from the response and have someone read them before the result is used
PythonSign-in required (Microsoft Entra)
import os
import time

import requests
from azure.identity import InteractiveBrowserCredential

ENV_URL = os.environ.get("DATAVERSE_URL", "https://example.crm.dynamics.com")
MODEL_ID = os.environ.get("AI_MODEL_ID", "")   # msdyn_aimodelid of the prompt
DRY_RUN = os.environ.get("DRY_RUN", "1") != "0"
API = f"{ENV_URL}/api/data/v9.2"
HEADERS = {"Accept": "application/json", "OData-MaxVersion": "4.0", "OData-Version": "4.0"}


def predict_body(inputs):
    # Prompt inputs travel in requestv2 as an open type; keys must match the prompt's input names
    return {"version": "2.0",
            "requestv2": {"@odata.type": "#Microsoft.Dynamics.CRM.expando", **inputs}}


def wait_seconds(value, default=5):
    # overrideRetryAfter format is not documented; fall back to a fixed wait
    try:
        return max(1, int(float(value)))
    except (TypeError, ValueError):
        return default


def output_of(resp):
    v2 = resp["responsev2"]
    out = v2["predictionOutput"]
    return {"status": v2.get("operationStatus"), "text": out.get("text", ""),
            "code": out.get("code", ""), "logs": out.get("logs", ""),
            "files": [f.get("file_name") for f in out.get("files", [])]}


def main():
    cred = InteractiveBrowserCredential(tenant_id=os.environ["TENANT_ID"], client_id=os.environ["CLIENT_ID"])
    token = cred.get_token(f"{ENV_URL}/user_impersonation").token
    session = requests.Session()
    session.headers.update({**HEADERS, "Authorization": f"Bearer {token}"})
    url = f"{API}/msdyn_aimodels({MODEL_ID})/Microsoft.Dynamics.CRM.Predict"
    body = predict_body({"question": "Count requests by document type"})
    if DRY_RUN:
        print("DRY_RUN: would POST", url, body)
        return
    r = session.post(url, json=body, timeout=120)
    r.raise_for_status()
    data = r.json()
    for _ in range(30):
        if str(data.get("overrideHttpStatusCode")) != "202":
            break
        time.sleep(wait_seconds(data.get("overrideRetryAfter")))
        data = session.get(data["overrideLocation"], timeout=60).json()
    result = output_of(data)
    print(result["status"], result["files"])
    print(result["code"])


if __name__ == "__main__":
    main()
Output(2026-09-12)
Success ['summary.csv']
version: 2.0 | wait: 5 3
Example instruction for Copilot

Write a Python function using requests that calls the Dataverse Predict action with version "2.0" and requestv2 (@odata.type #Microsoft.Dynamics.CRM.expando). If overrideHttpStatusCode is 202, poll overrideLocation, then return text, code, logs, and file names from predictionOutput. Read the environment URL and IDs from environment variables and do not send by default.

Caution

Predict calls consume AI Builder capacity. No capacity returns 403 (EntitlementNotAvailable); too many concurrent calls return 500 (MaxConcurrentPlexCallsReachedException) with no Retry-After header, so wait and retry. The format of overrideRetryAfter is not documented, so fall back to a fixed wait. The code field holds the executed source or a placeholder describing it.

Availability
Code interpreter must be enabled per environment and is off by default; Copilot Studio licensing applies. Sign-in is delegated; public clients use the <environment-url>/user_impersonation scope.
Requires
requests 2.34.2, azure-identity 1.25.3
Tested
run up to the sign-in step (not connected to Microsoft 365); aggregation/checking logic run on sample data (2026-09-12, Python 3.12.10 (venv) / Windows 11, requests 2.34.2, azure-identity 1.25.3)
Source
Microsoft Learn, "Code interpreter for developers"
Microsoft Learn, "Use OAuth authentication with Microsoft Dataverse"
Microsoft Learn, "Predict Action"
Verified
2026-09-12 (v1)
Supporting passages from the sources
In case the prediction isn't completed, 202 indicates that a polling is necessary, otherwise null.
The version parameter. The value is always "2.0".
Python Source code or placeholder describing executed code.
use a "<environment-url>/user_impersonation" scope for a public client.

03Save the executed code, logs, and generated files from a Predict responseVerified

predictionOutput in a Predict response holds the Python that ran (code), the execution log (logs), and generated files (base64_content in files). Save them to disk, flag lines that drop or filter rows, and have someone read the code before the result is used.

Approach and steps
  1. Save the Predict response JSON
  2. Check that responsev2.operationStatus is Success
  3. Write predictionOutput.code to a .py file and logs to a .txt file
  4. Decode base64_content for each entry in files and save it under file_name (strip any path)
  5. Flag lines that change the row count, such as dropna or drop_duplicates, and check that their conditions are intended
PythonRuns locally
import base64
import json
from pathlib import Path

SRC = "samples/excel_predict_response.json"   # a saved Predict response
OUT = Path("out_predict")
OUT.mkdir(exist_ok=True)

resp = json.loads(Path(SRC).read_text(encoding="utf-8"))
v2 = resp["responsev2"]
if v2.get("operationStatus") != "Success":
    raise SystemExit(f"prediction did not succeed: {v2.get('operationStatus')}")
out = v2["predictionOutput"]

code = out.get("code", "")
(OUT / "executed_code.py").write_text(code, encoding="utf-8")
(OUT / "logs.txt").write_text(out.get("logs", ""), encoding="utf-8")
for f in out.get("files", []):
    name = Path(f["file_name"]).name   # keep the base name only
    (OUT / name).write_bytes(base64.b64decode(f["base64_content"]))
    print("file:", name, f.get("content_type"))

print("code lines:", len(code.splitlines()), "| finish:", out.get("finishReason"))
# Lines that change the row count deserve a closer look
WATCH = ("dropna", "fillna", "drop_duplicates", ".query(")
for no, line in enumerate(code.splitlines(), 1):
    if any(w in line for w in WATCH):
        print(f"review line {no}: {line.strip()}")
Output(2026-09-12)
file: summary.csv text/csv
code lines: 7 | finish: stop
review line 3: df = df.dropna(subset=['指摘件数'])
Example instruction for Copilot

Write Python that reads a Dataverse Predict response JSON, saves predictionOutput.code to executed_code.py and logs to logs.txt, decodes files from base64 and saves them, and prints the lines of the code that contain dropna, fillna, drop_duplicates, or query with line numbers.

Caution

The code field can be a placeholder describing the executed code rather than the code itself; then check logs and the generated files. Check the content type before opening generated files. The flag list is only a starting point, not a substitute for reading. Responses can include parts of the input data, so restrict access to where you save them.

Availability
The response shape follows the Web API example in Code interpreter for developers. Uses only the Python standard library.
Tested
run and checked locally (2026-09-12, Python 3.12.10 (venv) / Windows 11)
Source
Microsoft Learn, "Code interpreter for developers"
Verified
2026-09-12 (v1)
Supporting passages from the sources
Generated file artifacts with file_name, content_type, and base64_content properties.
Python code execution log output (if provided).
operationStatus: A string value showing whether the operation succeeded. Expected value is Success.

04Recompute Copilot in Excel's answer with pandas and compare the numbersVerified

Copilot in Excel answers with Python-based analysis and leaves the workbook unchanged. Computing the same question yourself in local pandas and lining up the numbers makes it easier to catch a wrong column or filter.

Approach and steps
  1. Note the numbers in the answer and the conditions in the displayed code (columns, filters, aggregation)
  2. Save the same workbook locally and read it with pandas.read_excel
  3. Write your own aggregation for the same question without looking at Copilot's code
  4. Copy the answer's numbers into a dict and list rows that differ or exist on one side only
  5. Where they differ, compare the filters and missing-value handling in Copilot's code
PythonRuns locally
import pandas as pd

SRC = "samples/request_ledger.xlsx"
# Numbers copied from Copilot's answer to "average findings by department" (example)
copilot = {"営業部": 4.6, "広報部": 4.4, "人事部": 4.1}

df = pd.read_excel(SRC)
mine = df.groupby("依頼部署")["指摘件数"].mean().round(1)

check = pd.DataFrame({"copilot": pd.Series(copilot, dtype=float), "pandas": mine})
check["diff"] = (check["copilot"] - check["pandas"]).round(2)
print(check.to_string())

mismatch = check[(check["diff"].abs() > 0.05) | check.isna().any(axis=1)]
print("rows used:", len(df), "| mismatch:", list(mismatch.index))
Output(2026-09-12)
     copilot  pandas  diff
人事部      4.1     4.1   0.0
営業部      4.6     4.6   0.0
広報部      4.4     4.2   0.2
rows used: 40 | mismatch: ['広報部']
Example instruction for Copilot

Write pandas code that reads request_ledger.xlsx, computes the mean findings per department rounded to one decimal, lines it up with values pasted into a dict, and prints departments whose difference exceeds 0.05 or that appear on only one side.

Caution

Agreement does not rule out both sides reading the same wrong range, so compare row counts too. Inserted static tables or images do not refresh; recompute after the source changes. Unstructured data is not supported.

Availability
Direct answers are part of Copilot in Excel for Microsoft 365 (a Copilot license is required). The check runs in local Python with pandas.
Requires
pandas 3.0.5, openpyxl 3.1.5
Tested
run and checked locally (2026-09-12, Python 3.12.10 (venv) / Windows 11, pandas 3.0.5, openpyxl 3.1.5)
Practical edition
Excel ── Get answers computed with Python-based analysis
Source
Microsoft Support, "Get direct answers to your data analysis questions"
pandas, "pandas.read_excel"
Verified
2026-09-12 (v1)
Supporting passages from the sources
Copilot does not modify your workbook. Sometimes, it may return a static table or image visualization that you can insert into your sheet, but these aren’t refreshable.
You can expand the code to see how the answer was generated.

05Run advanced analysis code locally with a stand-in for xl()Verified

Code from advanced analysis mode targets Python in Excel cells and reads tables through xl(). Define a local function with the same name that reads the same table from the saved workbook, and you can run the code unchanged and compare it with the result in Excel.

Approach and steps
  1. Insert the advanced analysis code as a Python cell, or copy the code
  2. Save the workbook and keep a local copy
  3. Build a dict that maps each reference passed to xl() to a sheet in the saved workbook
  4. Define xl() with the same name and run the copied code as is
  5. Import pandas and the other libraries explicitly; Python in Excel imports them by default
PythonRuns locally
import pandas as pd   # pre-imported in Python in Excel; explicit here

BOOK = "samples/request_ledger.xlsx"
# Map each reference used in the copied code to a sheet in the saved book
REFS = {"RequestLedger[#All]": "依頼台帳"}


def xl(ref, headers=False):
    # Local stand-in for Python in Excel's xl(): reads saved cell values only
    return pd.read_excel(BOOK, sheet_name=REFS[ref], header=0 if headers else None)


# ---- code copied from the Python cell (unchanged) ----
df = xl("RequestLedger[#All]", headers=True)
df["依頼月"] = pd.to_datetime(df["依頼日"]).dt.to_period("M")
monthly = df.pivot_table(index="依頼月", columns="状態", values="依頼ID",
                         aggfunc="count", fill_value=0)
# ---- end of copied code ----

print(monthly.to_string())
print("rows read:", len(df))
Output(2026-09-12)
状態       受付  完了  差戻し  確認中
依頼月                      
2026-07   5   6    5    5
2026-08   0   8    6    5
rows read: 40
Example instruction for Copilot

I want to run Python in Excel code locally. Write a function with the same signature as xl(ref, headers=False) that maps reference names to sheet names with a dict and reads the saved workbook with pandas.read_excel, using the first row as headers when headers=True.

Caution

The local xl() only reads cell values; it does not reproduce table boundaries or formula recalculation, and unsaved edits are not included. Local library versions can differ from the Python in Excel environment, so consider version differences when results disagree.

Availability
Advanced analysis mode is reached from Copilot in Excel direct answers (a Copilot license is required). The local run uses pandas and openpyxl.
Requires
pandas 3.0.5, openpyxl 3.1.5
Tested
run and checked locally (2026-09-12, Python 3.12.10 (venv) / Windows 11, pandas 3.0.5, openpyxl 3.1.5)
Practical edition
Excel ── Get answers computed with Python-based analysis
Source
Microsoft Support, "Get direct answers to your data analysis questions"
Microsoft Support, "Open-source libraries and Python in Excel"
Verified
2026-09-12 (v1)
Supporting passages from the sources
This opens a new sheet and runs Python code for deeper, customizable analysis. It’s ideal for users who want to: Explore more complex scenarios Modify and insert underlying Python code as a refreshable Python cell
The following open-source libraries are available with Python in Excel by default. They've been imported with the statements listed.
This means that Python formulas have access to read cell values within the workbook, based on the cell reference, or values from external data sources, through the Power Query connection name.

06List what changed after Copilot writes Python results into a workbookVerified

When editing with Copilot, Python can run and write its results straight into the workbook. Save a copy before the edit, then compare it with the edited file cell by cell in openpyxl to list changed cells, new cells, and new sheets.

Approach and steps
  1. Before asking Copilot to edit, save a copy of the workbook under another name
  2. Save the edited workbook too, and read both with openpyxl's default mode (formulas stay as text)
  3. List cells with the same sheet and address whose value or formula differs
  4. List added or removed sheets and new cells on existing sheets
  5. Review the list and restore anything unintended from the copy
PythonRuns locally
from openpyxl import load_workbook

BEFORE = "samples/request_ledger.xlsx"      # copy saved before the Copilot edit
AFTER = "samples/excel_ledger_after.xlsx"   # workbook after the edit


def cells(path):
    wb = load_workbook(path)   # default mode: formulas as text, nothing recalculated
    found = {}
    for ws in wb.worksheets:
        for row in ws.iter_rows():
            for c in row:
                if c.value is not None:
                    found[(ws.title, c.coordinate)] = c.value
    return found, wb.sheetnames


a, sheets_a = cells(BEFORE)
b, sheets_b = cells(AFTER)
print("added sheets:", [s for s in sheets_b if s not in sheets_a])
print("removed sheets:", [s for s in sheets_a if s not in sheets_b])

changed = sorted(k for k in a.keys() & b.keys() if a[k] != b[k])
new_in_old_sheets = sorted(k for k in b.keys() - a.keys() if k[0] in sheets_a)
cleared = sorted(a.keys() - b.keys())
for k in changed:
    print("changed", k, repr(a[k]), "->", repr(b[k]))
print(f"changed={len(changed)} new_in_existing_sheets={len(new_in_old_sheets)} cleared={len(cleared)}")
Output(2026-09-12)
added sheets: ['集計']
removed sheets: []
changed ('依頼台帳', 'G3') '受付' -> '完了'
changed ('依頼台帳', 'H10') 9 -> 12
changed=2 new_in_existing_sheets=1 cleared=0
Example instruction for Copilot

Write Python with openpyxl that reads two xlsx files (before and after an edit) and prints added and removed sheets, cells whose value or formula changed, and counts of new and cleared cells on existing sheets. Do not modify either file.

Caution

Only cell values and formula text are compared; formatting, charts, PivotTables, and shapes are not. Formulas are not calculated, so check differences in formula results in Excel. Large workbooks take time and memory. Where change records are required, this list does not replace them.

Availability
Running Python when editing with Copilot is available on Windows, Mac, and the web (per the Microsoft 365 Copilot release notes). The comparison runs in local Python with openpyxl.
Requires
openpyxl 3.1.5
Tested
run and checked locally (2026-09-12, Python 3.12.10 (venv) / Windows 11, openpyxl 3.1.5)
Practical edition
Excel ── Ask Copilot to explain workbook change history
Source
Microsoft Learn, "Release Notes for Microsoft 365 Copilot"
Microsoft Support, "Get started with Copilot in Excel"
Verified
2026-09-12 (v1)
Supporting passages from the sources
Edit with Copilot helps you work with Python in Excel by executing Python code for advanced analysis, automation, and data transformation with results outputted directly in your workbook.
Copilot updates your workbook using Excel's built-in features. Your content stays editable, and you're in control of everything that's modified.
data_only controls whether cells with formulae have either the formula (default) or the value stored the last time Excel read the sheet.