{
  "nbformat": 4,
  "nbformat_minor": 5,
  "metadata": {
    "kernelspec": {
      "display_name": "Python 3",
      "language": "python",
      "name": "python3"
    },
    "language_info": {
      "name": "python",
      "version": "3.13.0"
    },
    "blog_metadata": {
      "topic": "What Fabric’s public data agent API means for governed analytics automation",
      "slug": "what-fabric-s-public-data-agent-api-means-for-governed-analy",
      "generated_by": "LinkedIn Post Generator + Azure OpenAI",
      "generated_at": "2026-07-07T17:11:11.017Z"
    }
  },
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "# What Fabric’s public data agent API means for governed analytics automation\n",
        "\n",
        "Microsoft’s public Fabric data agent API makes it easier to embed governed conversational analytics into internal apps and workflows instead of rebuilding one-off chatbots around the same curated data. This notebook turns the blog post into hands-on validation steps, focusing on identity propagation, request logging, response normalization, and governance review patterns. The goal is not autonomous analytics, but governed analytics automation with clearer ownership and better reuse."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "%pip install -q requests pandas"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "import os\n",
        "import uuid\n",
        "import json\n",
        "import logging\n",
        "from typing import Any, Dict, List\n",
        "\n",
        "import requests\n",
        "import pandas as pd"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Required environment variables\n",
        "\n",
        "Set the following environment variables before calling a real Fabric data agent endpoint:\n",
        "\n",
        "- `FABRIC_TOKEN`: bearer token used to authenticate the API call\n",
        "- `FABRIC_DATA_AGENT_URL`: public Fabric data agent endpoint URL\n",
        "\n",
        "Optional notebook variables can also be set inline for testing, but secrets should be managed securely in your environment or secret store."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Minimal internal app call with user context and request-id logging\n",
        "\n",
        "This example demonstrates the core integration pattern described in the post: send a natural-language question to a Fabric data agent while forwarding user context and a correlation ID. It also logs the HTTP status and request identifiers so platform and governance teams can trace usage."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "import os\n",
        "import uuid\n",
        "import logging\n",
        "import requests\n",
        "\n",
        "logging.basicConfig(level=logging.INFO, force=True)\n",
        "\n",
        "\n",
        "def call_fabric_data_agent(question: str, dataset: str, user: dict):\n",
        "    token = os.environ.get(\"FABRIC_TOKEN\")\n",
        "    agent_url = os.environ.get(\"FABRIC_DATA_AGENT_URL\")\n",
        "\n",
        "    if not token or not agent_url:\n",
        "        raise EnvironmentError(\"Missing FABRIC_TOKEN or FABRIC_DATA_AGENT_URL environment variable.\")\n",
        "\n",
        "    payload = {\n",
        "        \"question\": question,\n",
        "        \"dataset\": dataset,\n",
        "    }\n",
        "\n",
        "    headers = {\n",
        "        \"Authorization\": f\"Bearer {token}\",\n",
        "        \"Content-Type\": \"application/json\",\n",
        "        \"x-correlation-id\": str(uuid.uuid4()),\n",
        "        \"x-user-id\": user[\"id\"],\n",
        "        \"x-user-tenant\": user[\"tenant\"],\n",
        "    }\n",
        "\n",
        "    response = requests.post(agent_url, json=payload, headers=headers, timeout=30)\n",
        "    request_id = response.headers.get(\"x-request-id\", \"n/a\")\n",
        "    logging.info(\n",
        "        \"fabric_call status=%s correlation=%s request_id=%s\",\n",
        "        response.status_code,\n",
        "        headers[\"x-correlation-id\"],\n",
        "        request_id,\n",
        "    )\n",
        "\n",
        "    try:\n",
        "        body = response.json()\n",
        "    except ValueError:\n",
        "        body = {\"raw_text\": response.text}\n",
        "\n",
        "    result = {\n",
        "        \"answer\": body,\n",
        "        \"request_id\": request_id,\n",
        "        \"status\": response.status_code,\n",
        "    }\n",
        "    print(result)\n",
        "    return response, payload, headers\n",
        "\n",
        "\n",
        "# Example usage (uncomment after setting environment variables):\n",
        "# user = {\"id\": \"u12345\", \"tenant\": \"contoso\", \"roles\": [\"Finance.Reader\"]}\n",
        "# r, payload, headers = call_fabric_data_agent(\n",
        "#     question=\"Summarize Q4 revenue variance by region\",\n",
        "#     dataset=\"finance_curated\",\n",
        "#     user=user,\n",
        "# )"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Capture response metadata for governance and observability\n",
        "\n",
        "This wrapper normalizes the API response into a governance-friendly structure. It extracts request IDs, activity IDs, model details, data sources, policy tags, and answer text so downstream logging and audit systems can store a consistent event record."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "import logging\n",
        "\n",
        "logging.basicConfig(level=logging.INFO, force=True)\n",
        "\n",
        "\n",
        "def normalize_fabric_response(resp):\n",
        "    body = resp.json()\n",
        "    return {\n",
        "        \"requestId\": resp.headers.get(\"x-request-id\"),\n",
        "        \"activityId\": resp.headers.get(\"x-ms-activity-id\"),\n",
        "        \"model\": body.get(\"model\"),\n",
        "        \"dataSources\": body.get(\"dataSources\", []),\n",
        "        \"policyTags\": body.get(\"policyTags\", []),\n",
        "        \"answer\": body.get(\"answer\"),\n",
        "    }\n",
        "\n",
        "\n",
        "class MockResponse:\n",
        "    def __init__(self, body, headers=None):\n",
        "        self._body = body\n",
        "        self.headers = headers or {}\n",
        "\n",
        "    def json(self):\n",
        "        return self._body\n",
        "\n",
        "\n",
        "# Use a mock response for safe notebook validation if no live API call was made.\n",
        "mock_resp = MockResponse(\n",
        "    body={\n",
        "        \"model\": \"fabric-governed-agent\",\n",
        "        \"dataSources\": [\"finance_curated.sales\", \"finance_curated.regions\"],\n",
        "        \"policyTags\": [\"Finance\", \"Curated\", \"Internal\"],\n",
        "        \"answer\": \"Q4 revenue variance was highest in West and lowest in Central.\",\n",
        "    },\n",
        "    headers={\n",
        "        \"x-request-id\": str(uuid.uuid4()),\n",
        "        \"x-ms-activity-id\": str(uuid.uuid4()),\n",
        "    },\n",
        ")\n",
        "\n",
        "user = {\"id\": \"u12345\", \"tenant\": \"contoso\", \"roles\": [\"Finance.Reader\"]}\n",
        "payload = {\"question\": \"Summarize Q4 revenue variance by region\", \"dataset\": \"finance_curated\"}\n",
        "\n",
        "result = normalize_fabric_response(mock_resp)\n",
        "audit_event = {\n",
        "    \"userId\": user[\"id\"],\n",
        "    \"dataset\": payload[\"dataset\"],\n",
        "    \"question\": payload[\"question\"],\n",
        "    \"requestId\": result[\"requestId\"],\n",
        "    \"policyTags\": result[\"policyTags\"],\n",
        "}\n",
        "logging.info(\"governed_analytics_event=%s\", audit_event)\n",
        "print(result)"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Validate prerequisites and access configuration in Python\n",
        "\n",
        "The original post included a PowerShell prerequisite check. This Python version validates the same ideas for notebook users: token presence, HTTPS endpoint usage, workspace configuration, and placeholder checks for approved network and service principal access."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "import os\n",
        "\n",
        "\n",
        "def validate_prerequisites(\n",
        "    workspace_id: str = \"fabric-prod-ws\",\n",
        "    agent_endpoint: str = \"https://api.fabric.microsoft.com/dataagents/query\",\n",
        "):\n",
        "    checks = {\n",
        "        \"PythonAvailable\": True,\n",
        "        \"FabricToken\": bool(os.environ.get(\"FABRIC_TOKEN\")),\n",
        "        \"EndpointHttps\": agent_endpoint.startswith(\"https://\"),\n",
        "        \"WorkspaceSet\": bool(workspace_id and workspace_id.strip()),\n",
        "        \"ApprovedNetwork\": True,  # replace with enterprise network validation\n",
        "        \"ServicePrincipalAllowed\": True,  # replace with tenant/workspace access check\n",
        "    }\n",
        "\n",
        "    for key, value in checks.items():\n",
        "        print(f\"{key}: {value}\")\n",
        "\n",
        "    if False in checks.values():\n",
        "        raise RuntimeError(\"Prerequisite validation failed.\")\n",
        "\n",
        "    return checks\n",
        "\n",
        "\n",
        "checks = validate_prerequisites()"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Export a governance review checklist\n",
        "\n",
        "Governance teams should define one integration pattern early. This example creates a simple review checklist as a table and exports it to CSV so platform, admin, stewardship, and governance owners can track readiness for approved Fabric data agent integrations."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {},
      "source": [
        "import pandas as pd\n",
        "\n",
        "review = pd.DataFrame(\n",
        "    [\n",
        "        {\"Control\": \"Approved workspace\", \"Status\": \"Pass\", \"Owner\": \"Fabric Admin\"},\n",
        "        {\"Control\": \"Curated dataset only\", \"Status\": \"Pass\", \"Owner\": \"Data Steward\"},\n",
        "        {\"Control\": \"User context forwarded\", \"Status\": \"Pass\", \"Owner\": \"App Team\"},\n",
        "        {\"Control\": \"Request IDs logged\", \"Status\": \"Pass\", \"Owner\": \"Platform Ops\"},\n",
        "        {\"Control\": \"Sensitivity labels reviewed\", \"Status\": \"Pending\", \"Owner\": \"Governance\"},\n",
        "    ]\n",
        ")\n",
        "\n",
        "path = \"./fabric-data-agent-governance-checklist.csv\"\n",
        "review.to_csv(path, index=False)\n",
        "print(f\"Checklist exported to {path}\")\n",
        "review"
      ],
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Architecture flow for governed analytics automation\n",
        "\n",
        "The integration pattern in the blog can be summarized as:\n",
        "\n",
        "1. An internal app collects a user question.\n",
        "2. The app attaches user context such as tenant, role, and correlation ID.\n",
        "3. The app calls the Fabric public data agent API.\n",
        "4. Fabric applies governed access to curated datasets and policies.\n",
        "5. The API returns an answer plus metadata such as request ID, activity ID, and policy tags.\n",
        "6. The app writes logs and audit events for operations and governance review.\n",
        "\n",
        "This is the practical shift enabled by the public API: reuse a governed analytics capability across workflows instead of funding multiple one-off bots."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Summary\n",
        "\n",
        "Microsoft’s public Fabric data agent API is a meaningful platform step because it enables governed conversational analytics to be embedded into enterprise applications and workflows. The strongest implementation pattern is to centralize identity propagation, request logging, response normalization, and governance review from the first deployment.\n",
        "\n",
        "## Next Steps\n",
        "\n",
        "- Connect this notebook to a real Fabric data agent endpoint in a non-production workspace.\n",
        "- Replace placeholder access checks with tenant, workspace, and network validation logic.\n",
        "- Send normalized audit events to your enterprise logging platform.\n",
        "- Standardize one approved integration wrapper so teams stop rebuilding one-off analytics bots."
      ]
    }
  ]
}