<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Julia Alba</title>
    <description>The latest articles on DEV Community by Julia Alba (@juliaalba).</description>
    <link>https://dev.to/juliaalba</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4244739%2Fd3b07f53-0ebe-4963-9d24-90e89faed15d.png</url>
      <title>DEV Community: Julia Alba</title>
      <link>https://dev.to/juliaalba</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/juliaalba"/>
    <language>en</language>
    <item>
      <title>Why your Copilot Studio agent gets calculations wrong, and the agent flow that fixes it</title>
      <dc:creator>Julia Alba</dc:creator>
      <pubDate>Sun, 15 Nov 2026 09:45:56 +0000</pubDate>
      <link>https://dev.to/juliaalba/why-your-copilot-studio-agent-gets-calculations-wrong-and-the-agent-flow-that-fixes-it-2hba</link>
      <guid>https://dev.to/juliaalba/why-your-copilot-studio-agent-gets-calculations-wrong-and-the-agent-flow-that-fixes-it-2hba</guid>
      <description>&lt;p&gt;A language model predicts the digits of a total; it does not calculate them. Put a Copilot Studio agent in front of a price list, ask it "How much for 240 units of Standard?", and sooner or later it returns a wrong number. The usual symptoms:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The same question returns two different totals on two runs.&lt;/li&gt;
&lt;li&gt;You get "approximately" where you need cents.&lt;/li&gt;
&lt;li&gt;A follow-up is answered from conversation memory instead of the data.&lt;/li&gt;
&lt;li&gt;The user never gave a quantity, and the agent picked one.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A stricter prompt alone does not fix this. Moving the math does: every calculation goes into an agent flow, where the same input always gives the same figures. The model keeps the two jobs it is good at: understanding the question and wording the answer.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The example is invented. Standard costs 12.50 EUR and Premium 24.85 EUR per unit, orders of 100 units or more get 10% off, and VAT is 20%.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  1. Do the math in whole cents
&lt;/h2&gt;

&lt;p&gt;Create an agent flow on the &lt;strong&gt;Workflows&lt;/strong&gt; page (&lt;strong&gt;More create options&lt;/strong&gt;, then &lt;strong&gt;Agent flows&lt;/strong&gt;) with the trigger &lt;strong&gt;When an agent calls the flow&lt;/strong&gt; and two inputs: &lt;code&gt;Product&lt;/code&gt; (Text) and &lt;code&gt;Quantity&lt;/code&gt; (Number).&lt;/p&gt;

&lt;p&gt;Read the unit price, the discount tiers and the VAT rate from your price list into &lt;strong&gt;Integer&lt;/strong&gt; variables: prices in whole cents (12.50 EUR as 1250), rates as whole percents (10, 20).&lt;/p&gt;

&lt;p&gt;Why integers? Flow expressions have no &lt;code&gt;round()&lt;/code&gt; function, and &lt;code&gt;div()&lt;/code&gt; changes behavior with its operand types. From Microsoft's expression reference:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;div(11, 5)     -&amp;gt;  2      both Integer: the remainder is dropped
div(11, 5.0)   -&amp;gt;  2.2    one Float: the result is Float
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Dropping the remainder is useful: add half the divisor before you divide, and truncation becomes rounding half up. One variable per line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ListCents      mul(variables('Qty'), variables('UnitCents'))
DiscountCents  div(add(mul(variables('ListCents'), variables('DiscountPct')), 50), 100)
NetCents       sub(variables('ListCents'), variables('DiscountCents'))
VatCents       div(add(mul(variables('NetCents'), variables('VatPct')), 50), 100)
GrossCents     add(variables('NetCents'), variables('VatCents'))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Qty&lt;/code&gt; holds the quantity, and &lt;code&gt;DiscountPct&lt;/code&gt; is 10 for 100 units or more, otherwise 0. Each discount and VAT amount is rounded half up to the cent before any total is built. That is this example's rule; use the one your finance team specifies.&lt;/p&gt;

