{"id":111604,"date":"2026-07-10T12:00:00","date_gmt":"2026-07-10T12:00:00","guid":{"rendered":"https:\/\/www.red-gate.com\/simple-talk\/?p=111604"},"modified":"2026-07-07T14:23:59","modified_gmt":"2026-07-07T14:23:59","slug":"how-to-automate-fabric-data-agent-testing-with-promptfoo-and-github-actions","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/data-analytics\/microsoft-fabric\/how-to-automate-fabric-data-agent-testing-with-promptfoo-and-github-actions\/","title":{"rendered":"How to automate Fabric data agent testing with promptfoo and GitHub Actions"},"content":{"rendered":"\n<p><strong>This guide walks through setting up promptfoo, an open-source LLM testing framework, to run automated test suites against your Fabric data agent locally and inside a GitHub Actions pipeline. The result? Every prompt, assertion, and regression check will run on its own, <em>every time you push a change<\/em>.<\/strong><\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-why-testing-data-agents-is-essential-and-introducing-a-great-way-to-automate-the-process\">Why testing data agents is essential (and introducing a great way to automate the process)<\/h2>\n\n\n\n<p>If you read my <a href=\"https:\/\/www.red-gate.com\/simple-talk\/ai\/how-dataops-helps-reduce-gen-ai-risk-improve-data-quality\/\" target=\"_blank\" rel=\"noreferrer noopener\">previous article on why DataOps matters in the age of AI<\/a>, you already know I believe testing data agents is essential. But what I didn&#8217;t have a great answer for at the time was how to automate that testing in a repeatable, scalable way &#8211; especially one that could slot into a <a href=\"https:\/\/www.red-gate.com\/simple-talk\/devops\/demystifying-continuous-integration-vs-continuous-delivery-part-3-real-world-examples-of-ci-cd\/\" target=\"_blank\" rel=\"noreferrer noopener\">CI\/CD<\/a> pipeline.<\/p>\n\n\n\n<p>Now, with Microsoft&#8217;s recent introduction of <a href=\"https:\/\/learn.microsoft.com\/en-us\/fabric\/data-warehouse\/service-principals\" target=\"_blank\" rel=\"noreferrer noopener\">service principal support with Fabric data agents<\/a>, we have the answer.<\/p>\n\n\n\n<p>This opens up a lot of possibilities, with my favorite being around <a href=\"https:\/\/www.red-gate.com\/simple-talk\/devops\/ci-cd\/demystifying-continuous-integration-vs-continuous-delivery-part-1\/\" target=\"_blank\" rel=\"noreferrer noopener\">continuous integration<\/a> and <a href=\"https:\/\/www.red-gate.com\/simple-talk\/other\/to-automate-or-not-to-automateis-that-the-question\/\" target=\"_blank\" rel=\"noreferrer noopener\">automated testing<\/a>. This cuts out the need for teams to check the validity of their data agents&#8217; answers by manually entering various prompts &#8211; something I&#8217;ve always seen a lot of, and have always thought of it as wasted effort that could be spent on more valuable activities elsewhere (such as work that directly benefits your customer). <\/p>\n\n\n\n<p>After all, every hour a developer spends clicking through prompts is an hour <em>not<\/em> spent building the next feature, or fixing a real defect. There&#8217;s also the fact that manual testing gets long, and test cases get missed &#8211; especially as agents mature and the lists of prompts grow.<\/p>\n\n\n\n<p>Plus, the underlying data and the model supporting these agents can change as the semantic model scheme evolves, data refreshes, or even if Microsoft updates (or retires) the <a href=\"https:\/\/www.red-gate.com\/simple-talk\/ai\/i-built-an-application-by-talking-to-ai-heres-what-happened-and-what-i-learned-part-one\/\" target=\"_blank\" rel=\"noreferrer noopener\">LLM<\/a> (large language model) powering the agent. Manual testing <em>will<\/em> miss regressions that happen between reviews.<\/p>\n\n\n\n<p>So, wouldn\u2019t it be nice if, with a single command, we could run multiple prompts and get confirmation that the agent responded appropriately? Even better, what if that command could run inside a <a href=\"https:\/\/www.red-gate.com\/simple-talk\/featured\/introduction-to-github-actions-part-1-getting-started\/\" target=\"_blank\" rel=\"noreferrer noopener\">GitHub Action<\/a> so that, whenever the agent is updated and synced to source control, the tests execute automatically?<\/p>\n\n\n\n<p>Well, now we can &#8211; and in this article, I&#8217;ll show you how.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-introducing-promptfoo-what-it-is-and-how-it-works\">Introducing promptfoo (what it is, and how it works)<\/h2>\n\n\n\n<p><strong>The testing library that makes this possible is <a href=\"https:\/\/www.promptfoo.dev\/\" target=\"_blank\" rel=\"noreferrer noopener\">promptfoo<\/a>. Recently <a href=\"https:\/\/openai.com\/index\/openai-to-acquire-promptfoo\/\" target=\"_blank\" rel=\"noreferrer noopener\">acquired by OpenAI<\/a>, promptfoo is one of my favorite tools for automating the testing of interactions with LLMs.<\/strong> <strong>At its core, promptfoo revolves around a concept called providers<\/strong>. <strong>These are modules that serve as the interface between the testing framework and an AI tool &#8211; whether that\u2019s <a href=\"https:\/\/support.claude.com\/en\/articles\/8114491-get-started-with-claude\" target=\"_blank\" rel=\"noreferrer noopener\">Claude<\/a>, <a href=\"https:\/\/ai.azure.com\/\" target=\"_blank\" rel=\"noreferrer noopener\">Azure AI Foundry<\/a>,<\/strong> <strong>or another.<\/strong><\/p>\n\n\n\n<p>While Promptfoo ships with standard providers for the most popular services, it also lets you write your own <strong>custom provider<\/strong>. And this is exactly what we\u2019ll do to connect to the <a href=\"https:\/\/www.microsoft.com\/en-gb\/microsoft-fabric\" target=\"_blank\" rel=\"noreferrer noopener\">Fabric<\/a> data agent.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"396\" height=\"361\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/06\/image-18.png\" alt=\"An image showing an overview of the promptfoo architecture, showing how a custom provider bridges the testing framework and the Fabric Data Agent\" class=\"wp-image-111605\" srcset=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/06\/image-18.png 396w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/06\/image-18-300x273.png 300w\" sizes=\"auto, (max-width: 396px) 100vw, 396px\" \/><figcaption class=\"wp-element-caption\"><em>Overview of the promptfoo architecture, showing how a custom provider bridges the testing framework and the Fabric Data Agent<\/em><\/figcaption><\/figure>\n\n\n\n<p>This flexibility is what makes promptfoo so powerful. We define our prompts and the assertions we want to validate, such as \u201cthe response must mention revenue\u201d, or \u201cthe question is not valid to this agent\u2019s purpose\u201d. promptfoo then handles the orchestration of calling the agent and evaluating the results.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-how-to-use-promptfoo\">How to use promptfoo<\/h2>\n\n\n\n<p><strong>I&#8217;ll now walk through exactly how to use promptfoo &#8211; from getting started and the basics, to running tests automatically. First, here&#8217;s exactly what you need to use promptfoo.<\/strong><\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-everything-you-need-in-order-to-use-promptfoo\">Everything you need in order to use promptfoo<\/h3>\n\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li><strong>A Fabric Data Agent<\/strong> connected to a <a href=\"https:\/\/www.red-gate.com\/simple-talk\/other\/semantic-model-more-than-a-simple-name-change\/\" target=\"_blank\" rel=\"noreferrer noopener\">semantic model<\/a>, Fabric database, or <a href=\"https:\/\/www.red-gate.com\/simple-talk\/featured\/choosing-between-the-lakehouse-and-warehouse-in-microsoft-fabric\/\" target=\"_blank\" rel=\"noreferrer noopener\">lakehouse<\/a>. <br><em>Note that Eventhouses are not currently supported for service principals<\/em>.<em> If your agent relies on an Eventhouse, you&#8217;ll need to wait for that support to arrive.<\/em><br><br><\/li>\n\n\n\n<li><strong>Node.js<\/strong>: promptfoo runs on <a href=\"https:\/\/nodejs.org\/en\" target=\"_blank\" rel=\"noreferrer noopener\">Node.js<\/a>, so you&#8217;ll need it installed on your machine and any CI runner you plan to use.<br><br><\/li>\n\n\n\n<li><strong>Visual Studio Code<\/strong>: we&#8217;ll run and iterate on tests locally with <a href=\"https:\/\/code.visualstudio.com\/\" target=\"_blank\" rel=\"noreferrer noopener\">Visual Studio Code<\/a>.<br><br><\/li>\n\n\n\n<li><strong>Git<\/strong>: we use <a href=\"https:\/\/git-scm.com\/\" target=\"_blank\" rel=\"noreferrer noopener\">Git<\/a> to pull source code for the data agent and keep our tests in version control alongside it.<br><br><\/li>\n\n\n\n<li><strong>A GitHub repository<\/strong>: either in <a href=\"https:\/\/www.red-gate.com\/simple-talk\/devops\/tools\/getting-started-with-azure-devops\/\" target=\"_blank\" rel=\"noreferrer noopener\">Azure DevOps<\/a> or GitHub. This is where we&#8217;ll store source code and automate data agent testing. I&#8217;ll show how this works in <a href=\"https:\/\/github.com\/\" target=\"_blank\" rel=\"noreferrer noopener\">GitHub<\/a> specifically.<\/li>\n<\/ul>\n<\/div>\n\n\n<h2 class=\"wp-block-heading\" id=\"h-how-to-create-the-service-principal\">How to create the service principal<\/h2>\n\n\n\n<p><strong>A service principal is an application identity in <a href=\"https:\/\/www.microsoft.com\/en-gb\/security\/business\/identity-access\/microsoft-entra-id\" target=\"_blank\" rel=\"noreferrer noopener\">Microsoft Entra ID<\/a> (formerly Azure Active Directory) that allows an application or automated process to authenticate with Microsoft services.<\/strong> <strong>Think of it as a dedicated account with scoped permissions for your CI pipeline.<\/strong><\/p>\n\n\n\n<p>Here&#8217;s exactly how to set up and create the service principal, step-by-step.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-how-to-create-a-new-app-registration-in-microsoft-entra\">How to create a new app registration in Microsoft Entra<\/h3>\n\n\n\n<p>Navigate to the Microsoft Entra admin center and create a new app registration. <a href=\"https:\/\/learn.microsoft.com\/en-us\/entra\/identity-platform\/quickstart-register-app\" target=\"_blank\" rel=\"noreferrer noopener\">Microsoft has great instructions for these steps<\/a>. <\/p>\n\n\n\n<p>Once the registration is complete, create a client &#8216;secret&#8217;. Store that secret somewhere safe as you&#8217;ll need it again later (and won&#8217;t be able to retrieve it again from the portal.)<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"840\" height=\"453\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/06\/image-19.png\" alt=\"Images showing how to update the Fabric Admin setting to allow service principals to call Fabric APIs, and adding the service principal to a security group in Microsoft Entra ID.\" class=\"wp-image-111607\" srcset=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/06\/image-19.png 840w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/06\/image-19-300x162.png 300w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/06\/image-19-768x414.png 768w\" sizes=\"auto, (max-width: 840px) 100vw, 840px\" \/><figcaption class=\"wp-element-caption\"><em>Registering a new application and creating a client secret in the Microsoft Entra admin center<\/em>.<\/figcaption><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-how-to-add-the-service-principal-to-a-security-group\">How to add the service principal to a security group<\/h3>\n\n\n\n<p>To use service principals, you need to enable <strong>Grant service principals<\/strong> to call the Fabric public APIs in the Fabric Admin portal. Then, rather than granting permissions directly to the service principal, Microsoft recommends adding it to a security group and granting permissions to the group. <\/p>\n\n\n\n<p>This is the same pattern you would follow for any human user, and makes governance easier to manage as you add more principals.<\/p>\n\n\n\n<p>Navigate to <strong>Groups<\/strong> in the Entra admin center and create (or reuse) a group that has access to your Fabric workspace. Then, add the service principal as a member:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"840\" height=\"351\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/06\/image-20.png\" alt=\"\" class=\"wp-image-111608\" srcset=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/06\/image-20.png 840w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/06\/image-20-300x125.png 300w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/06\/image-20-768x321.png 768w\" sizes=\"auto, (max-width: 840px) 100vw, 840px\" \/><figcaption class=\"wp-element-caption\"><em>Updating the Fabric Admin setting to allow service principals to call Fabric APIs, and adding the service principal to a security group in Microsoft Entra ID<\/em>.<\/figcaption><\/figure>\n\n\n\n<section id=\"my-first-block-block_e54cb3c95f123d7fbc24f51a8b6a4a05\" class=\"my-first-block alignwide\">\n    <div class=\"bg-brand-600 text-base-white py-5xl px-4xl rounded-sm bg-gradient-to-r from-brand-600 to-brand-500 red\">\n        <div class=\"gap-4xl items-start md:items-center flex flex-col md:flex-row justify-between\">\n            <div class=\"flex-1 col-span-10 lg:col-span-7\">\n                <h3 class=\"mt-0 font-display mb-2 text-display-sm\">Subscribe to the Simple Talk newsletter<\/h3>\n                <div class=\"child:last-of-type:mb-0\">\n                                            Get selected articles, event information, podcasts and other industry content delivered straight to your inbox.                                    <\/div>\n            <\/div>\n                                            <a href=\"https:\/\/www.red-gate.com\/simple-talk\/subscribe\/\" class=\"btn btn--secondary btn--lg\" aria-label=\"Subscribe: Subscribe to the Simple Talk newsletter\">Subscribe<\/a>\n                    <\/div>\n    <\/div>\n<\/section>\n\n\n<h3 class=\"wp-block-heading\" id=\"h-how-to-grant-viewer-rights-access-to-the-data-agent-and-underlying-source\">How to grant viewer rights (access) to the data agent and underlying source<\/h3>\n\n\n\n<p>This step is critically important and one I see teams get wrong. The service principal having access to the data agent does <em>not<\/em> mean it inherits permissions to the underlying data source. <strong>You need to grant access in both places:<\/strong><\/p>\n\n\n\n<p><strong>How to<\/strong> <strong>grant access in the data agent<\/strong>:<br>In the Fabric portal, navigate to your data agent. Open its settings and add the security group with <strong>Member<\/strong> rights (at a minimum.)<\/p>\n\n\n\n<p><strong>How to grant access in the underlying source<\/strong>:<br>If your data agent is connected to a semantic model, navigate to the <strong>semantic model settings<\/strong> in the Fabric portal and grant the security group <strong>Build<\/strong> or <strong>Read<\/strong> access. If it&#8217;s connected to a Fabric database or lakehouse, grant the group appropriate access there as well. For <a href=\"https:\/\/learn.microsoft.com\/en-us\/fabric\/fundamentals\/direct-lake-overview\" target=\"_blank\" rel=\"noreferrer noopener\">Direct Lake<\/a> semantic models with Service Principal, configure a fixed identity.<\/p>\n\n\n<div class=\"block-core-list\">\n<ol class=\"wp-block-list\"><\/ol>\n<\/div>\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"396\" height=\"361\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/06\/image-18.png\" alt=\"an image showing how to grant the service principal Member rights on the Data Agent in the Fabric portal.\" class=\"wp-image-111606\" srcset=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/06\/image-18.png 396w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/06\/image-18-300x273.png 300w\" sizes=\"auto, (max-width: 396px) 100vw, 396px\" \/><figcaption class=\"wp-element-caption\"><em>Granting the service principal Member rights on the Data Agent in the Fabric portal<\/em>.<\/figcaption><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-how-to-import-the-testing-repository\">How to import the testing repository<\/h2>\n\n\n\n<p><strong>I&#8217;ll now explain how to import the testing repository.<\/strong> <strong>To make it<\/strong> <strong>easier, I&#8217;ve prepared a template repository that contains the promptfoo configuration, the custom Fabric Data Agent provider, and a ready-to-use GitHub Actions workflow.<\/strong><\/p>\n\n\n\n<p>The repository is located at <strong><a href=\"https:\/\/github.com\/clientfirsttech\/fabric-data-agent-testing-template\" target=\"_blank\" rel=\"noreferrer noopener\">Fabric Data Agent CI\/CD Testing Template<\/a><\/strong>. In the <a href=\"https:\/\/code.visualstudio.com\/\" target=\"_blank\" rel=\"noreferrer noopener\">Visual Studio Code (VSC)<\/a> terminal, you can clone this repository by following these steps:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \"># 1. Clone or copy this repository\ngit clone https:\/\/github.com\/clientfirsttech\/fabric-data-agent-testing-template my-fabric-agent-testing\ncd my-fabric-agent-testing\n\n# 2. Install dependencies\nnpm install\n\n# 3. Install Promptfoo\nnpm install -g promptfoo@latest\n\n# 4. Configure your Data Agent (see Configuration section)\ncp .env.example .env\n\n# 5. Open Folder in VS Code\ncode .<\/pre><\/div>\n\n\n\n<p>This installs the required dependencies, copies the <code>.env<\/code> file for later setup, and then opens the folder in VSC.<\/p>\n\n\n\n<p>The key files in the testing folder are:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><td><strong>File<\/strong><\/td><td><strong>Purpose<\/strong><\/td><\/tr><\/thead><tbody><tr><td><code>promptfooconfig.yaml<\/code><\/td><td>The main promptfoo configuration, defining prompts and assertions.<\/td><\/tr><tr><td><code>provider.ts<\/code><\/td><td>The custom provider that authenticates with the service principal and calls the Fabric Data Agent.<\/td><\/tr><tr><td><code>.github\/workflows\/promptfoo.yml<\/code><\/td><td>The GitHub Actions workflow that runs tests on push and pull request.<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"560\" height=\"781\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/06\/image-21.png\" alt=\"The template repository structure, showing the testing folder with its key files.\" class=\"wp-image-111609\" srcset=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/06\/image-21.png 560w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/06\/image-21-215x300.png 215w\" sizes=\"auto, (max-width: 560px) 100vw, 560px\" \/><figcaption class=\"wp-element-caption\"><em>The template repository structure, showing the testing folder with its key files<\/em>.<\/figcaption><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-understanding-the-yaml-configuration-and-how-to-make-changes-for-testing-your-data-agent\">Understanding the YAML configuration (and how to make changes for testing your data agent)<\/h2>\n\n\n\n<p>The <code>promptfooconfig.yaml<\/code> is where you define what you&#8217;re testing. Let me walk through its key sections:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">fabric_urls:\n         agent1: \"https:\/\/api.fabric.microsoft.com\/v1\/workspaces\/{workspace-id}\/dataagents\/{dataagent-id}\/aiassistant\/openai\"\n         agent2: \"https:\/\/api.fabric.microsoft.com\/v1\/workspaces\/{workspace-id}\/dataagents\/{dataagent-id}\/aiassistant\/openai\"\n      max_retries: 2\n      retry_delay: 2  # seconds\n      timeout: 60  # seconds for internal API calls<\/pre><\/div>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-how-to-point-to-the-correct-agent\">How to point to the correct agent<\/h3>\n\n\n\n<p>Here, you&#8217;ll replace the <em>agent1<\/em> and <em>agent2<\/em> URLs with the URLs for <strong>your<\/strong> agents. <em>agent1<\/em> and <em>agent2<\/em> are then used as references in specific test cases. <\/p>\n\n\n\n<p><strong>How do you find the URL for an agent?<\/strong> Go to the <strong>agent settings<\/strong> and open the <strong>Publishing<\/strong> tab:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"840\" height=\"139\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/06\/image-22.png\" alt=\"An image showing the agent settings page, which is where you get the data agent's URL.\" class=\"wp-image-111610\" srcset=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/06\/image-22.png 840w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/06\/image-22-300x50.png 300w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/06\/image-22-768x127.png 768w\" sizes=\"auto, (max-width: 840px) 100vw, 840px\" \/><figcaption class=\"wp-element-caption\"><em>The agent settings page is where you get the data agent&#8217;s URL<\/em>.<\/figcaption><\/figure>\n\n\n\n<p>Now, copy the URL of the data agent into where the <em>agent1<\/em> URL currently is.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-how-to-set-up-the-test-cases\">How to set up the test cases<\/h3>\n\n\n\n<p>Tests pair each agent and prompt with one or more <strong>assertions<\/strong>. The assert statement uses JavaScript functions to evaluate the response, including <a href=\"https:\/\/www.red-gate.com\/simple-talk\/featured\/using-regex-in-sql-server-2025-complete-guide\/\" target=\"_blank\" rel=\"noreferrer noopener\">regex expressions<\/a> to identify key phrasing.<\/p>\n\n\n\n<p>The conversation label lets you evaluate multiple prompts and responses in a single thread. The code below shows both a multi-turn conversation and a single-turn conversation:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">tests:\n  # Multi-turn conversation example (same conversation across turns)\n  - description: \"Conversation turn 1 - yearly total\"\n    vars:\n      agent: agent1\n      conversation: yearly-char-rollup\n      reset: true\n      query: \"What was the total number of characters in 1998?\"\n    assert:\n      - type: javascript\n        value: \"output.length &gt; 0\"\n\n  - description: \"Conversation turn 2 - follow-up by quarter\"\n    vars:\n      agent: agent1\n      conversation: yearly-char-rollup\n      query: \"How about by quarter\"\n    assert:\n      - type: javascript\n        value: \"\/quarter|q1|q2|q3|q4\/i.test(output)\"\n\n  # Test 1: Irrelevant question - should reject\n  - description: \"Reject irrelevant question (joke)\"\n    vars:\n      agent: agent1\n      query: \"Tell me a joke\"\n    assert:\n      - type: javascript\n        value: \"output.includes('This question is not relevant to my purpose,')\"<\/pre><\/div>\n\n\n\n<p>You&#8217;ll need to update this YAML to suit your testing needs.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-how-to-update-the-env-for-your-data-agent\">How to update the .env for your data agent<\/h2>\n\n\n\n<p>Now that you have the configuration file set up, configure your local environment variables so you can run tests from your machine. In a prior step, you copied <code>.env.example<\/code> and created an <code>.env<\/code> file. This is listed in <code>.gitignore<\/code>, so is <em>not<\/em> exposed in your GitHub repository as plain text. Set these properties:<\/p>\n\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li><code>TENANT_ID<\/code><\/li>\n\n\n\n<li><code>CLIENT_ID<\/code><\/li>\n\n\n\n<li><code>CLIENT_SECRET<\/code><\/li>\n\n\n\n<li><code>FABRIC-SCOPE<\/code><\/li>\n<\/ul>\n<\/div>\n\n\n<p>Note that <code>FABRIC-SCOPE<\/code> is set to the commercial cloud environment. Support for sovereign tenants may be added in the future.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"875\" height=\"549\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/06\/image-23.png\" alt=\"an image showing the process of adding the service principal credentials to the .env file.\" class=\"wp-image-111611\" srcset=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/06\/image-23.png 875w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/06\/image-23-300x188.png 300w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/06\/image-23-768x482.png 768w\" sizes=\"auto, (max-width: 875px) 100vw, 875px\" \/><figcaption class=\"wp-element-caption\"><em>Adding the service principal credentials to the .env file<\/em>.<\/figcaption><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-how-to-run-the-tests-locally\">How to run the tests locally<\/h2>\n\n\n\n<p>With the environment variables in place, run promptfoo with the <code>npm test<\/code> command. <\/p>\n\n\n\n<p>promptfoo will then iterate through each test case, call the Fabric data agent via the custom provider, evaluate the response against the defined assertions, and print a summary table to the console:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"875\" height=\"368\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/06\/image-24.png\" alt=\"an image showing promptfoo test results displayed in the terminal, showing each test case with a pass or fail status.\" class=\"wp-image-111612\" srcset=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/06\/image-24.png 875w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/06\/image-24-300x126.png 300w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/06\/image-24-768x323.png 768w\" sizes=\"auto, (max-width: 875px) 100vw, 875px\" \/><figcaption class=\"wp-element-caption\"><em>promptfoo test results displayed in the terminal, showing each test case with a pass or fail status<\/em>.<\/figcaption><\/figure>\n\n\n\n<p>If a test fails, the output will show you both the prompt <em>and<\/em> the agent\u2019s actual response, as well as <em>why<\/em> the assertion was not satisfied. This makes it easy to diagnose whether the issue is with the agent\u2019s configuration, the underlying data, or the assertion itself.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-how-to-run-tests-automatically-with-github-actions\">How to run tests automatically with GitHub Actions<\/h2>\n\n\n\n<p>The GitHub Actions workflow included in the template runs automatically whenever changes to the data agent\u2019s source files are pushed, or whenever a pull request is opened, against the main branch.<\/p>\n\n\n\n<p>The same properties in <code>.env<\/code> will need to be added to GitHub repository settings under <strong>Settings > Secrets and variables > Actions<\/strong>.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"875\" height=\"618\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/06\/image-25.png\" alt=\"an image showing the GitHub Actions workflow run showing each test step and a green pass status.\" class=\"wp-image-111613\" srcset=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/06\/image-25.png 875w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/06\/image-25-300x212.png 300w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/06\/image-25-768x542.png 768w\" sizes=\"auto, (max-width: 875px) 100vw, 875px\" \/><figcaption class=\"wp-element-caption\"><em>The GitHub Actions workflow run showing each test step and a green pass status<\/em>.<\/figcaption><\/figure>\n\n\n\n<p>Now, whenever you update the Fabric data agent and sync it with <a href=\"https:\/\/learn.microsoft.com\/en-us\/fabric\/cicd\/git-integration\/intro-to-git-integration\" target=\"_blank\" rel=\"noreferrer noopener\">Fabric Git integration<\/a>, these tests will run automatically. No more manual testing required!<\/p>\n\n\n\n<section id=\"my-first-block-block_5e18170883b1f018ecd5369af7670e6f\" class=\"my-first-block alignwide\">\n    <div class=\"bg-brand-600 text-base-white py-5xl px-4xl rounded-sm bg-gradient-to-r from-brand-600 to-brand-500 red\">\n        <div class=\"gap-4xl items-start md:items-center flex flex-col md:flex-row justify-between\">\n            <div class=\"flex-1 col-span-10 lg:col-span-7\">\n                <h3 class=\"mt-0 font-display mb-2 text-display-sm\">Simple Talk is brought to you by Redgate Software<\/h3>\n                <div class=\"child:last-of-type:mb-0\">\n                                            Take control of your databases with the trusted Database DevOps solutions provider. Automate with confidence, scale securely, and unlock growth through AI.                                    <\/div>\n            <\/div>\n                                            <a href=\"https:\/\/www.red-gate.com\/solutions\/overview\/\" class=\"btn btn--secondary btn--lg\" aria-label=\"Discover how Redgate can help you: Simple Talk is brought to you by Redgate Software\">Discover how Redgate can help you<\/a>\n                    <\/div>\n    <\/div>\n<\/section>\n\n\n<h2 class=\"wp-block-heading\" id=\"h-what-to-test\">What to test<\/h2>\n\n\n\n<p><strong>This is a good moment to step back and determine what actually makes a good test for a data agent. After all, the quality of your tests is just as important as having them in the first place.<\/strong><\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-test-for-inappropriate-prompts\">Test for inappropriate prompts<\/h3>\n\n\n\n<p>There are always those who will try to coax a data agent into doing something it wasn&#8217;t designed for. Someone may ask a financial data agent to \u201ctell me a dirty joke,\u201d or prompt it to \u201cevaluate my resume and tell me how to improve it\u201d. And someone else may simply try to extract information outside the agent\u2019s intended scope. <\/p>\n\n\n\n<p>Whatever the prompt, you need to ensure the agent responds with a polite refusal that stays on topic. A good assertion for these cases reads something like:<\/p>\n\n\n\n<p><code>output.includes(\u201cThis request is not relevant to my purpose, please ask a question regarding\u201d)<\/code><\/p>\n\n\n\n<p>These kinds of prompts may be uncomfortable to document in a test file, but your leadership should understand they are necessary and appropriate &#8211; precisely because validating these guardrails is how you protect your users <em>and<\/em> your organization.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-test-for-data-conditions\">Test for data conditions<\/h3>\n\n\n\n<p>The sample answers embedded in data agents can also serve as a check that the agent is translating questions into queries correctly. This can drift over time as:<\/p>\n\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>The underlying data changes (e.g., a column is renamed or a table is restructured).<br><br><\/li>\n\n\n\n<li>Metadata changes (e.g., descriptions or synonyms in the semantic model are updated).<br><br><\/li>\n\n\n\n<li>The AI model supporting the agent is updated or retired by Microsoft.<\/li>\n<\/ul>\n<\/div>\n\n\n<p>For data condition tests, you can combine a specific prompt with an assertion that checks for a known fact in the current data, such as: <em>The response should state that total revenue for Q4 was approximately $4.2 million.<\/em><\/p>\n\n\n\n<p>When the underlying data is refreshed or the model changes, a failing test here surfaces the discrepancy immediately, long before a user notices and raises the issue.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-test-for-response-consistency\">Test for response consistency<\/h3>\n\n\n\n<p>Another important category is consistency. Ask the same question multiple times and verify that the answers don&#8217;t contradict each other. Given the non-deterministic nature of LLMs, this is one of the most important aspects to test.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-in-summary\">In summary<\/h2>\n\n\n\n<p>If you followed along correctly, you now have an automated test suite for your Fabric data agent. It runs with a single command, locally and automatically, inside a GitHub Actions <em>every time<\/em> you or your team push a change. <\/p>\n\n\n\n<p>By combining service principal support, promptfoo\u2019s flexible assertion engine, and GitHub Actions, we&#8217;ve turned what was previously an error-prone manual process into a repeatable safety net.<\/p>\n\n\n\n<section id=\"faq\" class=\"faq-block my-5xl\">\n    <h2>FAQs<\/h2>\n\n                        <h3 class=\"mt-4xl\">1. What is promptfoo and why use it for testing Fabric Data Agents?<\/h3>\n            <div class=\"faq-answer\">\n                <p class=\"font-claude-response-body break-words whitespace-normal\">promptfoo is an LLM testing framework that lets you define prompts and assertions, then automatically evaluates whether an AI agent&#8217;s responses meet expectations. It supports custom providers, which makes it possible to connect it directly to a Fabric data agent&#8217;s API endpoint.<\/p>\n            <\/div>\n                    <h3 class=\"mt-4xl\">2. Do I need a service principal to test a Fabric data agent?<\/h3>\n            <div class=\"faq-answer\">\n                <p>Yes. Service principal support is what enables automated, non-interactive authentication to the Fabric data agent API &#8211; required for running tests in a CI\/CD pipeline like GitHub Actions without a human logging in.<\/p>\n            <\/div>\n                    <h3 class=\"mt-4xl\">3. Can promptfoo tests run automatically on a schedule or on code changes?<\/h3>\n            <div class=\"faq-answer\">\n                <p class=\"font-claude-response-body break-words whitespace-normal\">Yes. The included GitHub Actions workflow runs tests automatically whenever changes are pushed to the data agent&#8217;s source files or when a pull request is opened against the main branch.<\/p>\n            <\/div>\n                    <h3 class=\"mt-4xl\">4. What should a good Fabric data agent test suite actually check?<\/h3>\n            <div class=\"faq-answer\">\n                <p>Three main categories: rejecting inappropriate or out-of-scope prompts, verifying correct answers against known data conditions (to catch drift from schema or model changes), and confirming response consistency across repeated identical prompts.<\/p>\n            <\/div>\n                    <h3 class=\"mt-4xl\">5. Does this approach work with Eventhouses or sovereign cloud tenants?<\/h3>\n            <div class=\"faq-answer\">\n                <p>Not yet. Eventhouses aren&#8217;t currently supported for service principals, and <code>FABRIC-SCOPE<\/code> is limited to the commercial cloud environment, with sovereign tenant support potentially coming later.<\/p>\n            <\/div>\n            <\/section>\n","protected":false},"excerpt":{"rendered":"<p>Learn how to automate Fabric Data Agent testing using promptfoo, service principals, and GitHub Actions &#8211; step-by-step guide. No more manual testing required!&hellip;<\/p>\n","protected":false},"author":344919,"featured_media":105560,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[159160,53,159164],"tags":[158997],"coauthors":[159224],"class_list":["post-111604","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-data-analytics","category-featured","category-microsoft-fabric","tag-microsoft-fabric"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/111604","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/users\/344919"}],"replies":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/comments?post=111604"}],"version-history":[{"count":13,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/111604\/revisions"}],"predecessor-version":[{"id":111852,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/111604\/revisions\/111852"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media\/105560"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=111604"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=111604"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=111604"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=111604"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}