&lt;p&gt;The same steps for 101 units of Premium:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;List      101 x 2485                =  250985  -&amp;gt;  2,509.85
Discount  (250985 x 10 + 50) / 100  =   25099  -&amp;gt;    250.99   exact: 250.985
Net       250985 - 25099            =  225886  -&amp;gt;  2,258.86
VAT       (225886 x 20 + 50) / 100  =   45177  -&amp;gt;    451.77   exact: 451.772
Gross     225886 + 45177            =  271063  -&amp;gt;  2,710.63
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The trap:&lt;/strong&gt; if even one operand is a decimal, &lt;code&gt;div()&lt;/code&gt; returns a decimal and the +50 no longer rounds, so a figure can be a cent off. Keep every amount in an Integer variable.&lt;/p&gt;

&lt;p&gt;A price stored as 12.50 needs &lt;code&gt;int(formatNumber(mul(..., 100), 'F0'))&lt;/code&gt;, which rounds to the whole cent; &lt;code&gt;int()&lt;/code&gt; alone converts only whole-number text. Wrap a price that arrives as text in &lt;code&gt;float()&lt;/code&gt; first.&lt;/p&gt;

&lt;p&gt;Format each figure once, at the end. Only here do you want a decimal, so divide by &lt;code&gt;100.0&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;formatNumber(div(variables('NetCents'), 100.0), 'N2', 'en-US')   -&amp;gt;  2,258.86
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check the inputs in a &lt;strong&gt;Condition&lt;/strong&gt; before any math. If the price list has no row for the product, or the quantity is below 1 or not a whole number, end that branch with its own &lt;strong&gt;Respond to the agent&lt;/strong&gt;, whose text names the wrong input.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Return one finished text
&lt;/h2&gt;

&lt;p&gt;End the math branch with &lt;strong&gt;Respond to the agent&lt;/strong&gt; and a single Text output, &lt;code&gt;Result&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;240 x Standard at 12.50 EUR = 3,000.00 EUR. Volume discount 10%: 300.00 EUR. Net total: 2,700.00 EUR. VAT 20%: 540.00 EUR. Gross total: 3,240.00 EUR.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The model never has to add, round or format anything.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Make the agent copy, not compute
&lt;/h2&gt;

&lt;p&gt;Add the flow to the agent as a tool named &lt;strong&gt;Quote&lt;/strong&gt;. The tool description mainly decides when the agent calls it, so say what it does, when to use it and what it needs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Calculates a price quote for one product and quantity from the current price list: list price, volume discount, net total, VAT and gross total. Use it for every question about prices, discounts, VAT or totals. Required: product and quantity. Never answer a price question without calling this tool.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the description of the &lt;code&gt;Quantity&lt;/code&gt; input, write "Never assume a quantity." In the input's settings, under &lt;strong&gt;Prompt&lt;/strong&gt;, select &lt;strong&gt;Customize&lt;/strong&gt; and enter the question for a missing quantity: "How many units do you need?" The agent may still ask in its own words first.&lt;/p&gt;

&lt;p&gt;Under &lt;strong&gt;Completion&lt;/strong&gt;, the &lt;strong&gt;After running&lt;/strong&gt; setting controls what the user sees. The default, &lt;strong&gt;Don't respond&lt;/strong&gt;, lets the agent work the tool output into its own reply. Two other options matter:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Send specific response&lt;/strong&gt;, with a message that holds only &lt;code&gt;Result&lt;/code&gt;, shows the flow's text unchanged.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write the response with generative AI&lt;/strong&gt; has the tool itself word the answer at once.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When AI words the answer (the default included), add these rules to the agent's instructions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AFTER THE QUOTE TOOL RUNS
- Restate the Quote tool's Result in the user's language, in a natural tone.
- Any number, date, currency or unit you mention must match the Result exactly. Always give the net and gross totals; show the rest of the breakdown in full or not at all. Never change a figure. Do not calculate, estimate, round, convert or add figures of your own.
- If the Result says an input is missing or wrong, ask for it. Do not guess.
- Never state a figure the Quote tool did not return for this message. If the tool fails, say the quote cannot be calculated right now; do not estimate.
- After the answer, offer only what the Quote tool can calculate.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep the last two rules even with &lt;strong&gt;Send specific response&lt;/strong&gt;: the agent can still answer a follow-up from memory or add a sentence of its own.&lt;/p&gt;

&lt;p&gt;One setting to check before you go live: by default a tool runs with each user's credentials. For a price list that everyone shares, set the tool's credentials to &lt;strong&gt;Maker-provided&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Test the figures, not the tone
&lt;/h2&gt;

&lt;p&gt;On the agent's &lt;strong&gt;Evaluation&lt;/strong&gt; page, select &lt;strong&gt;New evaluation&lt;/strong&gt;, then &lt;strong&gt;Single responses&lt;/strong&gt;. Switch the toggle to &lt;strong&gt;Import&lt;/strong&gt; and upload a CSV whose first row holds the headings &lt;code&gt;Question&lt;/code&gt; and &lt;code&gt;Expected response&lt;/code&gt;. Rerun the test set after every change.&lt;/p&gt;

&lt;p&gt;Two test methods need care:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;General quality&lt;/strong&gt;, the default method, does not compare the answer with your expected response, and can flag a correct refusal. Remove it from this test set.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keyword match&lt;/strong&gt;, set to &lt;strong&gt;All&lt;/strong&gt;, checks the figures whatever the wording. It does not read the expected response either: open each test case and add two of its figures as keywords. Two, because the method looks for text inside the answer, and &lt;code&gt;2,700.00&lt;/code&gt; is also inside &lt;code&gt;12,700.00&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Two rows to start with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Question,Expected response
"How much for 101 units of Premium?","101 x Premium at 24.85 EUR = 2,509.85 EUR. Volume discount 10%: 250.99 EUR. Net total: 2,258.86 EUR. VAT 20%: 451.77 EUR. Gross total: 2,710.63 EUR."
"I need a quote for Standard.","How many units do you need?"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The keywords are &lt;code&gt;2,258.86&lt;/code&gt; and &lt;code&gt;2,710.63&lt;/code&gt; for the Premium question, and &lt;code&gt;How many units&lt;/code&gt; for the Standard question.&lt;/p&gt;

&lt;p&gt;The Premium question tells you what broke: a net of 2,258.87 means the +50 is missing from &lt;code&gt;DiscountCents&lt;/code&gt;, and a VAT of 451.78 means the VAT step ran in decimals.&lt;/p&gt;

&lt;p&gt;An agent that invents a quantity passes every happy-path test; the Standard question catches it. If it fails, read the answer: a question in other words is fine (change the keyword); a quote means the agent guessed.&lt;/p&gt;

&lt;p&gt;A single-response test has no earlier message, so test follow-ups such as "And for 300 units?" in a conversation test set.&lt;/p&gt;

&lt;h2&gt;
  
  
  When not to use this
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A system of record already calculates the figure: let a flow call that system.&lt;/li&gt;
&lt;li&gt;The math is one formula on values the agent already holds: Power Fx in a topic is simpler, and it has &lt;code&gt;Round()&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;How do you keep figures exact in your agents: a flow, Power Fx in a topic, or a call to the system of record?&lt;/p&gt;




&lt;p&gt;&lt;em&gt;The price list and all figures in this article are invented. The article was generated with AI under my direction. The expression behavior and the interface labels were checked against Microsoft Learn on 15 November 2026, and the figures were verified by calculation; the example as a whole has not been run end to end in a tenant, so run the two test rows before you rely on it.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;References (Microsoft Learn)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/azure/logic-apps/expression-functions-reference" rel="noopener noreferrer"&gt;Reference guide to functions in expressions for workflows in Azure Logic Apps and Power Automate&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/microsoft-copilot-studio/flows-overview" rel="noopener noreferrer"&gt;Agent flows overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/microsoft-copilot-studio/flow-agent" rel="noopener noreferrer"&gt;Add an agent flow as a tool to an agent&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/microsoft-copilot-studio/add-tools-custom-agent" rel="noopener noreferrer"&gt;Add tools to custom agents&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/microsoft-copilot-studio/advanced-additional-settings-topic-action-inputs" rel="noopener noreferrer"&gt;Additional settings for inputs of topics and tools&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/microsoft-copilot-studio/analytics-agent-evaluation-create" rel="noopener noreferrer"&gt;Create a single response test set&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/microsoft-copilot-studio/analytics-agent-evaluation-overview" rel="noopener noreferrer"&gt;Choose evaluation methods&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>copilotstudio</category>
      <category>powerautomate</category>
      <category>powerplatform</category>
      <category>powerfuldevs</category>
    </item>
  </channel>
</rss>
