<?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: Carlos José Castro Galante</title>
    <description>The latest articles on DEV Community by Carlos José Castro Galante (@carlosjcastrog).</description>
    <link>https://dev.to/carlosjcastrog</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%2F3850137%2F7c4306aa-d4a6-4d9d-9327-a2b882c9d13d.jpeg</url>
      <title>DEV Community: Carlos José Castro Galante</title>
      <link>https://dev.to/carlosjcastrog</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/carlosjcastrog"/>
    <language>en</language>
    <item>
      <title>Gemma 4 Reads University Syllabi Offline So Professors Only Have to Decide</title>
      <dc:creator>Carlos José Castro Galante</dc:creator>
      <pubDate>Fri, 23 Oct 2026 02:34:03 +0000</pubDate>
      <link>https://dev.to/carlosjcastrog/i-taught-gemma-4-to-read-university-syllabi-so-professors-dont-have-to-10f1</link>
      <guid>https://dev.to/carlosjcastrog/i-taught-gemma-4-to-read-university-syllabi-so-professors-dont-have-to-10f1</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week3-2026-10-19"&gt;Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;My request to have a single course recognized was filed in 2025 and resolved in September 2026. In Argentina that isn't unusual. When a student moves between universities, a professor has to read two long syllabi side by side, match topics by hand, count them against a percentage written in a regulation and write down what the student still has to take, and the case then climbs through faculty offices and councils. I'm not writing this to argue with the outcome of my own case. I'm writing it because most of the waiting happens around reading and counting, which is exactly the kind of work a model does well, while the decision itself should always stay with a person.&lt;/p&gt;

&lt;p&gt;So I built &lt;strong&gt;Tramo&lt;/strong&gt;, an open source assistant that drafts course equivalency reports with &lt;strong&gt;Gemma 4 running locally through Ollama&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You give it the analytic program of the subject a student already passed and the program of the subject they want recognized, as a PDF, a Word file or pasted text. Gemma extracts the teaching topics of both programs, compares every destination topic against the whole origin program, labels it as covered, partial or without evidence and explains each judgment with the origin topics it relied on. Plain code then computes the coverage and applies the rule of my faculty, which only allows a partial equivalency when more than half of the destination program is covered, and it lists the topics the student would still have to take. The professor can overrule any topic, and every number is recalculated on the spot.&lt;/p&gt;

&lt;p&gt;The professor gets to be lazy about the part of the job that never needed a professor, which is reading and counting, and keeps all of the part that does, which is deciding. Students get an answer sooner. And no document ever leaves the computer, because there's no cloud anywhere in the loop.&lt;/p&gt;

&lt;h3&gt;
  
  
  What an equivalency is, for readers outside Latin America
&lt;/h3&gt;

&lt;p&gt;Every course in an Argentine university has an analytic program, a document that lists its units and topics in detail. When you change careers or universities, you can ask the new institution to recognize courses you already passed, and the institution compares both programs to decide whether the content is equivalent. At my faculty, the Facultad de Tecnología y Ciencias Aplicadas of the Universidad Nacional de Catamarca, the regulation says that a partial equivalency can only be granted when the equivalent content exceeds 50 percent of the destination program, and that the professor must then list the topics the student has to take before the course counts as passed. It's a fair rule, but applying it means reading and counting dozens of topics by hand for every single request.&lt;/p&gt;

&lt;h3&gt;
  
  
  What a professor can do with Tramo
&lt;/h3&gt;

&lt;p&gt;A professor can paste both programs or drop a PDF or a Word file on each field, check and correct the extracted text before running anything, and try the whole flow in seconds with a built in sample pair of programs. The analysis runs on the same computer and shows its progress as it happens. The report presents the coverage against the 50 percent threshold, the proposed verdict, a count of topics by status, the topics still to be taken, a written recommendation and the detail of every topic with its evidence and its reasoning. The list can be filtered by status, any status can be changed with a single selector, and the final report can be copied as text or printed and saved as a PDF that carries a notice saying it's a draft generated by AI.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Tramo&lt;/em&gt; means a stretch of road in Spanish, and the interface borrows its language from road markings. A covered topic gets a continuous line, a partial one gets a dashed line and a topic without evidence gets a dotted line, so a professor can scan a long report the way you read the lanes ahead of you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;Here's the complete flow in under a minute and a half, from loading two programs to overruling the model and reading the limits of the tool. The video has no voice, only text on screen, and the analysis is sped up eight times so you don't have to sit through the wait.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/E-zZ3ENiBgU" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;The interface is in Spanish because it's built for Argentine faculties, so here's what each screen does.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F46otvtf1yyr3920kj3bm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F46otvtf1yyr3920kj3bm.png" alt="Tramo home page with the title from syllabus to equivalency report and a road marking illustration" width="800" height="382"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The home page explains in one sentence what Tramo does and who makes the final decision.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7ohkem1hcd8xercmho6h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7ohkem1hcd8xercmho6h.png" alt="Tramo form with both study programs loaded and a responsibility notice above the generate button" width="800" height="396"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Both programs are loaded, and the note above the button explains what Tramo is and what it isn't before anything runs.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fshuza1p796hjf4jf0u7v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fshuza1p796hjf4jf0u7v.png" alt="Tramo analysis screen with the title filling as Gemma 4 processes the syllabi" width="800" height="395"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;While Gemma works, the title and the road fill with the progress reported by the pipeline itself, stage by stage, next to an elapsed timer.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdm4tw40jxtr2bm51ahgf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdm4tw40jxtr2bm51ahgf.png" alt="Tramo equivalency report with coverage meter, 50 percent threshold and verdict" width="800" height="395"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The report shows coverage against the 50 percent threshold, the verdict that follows from the regulation and a count of topics by status.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjmu7a1hshyb9rt9hejp3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjmu7a1hshyb9rt9hejp3.png" alt="Tramo topic detail showing cited origin topics and the reasoning of the model" width="800" height="393"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Every topic cites the origin topics behind its status and explains the reasoning in plain language.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu8plsjuqw15s2zthxav1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu8plsjuqw15s2zthxav1.png" alt="Tramo report after the professor changes a topic status, with recalculated coverage" width="800" height="395"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;When the professor changes a status, code recalculates everything and the AI recommendation disappears, because it was written for a report that no longer exists.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxnl9uqxprdjbdajf2cnl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxnl9uqxprdjbdajf2cnl.png" alt="Tramo limits and responsibility section explaining what the AI does and who decides" width="799" height="395"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;The page explains what the AI does, when it can be wrong and who is responsible for the result.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/carlosjcastro" rel="noopener noreferrer"&gt;
        carlosjcastro
      &lt;/a&gt; / &lt;a href="https://github.com/carlosjcastro/tramo" rel="noopener noreferrer"&gt;
        tramo
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Local AI assistant that compares two course syllabi and drafts a university equivalency report. Open weights, private, the professor decides.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Tramo&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/carlosjcastro/tramo/docs/cover.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fcarlosjcastro%2Ftramo%2FHEAD%2Fdocs%2Fcover.png" alt="Tramo, from syllabus to equivalency report"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://youtu.be/E-zZ3ENiBgU" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/1e757cb40b5be680e64911c98021b9834446280df0408226364836b7c279e949/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f57617463685f7468655f64656d6f2d596f75547562652d4646303030303f7374796c653d666f722d7468652d6261646765266c6f676f3d796f7574756265266c6f676f436f6c6f723d7768697465" alt="Watch the demo on YouTube"&gt;&lt;/a&gt;
&lt;a href="https://dev.to/carlosjcastrog/i-taught-gemma-4-to-read-university-syllabi-so-professors-dont-have-to-10f1" rel="nofollow"&gt;&lt;img src="https://camo.githubusercontent.com/8d966db2f5ebdea5a5f658542c7bb2888005e4273d9c6c3a3b9f8889578296fe/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f526561645f7468655f706f73742d4445562d3041304130413f7374796c653d666f722d7468652d6261646765266c6f676f3d646576646f74746f266c6f676f436f6c6f723d7768697465" alt="Read the post on DEV"&gt;&lt;/a&gt;
&lt;a href="https://github.com/carlosjcastro/tramo/LICENSE" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/3e45b7f1fbb40d709d9f3680e2e3789608fdd40c799aa3db846a5ef952544c15/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e73652d4d49542d6632633230303f7374796c653d666f722d7468652d6261646765266c6162656c436f6c6f723d316231643166" alt="License MIT"&gt;&lt;/a&gt;
&lt;a href="https://ollama.com" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/b90b33616e83349b05212541a0415bbc259aee179da3670d50b2348b21f2a2df/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f47656d6d615f342d4f6c6c616d612d3162316431663f7374796c653d666f722d7468652d6261646765266c6162656c436f6c6f723d663263323030" alt="Gemma 4 with Ollama"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Tramo compares the analytic program of a subject a student has already passed with the program of the subject they want recognized, and drafts an equivalency report for the professor who reviews the case. It runs entirely on the computer of whoever uses it, with an open weights model served by Ollama, so the documents never leave the machine.&lt;/p&gt;
&lt;p&gt;The software does not decide anything. The model proposes a status for every topic and explains its reasoning with evidence taken from the documents, plain code computes the coverage and applies the equivalency rule, and the professor remains free to overrule any single judgment before signing. The final resolution belongs to the university authorities.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;The problem it addresses&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;Universities in Argentina and across Latin America resolve equivalencies by hand, and a single case often takes many months. The reviewing professor has to read two long analytic programs side by side…&lt;/p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/carlosjcastro/tramo" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Tramo is MIT licensed. The repository includes 119 tests, a security policy with private vulnerability reporting, a contributing guide and a README that documents the architecture, every setting and the known limits in detail.&lt;/p&gt;

&lt;p&gt;If you have Ollama installed, you can run it in a few minutes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/carlosjcastro/tramo
&lt;span class="nb"&gt;cd &lt;/span&gt;tramo
python &lt;span class="nt"&gt;-m&lt;/span&gt; venv .venv
.venv&lt;span class="se"&gt;\S&lt;/span&gt;cripts&lt;span class="se"&gt;\a&lt;/span&gt;ctivate
pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements_dev.txt
ollama pull gemma4:e4b
python &lt;span class="nt"&gt;-m&lt;/span&gt; pytest
python &lt;span class="nt"&gt;-m&lt;/span&gt; uvicorn app.api:app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On Linux and macOS, activate the environment with &lt;code&gt;source .venv/bin/activate&lt;/code&gt;. Then open &lt;code&gt;http://localhost:8000&lt;/code&gt;, press the button that loads the sample programs and generate your first report.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;Tramo runs &lt;strong&gt;Gemma 4 (e4b)&lt;/strong&gt; locally through &lt;strong&gt;Ollama&lt;/strong&gt;. The backend is Python with FastAPI and Pydantic, and the interface is plain HTML, CSS and JavaScript modules with no framework and no build step. Everything runs on a single computer with a consumer GPU, without a server or a cloud account.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Choice&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Model&lt;/td&gt;
&lt;td&gt;Gemma 4 e4b, open weights&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Inference&lt;/td&gt;
&lt;td&gt;Ollama on the same machine, structured outputs with a JSON schema&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Backend&lt;/td&gt;
&lt;td&gt;Python, FastAPI, Pydantic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Interface&lt;/td&gt;
&lt;td&gt;HTML, CSS and ES modules, no framework, strict content security policy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data&lt;/td&gt;
&lt;td&gt;Processed in memory, never stored or logged&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tests&lt;/td&gt;
&lt;td&gt;119 unit and API tests with fake model clients&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;License&lt;/td&gt;
&lt;td&gt;MIT&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;I chose the e4b size of Gemma 4 because it fits entirely on the GPU of a consumer card with a context of 12288 tokens, it handled programs written in Spanish well, and it's small enough that a faculty could run it on a computer it already owns.&lt;/p&gt;

&lt;h3&gt;
  
  
  The model proposes, the code decides
&lt;/h3&gt;

&lt;p&gt;I designed everything around one rule, which is that Gemma never produces a number that the interface presents as a fact. The pipeline has four stages, and only three of them call the model.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Topic extraction.&lt;/strong&gt; Gemma reads each program and returns its teaching topics through the structured outputs of Ollama, so every call sends the JSON schema the answer has to follow, with temperature zero. If the answer doesn't validate, a rule based splitter takes over and the report says so, because a silent fallback would be worse than an honest one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Topic assessment.&lt;/strong&gt; Destination topics go to the model in batches of twelve, and each one is compared against the complete list of origin topics. The model labels it as covered, partial or without evidence, cites up to five origin topics and writes a short reason. The code then checks that every topic was assessed exactly once and that every cited source actually exists in the origin program.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The rule.&lt;/strong&gt; Plain Python computes the coverage, counting a covered topic as one and a partial one as half, and applies the regulation of my faculty. Article 87 allows a partial equivalency only above 50 percent, and article 88 asks for the list of topics still to be taken. No text inside a document can change this result, because the model never touches it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The recommendation.&lt;/strong&gt; Gemma writes a short recommendation from facts the code has already computed, including the longest runs of consecutive topics without evidence. Before the text reaches the screen, any sentence with a number the code didn't produce is removed.
I deliberately didn't use embeddings or retrieval for the comparison. A syllabus fits comfortably in the context window, so every destination topic sees the whole origin program instead of a few chunks picked by similarity, and topics that are worded very differently in each university still get a fair reading.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Structured outputs with Ollama
&lt;/h3&gt;

&lt;p&gt;The heart of every model call is the same pattern. Ollama receives the JSON schema of a Pydantic model, Gemma answers inside that shape, and Pydantic validates the result before anything else touches it. This is a simplified version of the call.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gemma4:e4b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;TopicList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;model_json_schema&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;temperature&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;num_ctx&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;12288&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;think&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;topics&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TopicList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;model_validate_json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The rule that decides the verdict is the opposite of a model call. It's a few lines of arithmetic that anyone can read and test, which is exactly why it lives in code. This simplified version shows the idea.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;WEIGHTS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cubierto&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;parcial&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sin_evidencia&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;coverage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;statuses&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;WEIGHTS&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;statuses&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;statuses&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What broke along the way
&lt;/h3&gt;

&lt;p&gt;Three moments shaped the final design more than any plan did.&lt;/p&gt;

&lt;p&gt;My first version didn't use a model to read the programs at all. It split them with rules, and on one test it reported 55 percent coverage while proposing that the student retake the names of the professors and the weekly hours of the course. That report convinced me that reading had to be done by a model and counting by code, and never the other way around.&lt;/p&gt;

&lt;p&gt;The second moment came from the thinking mode of Gemma 4. With thinking on, Ollama ignored my JSON schema and returned plain text, and a complete analysis took five and a half minutes. I wrote a small benchmark to compare both settings, and with thinking off the model returned valid JSON for the extraction in about 4.6 seconds. The whole analysis dropped to about a minute and a half, so thinking is off by default and a single environment variable turns it back on. The benchmark lives in the repository, so anyone can repeat the comparison on their own hardware.&lt;/p&gt;

&lt;p&gt;The third one was a lesson in humility. Running the same pair of programs several times gave coverage between 24 and 32 percent while the verdict stayed the same. Temperature zero doesn't make a local model fully deterministic, and pretending otherwise would be dishonest. That's why every topic shows its reasoning, why the professor can change any status and why the numbers always come from code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Streaming the progress
&lt;/h3&gt;

&lt;p&gt;A minute and a half is long enough to wonder whether something froze, so the analysis is streamed. The backend runs the pipeline as a generator and sends newline delimited JSON events, and the browser reads them as they arrive.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"progress"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"done"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"total"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"report"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"report"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"coverage"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.31&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"verdict"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"matches"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]}}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each stage has a weight, so the percentage on screen comes from completed work and never from a timer. When the professor overrules a status, the browser sends the edited list back and the server recalculates the coverage, the verdict and the pending topics with the same code that produced the original report.&lt;/p&gt;

&lt;p&gt;After the progress screen looked frozen on my own machine because of the reduced motion setting of the system, I also added a toggle for animations. It starts from the preference of the operating system, remembers the choice, and with animations off the progress stays visible through the title, the bar and the percentage.&lt;/p&gt;

&lt;h3&gt;
  
  
  Treating every document as hostile
&lt;/h3&gt;

&lt;p&gt;Tramo takes files from users and feeds their text to a model, so it treats every input as untrusted.&lt;/p&gt;

&lt;p&gt;Uploads have to be PDF or Word files verified by their signature and not by their extension, and their size is capped before they're stored in memory. PDFs with scripts, launch actions or attachments are rejected, PDFs longer than 100 pages are refused, and Word files with macros, ActiveX controls, path traversal or an abnormal expanded size are rejected too. Hidden and control characters are removed from every text, and file names are cleaned before they're shown.&lt;/p&gt;

&lt;p&gt;Program text reaches the model between delimiters as data, the delimiters are stripped from the input, and any text that tries to give the model instructions raises a visible warning in the report. Even a successful injection couldn't change the verdict, because the verdict is computed by code.&lt;/p&gt;

&lt;p&gt;Requests are rate limited per IP address, with three analyses every ten minutes, twenty uploads per minute and one analysis running at a time. Request bodies are capped while they're being read, the Host header is validated, the API documentation is disabled, responses carry a strict content security policy, and errors never echo the submitted text or expose internals. Documents are processed in memory and never stored or logged.&lt;/p&gt;

&lt;h3&gt;
  
  
  Accessible and honest by design
&lt;/h3&gt;

&lt;p&gt;The interface works with a keyboard, announces progress and errors to screen readers, adapts from phones to wide screens and follows the light or dark preference of the system. The content security policy forbids inline scripts and styles, so even the progress bar is driven through the CSS object model.&lt;/p&gt;

&lt;p&gt;An equivalency is an administrative decision about a person, so honesty is part of the product. The interface says what Tramo is before anything runs, and the report, including its printed version, says that it's a draft that can contain mistakes and doesn't replace the professor or the authorities who resolve the case. A dedicated section explains what the model does, when it can be wrong, how copyright and personal data are handled and who is responsible for the result. Tramo is an independent project and it has no endorsement from any university.&lt;/p&gt;

&lt;h3&gt;
  
  
  What it can't do yet
&lt;/h3&gt;

&lt;p&gt;Tramo compares text, so it doesn't know the practical work, the bibliography or the depth with which each course was actually taught. It can't read scanned PDFs without a text layer, and its prompts still need calibration with professors and with the official equivalency tables of each institution. The next steps I have in mind are a second verification pass for topics labeled as covered, a button to cancel a running analysis, support for other regulations through configurable rules and an export of the report to Word.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Does Open Innovation Matter?
&lt;/h2&gt;

&lt;p&gt;The documents around an equivalency belong to a student, and sending them to a closed API means agreements, consent forms and trust in the retention policy of a company. Many faculties can't arrange that quickly, and some never will. With open weights running locally that obstacle simply isn't there, because nothing leaves the computer and there's no account, key or invoice involved. Once the model is downloaded, Tramo doesn't even need an internet connection.&lt;/p&gt;

&lt;p&gt;Openness also makes the result auditable. The model, the prompts, the schemas and the rule live in the same public repository, so anyone reviewing a report can see exactly how it was produced. A university council can't inspect a closed model, but it can read this code and run it on its own machines.&lt;/p&gt;

&lt;p&gt;The tool also belongs to whoever uses it. A faculty with a different regulation can change the threshold with one variable, swap Gemma for another model in Ollama or rewrite the rule in a single Python module, without asking anyone for permission. The MIT license lets any university adopt it for free, and I'd love to hear from the ones that do.&lt;/p&gt;

&lt;p&gt;A closed API would have made privacy a promise, while here it's a property of the architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Lazy Payoff
&lt;/h2&gt;

&lt;p&gt;I haven't timed a professor doing this by hand, so I won't invent a number. What I can measure is the machine side. On my computer, Tramo reads both programs, assesses every destination topic and drafts the report in about a minute and a half, and the professor starts from a list where each topic already carries its evidence and its mark. Reviewing that list is still work, but it's the kind of work that needs a professor.&lt;/p&gt;

&lt;p&gt;This week I've been the only user. I ran Tramo many times on syllabi from two Argentine universities, the same kind of documents that sat in my own case, and every run gave me a report I could audit topic by topic in a few minutes. The next step is putting it in front of the professors who review these cases every semester and calibrating the prompts with them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prize Categories
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Gemma&lt;/strong&gt;. Every model call in Tramo, from topic extraction to the written recommendation, runs on Gemma 4 locally through Ollama, and the whole design depends on an open model that a faculty can run on its own hardware.&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://youtu.be/E-zZ3ENiBgU" rel="noopener noreferrer"&gt;Watch the demo video on YouTube&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/carlosjcastro/tramo" rel="noopener noreferrer"&gt;Explore the code on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/carlosjcastro/tramo#readme" rel="noopener noreferrer"&gt;Read the README for the full architecture, settings and security details&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/carlosjcastro/tramo/issues" rel="noopener noreferrer"&gt;Report a problem or suggest an idea&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you've ever changed universities or careers, how long did it take to get your courses recognized, and what would have made that wait shorter for you?&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
      <category>ai</category>
      <category>opensource</category>
    </item>
    <item>
      <title>How to learn AI from scratch with a free Microsoft Learn plan in Spanish</title>
      <dc:creator>Carlos José Castro Galante</dc:creator>
      <pubDate>Wed, 21 Oct 2026 02:04:19 +0000</pubDate>
      <link>https://dev.to/carlosjcastrog/how-to-learn-ai-from-scratch-with-a-free-microsoft-learn-plan-in-spanish-2033</link>
      <guid>https://dev.to/carlosjcastrog/how-to-learn-ai-from-scratch-with-a-free-microsoft-learn-plan-in-spanish-2033</guid>
      <description>&lt;p&gt;According to the Job Outlook 2026 Spring Update from NACE, the US based National Association of Colleges and Employers, more than one third of entry level jobs now require AI skills, which is nearly triple the share employers reported in fall 2025. The data comes from &lt;a href="https://naceweb.org/job-market/trends-and-predictions/demand-for-ai-skills-in-entry-level-jobs-nearly-triples-since-fall-2025" rel="noopener noreferrer"&gt;185 employer responses&lt;/a&gt;, so it doesn't describe every market, but it does show where hiring expectations are heading for people about to graduate. That's why I built a &lt;a href="https://learn.microsoft.com/plans/d2w4u1tgn0j25z?sharingId=D34D672B88CE3FAC&amp;amp;wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;free Microsoft Learn plan&lt;/a&gt; that takes students from any major from understanding what AI is to using it with good judgment in their studies and at work. The plan and the modules it contains are in Spanish, and in this article I'll explain what it includes, the order I recommend and how to start.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this Microsoft Learn plan is and who it's for
&lt;/h2&gt;

&lt;p&gt;A Microsoft Learn plan is a collection of official modules organized in stages, with a suggested time frame to complete them and your progress saved in your account. This one gathers three beginner level modules and is designed for students from any major, because none of the three has mandatory prerequisites. If you're looking for a free AI course for students, this plan gives you an ordered path built from official Microsoft content.&lt;/p&gt;

&lt;p&gt;Everything in the plan is in Spanish, so it fits best if you read Spanish comfortably. I'm a Microsoft Learn Student Ambassador, and I created this plan by selecting and ordering modules that already exist on Microsoft Learn, without writing or changing their content. The plan page estimates about two and a half hours of study in total and suggests finishing it in seven days, although you can go at whatever pace suits you.&lt;/p&gt;

&lt;p&gt;You can &lt;a href="https://learn.microsoft.com/plans/d2w4u1tgn0j25z?sharingId=D34D672B88CE3FAC&amp;amp;wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;open the plan on Microsoft Learn&lt;/a&gt; and start whenever you like.&lt;/p&gt;

&lt;h2&gt;
  
  
  The three modules, in the recommended order
&lt;/h2&gt;

&lt;p&gt;The three modules are part of the plan, and I recommend going through them from there so you can see your progress across all three in one place.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Using AI for everyday tasks
&lt;/h3&gt;

&lt;p&gt;The module Uso de la inteligencia artificial para tareas diarias, which translates to using artificial intelligence for everyday tasks, has eight units and is aimed at students, professionals and teachers who use Microsoft Copilot. Its goals are to understand generative AI, identify ways to use it at home and at work, and write effective prompts while finding useful resources. It includes an exercise on personalizing and refining prompts, which is the most direct practice in the plan for learning how to ask AI for better results.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Introduction to AI concepts
&lt;/h3&gt;

&lt;p&gt;The module Introducción a los conceptos de inteligencia artificial, which translates to introduction to artificial intelligence concepts, has ten units and explains what kinds of solutions AI makes possible. It covers agents and generative AI, text and natural language, speech, computer vision and information extraction, and it ends with a unit on responsible AI and an exercise exploring AI workloads. It has no mandatory prerequisites, although the page itself recommends a conceptual understanding of machine learning and suggests completing the module Introducción a los conceptos de Machine Learning first, which isn't part of this plan.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Work smarter with AI
&lt;/h3&gt;

&lt;p&gt;The module Trabaja de manera más inteligente con IA, which translates to work smarter with AI, has nine units and is designed for professional users of Microsoft 365 Copilot, so the page asks for familiarity with productivity apps such as Word, Excel, PowerPoint and Outlook. When you finish it, you'll be able to explain what AI and generative AI are and how they differ from traditional tools, describe how AI and AI agents support daily work and why giving AI context improves results, and review AI generated content while applying responsible AI practices. It includes a specific exercise on reviewing AI generated content.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the order goes from using AI to working with it
&lt;/h2&gt;

&lt;p&gt;I chose this order so the learning moves from the concrete to the applied. You first practice with everyday tasks and learn to write good prompts, then you understand what's behind AI, such as text, speech and vision, and finally you bring that foundation to daily work, where reviewing what AI produces becomes part of your responsibility. Responsible AI appears in the last two modules, first as a concept and then as a review practice.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to start the plan step by step
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Open the &lt;a href="https://learn.microsoft.com/plans/d2w4u1tgn0j25z?sharingId=D34D672B88CE3FAC&amp;amp;wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;plan on Microsoft Learn&lt;/a&gt; in your browser.&lt;/li&gt;
&lt;li&gt;Sign in with your Microsoft account so Learn saves your progress.&lt;/li&gt;
&lt;li&gt;Start with the first module and follow the order shown.&lt;/li&gt;
&lt;li&gt;Complete each module through its assessment and summary, and return to the plan page to see how much is left.
## What to keep in mind before you start&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This plan is a study path and not a certification exam. The three modules were last updated between August 9 and September 30, 2026, according to the metadata of their pages, but Microsoft Learn changes often, so you may see units that differ from what I described here. If something doesn't match, what each module page shows is what applies.&lt;/p&gt;

&lt;h2&gt;
  
  
  If you want a technical certification afterwards
&lt;/h2&gt;

&lt;p&gt;When you finish the plan and want a more technical step, the &lt;a href="https://learn.microsoft.com/credentials/certifications/azure-ai-fundamentals/?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;Microsoft Certified Azure AI Fundamentals&lt;/a&gt; certification, with the AI-901 exam, is aimed according to its page at people starting a career in developing AI solutions, and it asks for Python knowledge and familiarity with Azure resources. That makes it a later and more technical step, and not a requirement for this plan.&lt;/p&gt;

&lt;p&gt;Which of these three areas would you most like to use AI better in, your studies, your first job or a project of your own?&lt;/p&gt;

</description>
      <category>ai</category>
      <category>microsoft</category>
      <category>beginners</category>
      <category>learning</category>
    </item>
    <item>
      <title>How to deploy to Azure with GitHub Copilot or Claude Code using Azure Skills and the Azure MCP Server</title>
      <dc:creator>Carlos José Castro Galante</dc:creator>
      <pubDate>Mon, 19 Oct 2026 22:39:00 +0000</pubDate>
      <link>https://dev.to/carlosjcastrog/how-to-deploy-to-azure-with-github-copilot-or-claude-code-using-azure-skills-and-the-azure-mcp-32p0</link>
      <guid>https://dev.to/carlosjcastrog/how-to-deploy-to-azure-with-github-copilot-or-claude-code-using-azure-skills-and-the-azure-mcp-32p0</guid>
      <description>&lt;p&gt;The 2026 Stack Overflow Developer Survey found that 73% of the developers who use AI coding assistants or agents use them every day, with Claude Code at 65.5% and GitHub Copilot at 58.7% among the agents they rely on most. The same survey shows that only 20% use AI for deploying, operating or troubleshooting production systems, which tells you that most of us still let the agent write the code and then take over ourselves when it's time to ship. The Azure Skills Plugin is Microsoft's attempt to close that gap, and in this post I'll explain what it installs, how to set it up in GitHub Copilot or Claude Code, how its prepare, validate and deploy workflow behaves, and which guardrails you should understand before you let an agent work on your subscription.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the plugin installs in your agent
&lt;/h2&gt;

&lt;p&gt;The plugin bundles two layers that Microsoft describes as the brain and the hands. The first layer is a set of Azure skills, which are folders with a &lt;code&gt;SKILL.md&lt;/code&gt; file holding workflows, decision trees and rules that the agent loads when your request matches the skill's description. When I cloned the repository on October 18, 2026, it had 28 skill folders, among them &lt;code&gt;azure-prepare&lt;/code&gt;, &lt;code&gt;azure-validate&lt;/code&gt;, &lt;code&gt;azure-deploy&lt;/code&gt;, &lt;code&gt;azure-diagnostics&lt;/code&gt;, &lt;code&gt;azure-compliance&lt;/code&gt;, &lt;code&gt;azure-quotas&lt;/code&gt;, &lt;code&gt;azure-storage&lt;/code&gt;, &lt;code&gt;entra-app-registration&lt;/code&gt; and &lt;code&gt;microsoft-foundry&lt;/code&gt;, and the list changes often because the repository syncs from Microsoft's main GitHub Copilot for Azure project.&lt;/p&gt;

&lt;p&gt;The second layer is the Azure MCP Server, which the README describes as more than 200 structured tools across more than 40 Azure services. Those tools are what let the agent list your resource groups, check prices, query logs or run &lt;code&gt;azd&lt;/code&gt; commands, and the &lt;a href="https://learn.microsoft.com/azure/developer/azure-skills/overview?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;Azure Skills overview&lt;/a&gt; sums up the split clearly by saying that the MCP server provides the individual operations while the skills provide the workflows that combine them. The README also lists Foundry MCP for model discovery, model deployment and agent workflows in Microsoft Foundry.&lt;/p&gt;

&lt;p&gt;Microsoft introduced the plugin in March 2026 in the post &lt;a href="https://devblogs.microsoft.com/all-things-azure/announcing-the-azure-skills-plugin/?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;Announcing the Azure Skills Plugin&lt;/a&gt;, and the repository is still very active, with its last sync on October 16, 2026 bringing &lt;code&gt;plugin.json&lt;/code&gt; to version 1.2.95.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's generally available and what isn't labeled
&lt;/h2&gt;

&lt;p&gt;The page &lt;a href="https://learn.microsoft.com/azure/developer/github-copilot-azure/introduction?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;What is GitHub Copilot for Azure&lt;/a&gt; says that the &lt;code&gt;azure-prepare&lt;/code&gt;, &lt;code&gt;azure-validate&lt;/code&gt; and &lt;code&gt;azure-deploy&lt;/code&gt; skills install automatically with that extension, and it lists general availability for Visual Studio Code, Visual Studio 2026, Claude Code and IntelliJ, while Visual Studio 2022 is still in public preview. Neither the plugin's README nor the Azure Skills pages on Microsoft Learn give the plugin a preview or GA label of its own, so I'd treat the GA status as applying to those clients and recheck the pages before you build a team process on top of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you need before installing
&lt;/h2&gt;

&lt;p&gt;You'll need an Azure subscription, and if you're a student, &lt;a href="https://azure.microsoft.com/free/students?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;Azure for Students&lt;/a&gt; gives you credit without a credit card. The README asks for Node.js 18 or later on your PATH because &lt;code&gt;npx&lt;/code&gt; starts the MCP server, the Azure CLI signed in with &lt;code&gt;az login&lt;/code&gt;, and the Azure Developer CLI signed in with &lt;code&gt;azd auth login&lt;/code&gt; if you plan to deploy. The skill reference page on Learn asks for Azure CLI 2.60.0 or later, and the VS Code skills extension also needs Git installed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node &lt;span class="nt"&gt;--version&lt;/span&gt;
az &lt;span class="nt"&gt;--version&lt;/span&gt;
az login
azd auth login
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Installing it in GitHub Copilot or Claude Code
&lt;/h2&gt;

&lt;p&gt;In Visual Studio Code you install the &lt;a href="https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-azure-mcp-server" rel="noopener noreferrer"&gt;Azure MCP Server extension&lt;/a&gt; from the Marketplace, and according to the README it also installs a companion extension that brings the skills, so the MCP server, Foundry MCP and the skills end up configured together.&lt;/p&gt;

&lt;p&gt;In GitHub Copilot CLI you add the marketplace the first time and then install the plugin from inside the chat.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/plugin marketplace add microsoft/azure-skills
/plugin install azure@azure-skills
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In Claude Code the README points to the official plugin marketplace, while the &lt;a href="https://learn.microsoft.com/azure/developer/azure-skills/install?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;install page on Microsoft Learn&lt;/a&gt; uses the same two marketplace commands shown above for Copilot CLI, so either route works.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/plugin install azure@claude-plugins-official
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The README also covers Cursor, Codex CLI, Gemini CLI, IntelliJ IDEA and APM, which installs the plugin across several agents from a single &lt;code&gt;apm.yml&lt;/code&gt;. Once it's installed, the README suggests three prompts to confirm each layer is working, and the second one is the most telling because it should come back with resource groups from your own account instead of a generic answer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;What Azure services would I need to deploy this project?
List my Azure resource groups.
What AI models are available in Microsoft Foundry?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How prepare, validate and deploy work together
&lt;/h2&gt;

&lt;p&gt;The three deployment skills are designed as a chain, and each one refuses to run if the previous one didn't finish. When you ask something like "Prepare this app for Azure", the &lt;code&gt;azure-prepare&lt;/code&gt; skill instructs the agent to write a skeleton of &lt;code&gt;.azure/deployment-plan.md&lt;/code&gt; in your workspace before doing anything else, and then to analyze whether the project is new, being modified or being modernized, gather requirements, scan the codebase, choose a recipe and map each component to an Azure service. The default recipe is the Azure Developer CLI, with Azure CLI, Bicep and Terraform as alternatives, and the skill tells the agent to present the finished plan and wait for your approval before it generates &lt;code&gt;azure.yaml&lt;/code&gt;, the &lt;code&gt;infra&lt;/code&gt; folder or any Dockerfile.&lt;/p&gt;

&lt;p&gt;The skill also carries some security decisions of its own. For example, it forbids generating &lt;code&gt;administratorLogin&lt;/code&gt; or &lt;code&gt;administratorLoginPassword&lt;/code&gt; in SQL Server Bicep files and requires Entra-only authentication instead, which fits well with what I covered in &lt;a href="https://dev.to/carlosjcastrog/why-azure-managed-identity-replaces-stored-credentials-and-how-to-use-it-in-2026-29cj"&gt;How to authenticate Azure apps without storing credentials&lt;/a&gt;. When preparation ends, the agent has to change the plan's status to &lt;code&gt;Ready for Validation&lt;/code&gt; before it can move on.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;azure-validate&lt;/code&gt; skill runs a workflow script that walks the agent through each check one at a time, recording its progress in &lt;code&gt;.azure/validate-status.json&lt;/code&gt; and writing a Validation Proof section in the plan with the commands it ran and their timestamps. The rule is that every check has to pass, and if you only asked to validate, the agent stops there and reports the results instead of deploying.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;azure-deploy&lt;/code&gt; skill only starts when the plan says &lt;code&gt;Validated&lt;/code&gt; and that proof section isn't empty, and the skill itself forbids the agent from setting that status by hand. From there it runs &lt;code&gt;azd up&lt;/code&gt;, &lt;code&gt;azd deploy&lt;/code&gt;, &lt;code&gt;terraform apply&lt;/code&gt; or &lt;code&gt;az deployment&lt;/code&gt; depending on the recipe, handles errors, checks that the endpoints respond, verifies the role assignments it created against your live subscription and returns the URLs with &lt;code&gt;https://&lt;/code&gt;. If you've followed my post on &lt;a href="https://dev.to/carlosjcastrog/azure-developer-cli-from-zero-to-deployed-in-three-commands-549m"&gt;the Azure Developer CLI&lt;/a&gt;, this is the same &lt;code&gt;azd&lt;/code&gt; workflow, with the agent doing the planning and the checks you'd otherwise do yourself.&lt;/p&gt;

&lt;p&gt;These are the kinds of prompts the README suggests for the workflow and for day two operations.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Prepare this app for Azure.
Validate my Azure deployment files before I run azd up.
Deploy this project to Azure Container Apps.
Troubleshoot why my container app is failing health probes.
What role should I assign to let this managed identity read blobs?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There's one inconsistency worth knowing about. The Azure Skills overview and quickstart on Microsoft Learn, last updated in April 2026, still call the plan &lt;code&gt;.azure/plan.md&lt;/code&gt;, while the current &lt;code&gt;SKILL.md&lt;/code&gt; and the &lt;code&gt;azure-deploy&lt;/code&gt; reference page from July use &lt;code&gt;.azure/deployment-plan.md&lt;/code&gt;, and the skill explicitly tells the agent not to use any other name. If you're following the quickstart and can't find the file, that's the reason.&lt;/p&gt;

&lt;h2&gt;
  
  
  The guardrails, and what they don't cover
&lt;/h2&gt;

&lt;p&gt;Every deployment skill links to a set of global rules that require the agent to ask you before any destructive action, which the rules define as deleting, overwriting, irreversible operations, provisioning expens`&lt;code&gt;ive resources and changing access policies or RBAC. The same rules say the agent must never assume your subscription or region, must not delete your project directory and may only run&lt;/code&gt;azd init` with a template in an empty directory.&lt;/p&gt;

&lt;p&gt;It's important to understand that these rules are instructions written for the model and not a technical lock. The Azure MCP Server acts with your Azure CLI credentials or a managed identity, and the &lt;a href="https://learn.microsoft.com/azure/developer/azure-mcp-server/overview?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;Azure MCP Server overview&lt;/a&gt; explains that access is governed by Azure RBAC, which means the agent can do whatever your account is allowed to do. Your host's approval settings matter just as much, because Microsoft's own quickstart for building with agents in VS Code describes Autopilot as running commands and editing files without asking you again. My recommendation is to try the plugin in a subscription or resource group where your account has only the permissions the project needs, and to keep tool approvals on until you've watched the workflow a few times.&lt;/p&gt;

&lt;p&gt;The plugin also collects usage telemetry by default. In Claude Code it registers hooks on session start and after each tool call that send skill and tool usage events, and the script skips them when &lt;code&gt;AZURE_MCP_COLLECT_TELEMETRY&lt;/code&gt; is set to &lt;code&gt;false&lt;/code&gt;, the same variable that turns off telemetry in the Azure MCP Server.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;`bash&lt;br&gt;
export AZURE_MCP_COLLECT_TELEMETRY=false&lt;br&gt;
`&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;If you want to go further with agents in your operations work, the learning path &lt;a href="https://learn.microsoft.com/training/paths/optimize-devops-ai-agents-azure/?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;Optimize DevOps with AI agents on Azure&lt;/a&gt; covers GitHub Copilot plan mode for cloud operations, the Azure SRE Agent and, in its last module, how to move traditional operations tasks to agent instructions, skills and multi-agent orchestration.&lt;/p&gt;

&lt;p&gt;When your coding agent shows you an Azure deployment plan, what's the first thing you check before you approve it?&lt;/p&gt;

</description>
      <category>azure</category>
      <category>githubcopilot</category>
      <category>mcp</category>
      <category>ai</category>
    </item>
    <item>
      <title>Framejury, a culling assistant that works offline and explains every photo it rejects</title>
      <dc:creator>Carlos José Castro Galante</dc:creator>
      <pubDate>Fri, 16 Oct 2026 14:33:50 +0000</pubDate>
      <link>https://dev.to/carlosjcastrog/framejury-a-culling-assistant-that-works-offline-and-explains-every-photo-it-rejects-14jm</link>
      <guid>https://dev.to/carlosjcastrog/framejury-a-culling-assistant-that-works-offline-and-explains-every-photo-it-rejects-14jm</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week2-2026-10-12"&gt;Hacktoberfest Open-Source AI Challenge Week 2: Side Hustle&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;Framejury looks through a folder of event photos, groups the frames that were shot seconds apart and look nearly the same, and marks the best one of each group to keep, while every other frame is rejected with a sentence that says why, the numbers behind that sentence are one click away, and a group that is too close to call gets flagged so the photographer makes the final choice. Any decision can be changed by the photographer and undone later, and nothing is uploaded anywhere, because the vision model runs on the same computer through Ollama.&lt;/p&gt;

&lt;p&gt;The person who would pay for this is the photographer who delivers events such as graduations, weddings or school sports days, where a single afternoon can produce thousands of frames and long bursts of the same moment, and picking the keeper from each burst is hours of unbilled work. Paid tools that automate that work already exist, mostly as monthly subscriptions and with some of the cloud ones charging for each exported photo, so Framejury goes after the same hours with a different deal, since it's free and MIT licensed, it keeps client photos on the photographer's disk and it doesn't ask to be trusted, because every decision comes with its evidence.&lt;/p&gt;

&lt;p&gt;The value Framejury offers is attention more than speed, because on my laptop, which has an RTX 4050 with 6 GB of video memory, the analysis takes between 27 and 36 seconds for seven photos, and in raw time that's about as fast as a person culling at four seconds per photo. The difference is that the machine can work while the photographer does something else and then ask for a human look only at the photos that deserve one. The results page estimates the time saved by assuming four seconds of manual work per photo, an assumption the user can edit, and it subtracts the analysis time, so on my small test folders it plainly reports none, and I left it that way because an inflated number would be worse than no number.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/XVh0xfndIb4" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;The video lasts one minute and 18 seconds, has no voice and tells the story with captions, with the analysis stage sped up, and I switched the internet connection off while the analysis ran, so what you see is the offline behavior. The sample photos are by Trougnouf (Benoit Brummer) from the 2023 ULB graduation ceremony, published on Wikimedia Commons under CC BY-SA 4.0, and they aren't part of the repository, while the frames below show the interface as a photographer would use it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7v6g1vulqtr8h8xxy2n2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7v6g1vulqtr8h8xxy2n2.png" alt="A group of five photos with one Keep and four Reject decisions" width="799" height="394"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;One burst, one keeper, and a reason under every photo.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2nw4yo8c7ifcy10yxfuq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2nw4yo8c7ifcy10yxfuq.png" alt="The Evidence panel open on a rejected photo" width="800" height="396"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The Evidence panel shows the sharpness relative to the best frame, the clipped areas and what the model observed.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F582v484yrs9ghyzsqga9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F582v484yrs9ghyzsqga9.png" alt="The export panel with CSV, folder copy and XMP options" width="800" height="372"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Exports only write copies or new companion files.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fd5f01dth73kh4xqsmv8l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fd5f01dth73kh4xqsmv8l.png" alt="A notice explaining that Ollama isn't running" width="799" height="380"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;When Ollama is off, the interface explains what happened, offers a command to copy and a retry button.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/carlosjcastro" rel="noopener noreferrer"&gt;
        carlosjcastro
      &lt;/a&gt; / &lt;a href="https://github.com/carlosjcastro/framejury" rel="noopener noreferrer"&gt;
        framejury
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A local photo culling tool for event photographers that groups near identical shots, picks the best one of each group with Gemma 4 through Ollama and explains every decision in plain words, while your photos never leave your computer.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/carlosjcastro/framejury/docs/framejury-banner.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fcarlosjcastro%2Fframejury%2FHEAD%2Fdocs%2Fframejury-banner.png" alt="Framejury, local photo culling that explains every decision"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Framejury&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;Framejury is a photo culling tool for event photographers that looks through a folder of photos, groups the shots that are nearly identical, picks the best one of each group and explains the choice in words you can read, check against the evidence and change with a single click. Everything runs on your own computer, which means your photos are never uploaded anywhere and the vision model that compares them runs locally through Ollama.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Demo&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;A minute and a half with captions and no sound needed. &lt;a href="https://youtu.be/XVh0xfndIb4" rel="nofollow noopener noreferrer"&gt;Watch it on YouTube&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Sample photos in the demo are by Trougnouf (Benoit Brummer), from the 2023 ULB graduation ceremony, via Wikimedia Commons, under CC BY-SA 4.0.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Why Framejury exists&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;After a wedding, a concert or a company event, a photographer comes home with thousands of frames, and a large share of them are bursts of the same moment, so choosing the…&lt;/p&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/carlosjcastro/framejury" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;The repository has a Python package called &lt;code&gt;framejury&lt;/code&gt; in &lt;code&gt;backend&lt;/code&gt;, a Next.js interface in &lt;code&gt;frontend&lt;/code&gt;, and the MIT license together with a security policy and a contributing guide. It needs Python 3.10 or newer, Node.js 20.9 or newer and Ollama, and after pulling the model with &lt;code&gt;ollama pull gemma4:e4b-it-qat&lt;/code&gt; the two parts start in separate terminals.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;uvicorn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;framejury.api:app&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--host&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;127.0.0.1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--port&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;8000&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;npm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;run&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;dev&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A graphics card speeds up the model step, although Ollama also runs on a processor alone, only more slowly, and the analysis can skip the model and work from measurements only. The engine also runs without the interface through &lt;code&gt;python -m framejury analyze &amp;lt;folder&amp;gt;&lt;/code&gt;, which writes a report and plain lists of the photos to keep, reject and review, and the backend has 42 tests that use a simulated Ollama connection, so they run without the model, while the history in the repository starts on October 13.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;Framejury runs as two local processes, a FastAPI service that handles jobs, thumbnails, manual overrides and exports, and a Next.js 16 interface with React 19 and Tailwind 4 that talks to it, while the open pieces are Gemma 4, in the quantized build &lt;code&gt;gemma4:e4b-it-qat&lt;/code&gt;, and Ollama, which serves it on localhost to the engine through a small httpx client.&lt;/p&gt;

&lt;p&gt;A group of photos starts with ordinary image analysis, where sharpness is the variance of the Laplacian computed on a six by six grid of tiles over an image reduced to 1,024 pixels and the score is the 90th percentile of those tiles, so a face in focus against a soft background still counts as sharp. Exposure counts the pixels that are nearly white or nearly black, a 64 bit difference hash gives each photo a visual fingerprint, and the capture time comes from EXIF with the file's modified date as a fallback.&lt;/p&gt;

&lt;p&gt;A photo joins the current group when it was taken within 12 seconds of the previous one and its hash differs by at most 24 of 64 bits from at least one photo already in the group, both limits being settings in the interface, and by default only the four sharpest photos of each group go on to the model while the rest are rejected right away with their sharpness relative to the best frame, which keeps the cost of a group bounded.&lt;/p&gt;

&lt;p&gt;The model receives that shortlist as images and answers with JSON that a schema forces into shape, giving for each photo the number of people, how many of them have closed eyes, an expression score and a subject focus score from 1 to 5, plus the photo it would keep, with thinking turned off and the temperature set to 0. Python rules in &lt;code&gt;decision.py&lt;/code&gt;, a file of about 200 lines, then turn those observations and the measurements into a score that follows these weights.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What counts&lt;/th&gt;
&lt;th&gt;Points&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Sharpness relative to the best photo of the group&lt;/td&gt;
&lt;td&gt;up to 60&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Subject focus reported by the model&lt;/td&gt;
&lt;td&gt;up to 15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Expression reported by the model&lt;/td&gt;
&lt;td&gt;up to 15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The model's pick, when both passes agree&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hard flag, which means under 60% of the best sharpness, subject focus of 2 or less, any closed eyes, more than 25% blown highlights or more than 40% crushed shadows&lt;/td&gt;
&lt;td&gt;minus 50&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Soft flag, which means an expression of 2 or less&lt;/td&gt;
&lt;td&gt;minus 15&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The model's vote is worth 5 points out of a possible 95, so Gemma works as a witness whose observations feed the rules and never as the judge, and a keeper that carries a hard flag becomes Review instead of Keep, while a group whose two best scores are within 5 points is flagged as needing a look and the runner up's reason starts with a note that it's very close to the kept photo.&lt;/p&gt;

&lt;p&gt;Language models tend to favor the image that comes first, so each group is judged twice with the order reversed and the model's pick only counts when both passes agree, and when they disagree the rules decide on the measurements alone.&lt;/p&gt;

&lt;p&gt;The most instructive bug came from the notes, because Gemma kept writing sentences such as "the second image has closed eyes", which mean nothing on the card of a single photo, so I changed the prompt to ask for a description of each photo on its own and added a filter that discards any sentence pointing at a photo by position, in English or in Spanish, which makes the card fall back to the reason written by the rules.&lt;/p&gt;

&lt;p&gt;The same distrust applies to the closing note that the model can write after an analysis, since the engine gives it only a JSON of facts and discards the answer if it mentions a file name or a number that isn't in those facts, letting a template write a plain note instead, which is also what happens when Ollama can't be reached.&lt;/p&gt;

&lt;p&gt;Because the engine reads private photos, it listens only on &lt;code&gt;127.0.0.1&lt;/code&gt;, refuses any host name other than &lt;code&gt;localhost&lt;/code&gt; and &lt;code&gt;127.0.0.1&lt;/code&gt;, rejects requests that change something when they come from another origin, and checks that every thumbnail path stays inside the folder the user chose.&lt;/p&gt;

&lt;p&gt;Nothing about the analysis is locked in, since the advanced options let the user choose which criteria count among sharpness, exposure, closed eyes, subject focus and expression, limit the file types to JPEG, PNG, WebP or RAW, include subfolders, skip files by pattern, adjust the time window, the similarity threshold, the number of candidates and the strictness about soft shots, and compare a selection chosen by hand as one group, with the reasons written in English or in Spanish.&lt;/p&gt;

&lt;p&gt;On the interface side, the home page checks that Ollama and the model are ready before anything starts and every failure becomes a plain message with a command to copy, each decision is shown with an icon and a corner mark that differ between Keep, Review and Reject so color is never the only signal, buttons expose their pressed state and are at least 44 pixels tall on phones, and animations follow the reduced motion setting unless the user opts in with a switch in the header, while a print view turns the results into a PDF that can be sent to a client.&lt;/p&gt;

&lt;p&gt;Exports copy the Keep and Review photos into a &lt;code&gt;framejury-export&lt;/code&gt; folder next to the originals, bringing along RAW and JPEG pairs and existing sidecar files and adding the rejects too if the user asks, a CSV lists every decision with its reason and measurements, and optional XMP sidecars carry a color label, green for Keep and red for Reject, without overwriting any sidecar that already exists, an option that is marked experimental in the interface because Lightroom reads those labels for RAW files and ignores them for JPEG, and I haven't opened the sidecars in Lightroom myself.&lt;/p&gt;

&lt;p&gt;I used Claude as an assistant for guidance, code review and fixing errors, including drafts of some of the fixes, and I ran, tested and committed every change myself, and Framejury also depends on the open source work of others, namely Gemma from Google, Ollama, Pillow, NumPy, OpenCV, rawpy, httpx, FastAPI, Uvicorn, Next.js, React and Tailwind, as well as on the sample photos credited above.&lt;/p&gt;

&lt;p&gt;The small Gemma model has the weaknesses of a small model, since the notes it writes are often generic, closed eyes are harder to detect behind glasses or on small faces and RAW files are judged from their embedded preview and not from the full sensor data, while the Review status is rare because close calls stay as Keep or Reject with a flag on the group, which is also why the header can say zero reviews while the results page counts photos that deserve a look. One burst in the Wikimedia set also split into three groups because its frames had been cropped differently, and I would fix these points first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Does Open Innovation Matter?
&lt;/h2&gt;

&lt;p&gt;A cloud service needs the photos to leave the photographer's computer, and for client work that decision isn't always the photographer's to make, whereas with Gemma running through Ollama the only network use is the single download of the model, plus the packages and fonts needed to install and start the interface, so the analysis itself works in a venue or on a train without a connection.&lt;/p&gt;

&lt;p&gt;Cost follows the same logic, since a group can send up to eight image evaluations to the model, four photos in two passes, and a hosted vision API would bill each of them on every run, while a local model costs electricity on a machine the photographer already owns.&lt;/p&gt;

&lt;p&gt;Control is the third reason, because the engine lists the models installed in Ollama and flags which ones can see images, so a photographer with a stronger graphics card can pick a larger model in the advanced options and one with an older computer can pick a smaller one without waiting for me, and a pinned local model at temperature 0 also behaves next month the way it does today, while a hosted model can change underneath the tool and quietly alter the decisions on a folder that was already delivered.&lt;/p&gt;

&lt;p&gt;The rules are the last piece, and they're ordinary Python that anyone can read and change, so much so that the contributing guide asks that new criteria arrive as readable rules in &lt;code&gt;decision.py&lt;/code&gt; and not as hidden behavior inside a prompt, and a closed API would have given me a faster first version but not a tool I could ask a photographer to trust with a client's photos.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prize Categories
&lt;/h2&gt;

&lt;p&gt;I'm entering Best Use of Gemma, because Framejury runs Gemma 4 locally through Ollama to judge closed eyes, expression and subject focus with structured output, asks it twice in opposite order to reduce position bias, and lets the user swap the model for any other vision model installed on their machine.&lt;/p&gt;

&lt;p&gt;If you shoot events, which decisions would you still want to make yourself, and what would a culling tool have to show you before you trusted its keepers?&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
      <category>gemma</category>
      <category>opensource</category>
    </item>
    <item>
      <title>How to trace and evaluate an AI agent in Microsoft Foundry, from the first trace to production monitoring</title>
      <dc:creator>Carlos José Castro Galante</dc:creator>
      <pubDate>Thu, 15 Oct 2026 23:04:16 +0000</pubDate>
      <link>https://dev.to/carlosjcastrog/how-to-trace-and-evaluate-an-ai-agent-in-microsoft-foundry-from-the-first-trace-to-production-18ip</link>
      <guid>https://dev.to/carlosjcastrog/how-to-trace-and-evaluate-an-ai-agent-in-microsoft-foundry-from-the-first-trace-to-production-18ip</guid>
      <description>&lt;p&gt;Microsoft Foundry splits agent observability into three capabilities, and each one answers a different question. Tracing shows what the agent did during a run, evaluation scores how good its answers were against criteria you choose, and monitoring keeps asking both questions once the agent is in front of users. This post goes through them in the order you'll usually need them, using the official Python samples, and it says which parts Microsoft labels as generally available and which as preview, because that label decides how much weight each piece can carry.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's generally available and what isn't
&lt;/h2&gt;

&lt;p&gt;Tracing is generally available for prompt agents and hosted agents, while workflow agents and external agents are still in preview. Microsoft's &lt;a href="https://devblogs.microsoft.com/foundry/build-2026-from-observability-to-roi-for-ai-agents-on-any-framework/?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;Build announcement from June 3, 2026&lt;/a&gt; says tracing and evaluations reached general availability in the spring, but the reference pages still attach preview labels to individual pieces. On the &lt;a href="https://learn.microsoft.com/azure/foundry/concepts/evaluation-evaluators/agent-evaluators?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;agent evaluators page&lt;/a&gt;, Task Adherence, Task Completion, Intent Resolution, Customer Satisfaction, Output Quality and Tool Use Quality are labeled preview, while the tool evaluators such as Tool Call Accuracy don't carry any label at all. On the &lt;a href="https://learn.microsoft.com/azure/foundry/observability/how-to/how-to-monitor-agents-dashboard?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;monitoring page&lt;/a&gt;, agent metrics, recurring evaluations, red team scans and alerts are labeled preview, and the page says Microsoft doesn't recommend its preview items for production workloads. The GitHub Action for evaluations is in preview too. I checked these labels on October 14, 2026, and since they change quickly, it's worth reopening the linked pages before you build on any of them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1, connect Application Insights and read your first trace
&lt;/h2&gt;

&lt;p&gt;Tracing for prompt agents and hosted agents works on the server side, so you don't need to change anything in your code. With the New Foundry toggle turned on, open your project, go to Agents, select Traces and choose Connect, then attach an existing Application Insights resource or create a new one. From that point the runs of an agent hosted in Foundry are recorded through OpenTelemetry, including the model calls, the tool invocations, the decisions the agent took and its dependencies on other services, as described in the &lt;a href="https://learn.microsoft.com/azure/foundry/concepts/observability?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;observability overview&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To read the traces you'll need the Log Analytics Reader role on the connected Application Insights resource, plus Privileged Monitoring Data Reader when the underlying Log Analytics tables are protected. After you run the agent once, the Traces tab lets you search, filter and sort the traces from the last 90 days, open one to step through its spans, or search by Response ID or Trace ID and open a Conversation ID to see the history, token counts, run steps, tool calls, inputs and outputs. If nothing shows up, the &lt;a href="https://learn.microsoft.com/azure/foundry/observability/how-to/trace-agent-setup?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;setup page&lt;/a&gt; recommends waiting a few minutes and refreshing before you head to its troubleshooting section. That page also covers client side tracing with &lt;code&gt;azure-core-tracing-opentelemetry&lt;/code&gt;, which is optional and meant for the code of your own application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2, evaluate the agent with a small dataset
&lt;/h2&gt;

&lt;p&gt;A trace tells you what happened but not whether the answer was good, and that's the job of evaluation. The sample below follows the official page on &lt;a href="https://learn.microsoft.com/azure/foundry/observability/how-to/evaluate-agent?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;evaluating your AI agents&lt;/a&gt;, with the rubric generation step removed so it only uses evaluators that ship with Foundry. You'll need the Foundry User role on the project, which Microsoft renamed from Azure AI User without changing the role IDs, and a chat completion model deployment that acts as the judge for the evaluators that use a model.&lt;/p&gt;

&lt;p&gt;That page asks for &lt;code&gt;azure-ai-projects&lt;/code&gt; 2.4.0 or later, while other pages in the same documentation list lower minimums, so it's safer to install the latest release.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="s2"&gt;"azure-ai-projects&amp;gt;=2.4.0"&lt;/span&gt; azure-identity
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set the environment variables &lt;code&gt;AZURE_AI_PROJECT_ENDPOINT&lt;/code&gt; and &lt;code&gt;AZURE_AI_MODEL_DEPLOYMENT_NAME&lt;/code&gt;, then create a JSONL file called &lt;code&gt;test-queries.jsonl&lt;/code&gt; where each line holds a &lt;code&gt;query&lt;/code&gt; field. These three lines are only example questions, so swap them for the questions your agent gets.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{"query": "What is the difference between an Azure region and an availability zone?"}
{"query": "When should I use Azure Functions instead of a virtual machine?"}
{"query": "How do I stop a storage account from being publicly accessible?"}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first part of the script creates the clients and uploads the dataset to your project.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;azure.identity&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;DefaultAzureCredential&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;azure.ai.projects&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AIProjectClient&lt;/span&gt;

&lt;span class="n"&gt;endpoint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;AZURE_AI_PROJECT_ENDPOINT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;model_deployment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;AZURE_AI_MODEL_DEPLOYMENT_NAME&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;AGENT_NAME&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;my-agent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;  &lt;span class="c1"&gt;# replace with the name of your agent
&lt;/span&gt;
&lt;span class="n"&gt;project_client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AIProjectClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;credential&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;DefaultAzureCredential&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;project_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_openai_client&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;dataset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;project_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datasets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;upload_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;agent-test-queries&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;./test-queries.jsonl&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second part defines what to measure and creates the evaluation. Coherence is scored by the judge deployment, while Violence comes from the risk and safety group, which the official sample runs without a judge deployment.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;azure.ai.projects.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TestingCriterionAzureAIEvaluator&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;openai.types.eval_create_params&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;DataSourceConfigCustom&lt;/span&gt;

&lt;span class="n"&gt;testing_criteria&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nc"&gt;TestingCriterionAzureAIEvaluator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;azure_ai_evaluator&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Coherence&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;evaluator_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;builtin.coherence&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;initialization_parameters&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;deployment_name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;model_deployment&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;data_mapping&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;query&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{item.query}}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;response&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{sample.output_text}}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nc"&gt;TestingCriterionAzureAIEvaluator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;azure_ai_evaluator&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Violence&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;evaluator_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;builtin.violence&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;data_mapping&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;query&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{item.query}}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;response&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{sample.output_text}}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;data_source_config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;DataSourceConfigCustom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;custom&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;item_schema&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;object&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;properties&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;query&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;string&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}},&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;required&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;query&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;include_sample_schema&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;evaluation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;evals&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Agent Quality Evaluation&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;data_source_config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;data_source_config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;testing_criteria&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;testing_criteria&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The third part starts the run. With the &lt;code&gt;azure_ai_target_completions&lt;/code&gt; source, Foundry sends every query to the agent named in the target and evaluates the answers it gets back, which is why the mappings above read from &lt;code&gt;sample.output_text&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;eval_run&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;evals&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;runs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;eval_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;evaluation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Agent Evaluation Run&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;data_source&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;azure_ai_target_completions&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;source&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;file_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;input_messages&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;template&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;template&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;message&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;input_text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{item.query}}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;target&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;azure_ai_agent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;AGENT_NAME&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;run&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;evals&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;runs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;retrieve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;run_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;eval_run&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;eval_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;evaluation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;completed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;failed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;
    &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Status: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Report URL: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;report_url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the response, &lt;code&gt;result_counts&lt;/code&gt; gives the total, passed, failed and errored rows, &lt;code&gt;per_testing_criteria_results&lt;/code&gt; breaks the same numbers down by evaluator, and each row carries a &lt;code&gt;score&lt;/code&gt;, a &lt;code&gt;label&lt;/code&gt;, a &lt;code&gt;reason&lt;/code&gt;, a &lt;code&gt;threshold&lt;/code&gt; and a &lt;code&gt;passed&lt;/code&gt; flag for every evaluator. The &lt;code&gt;report_url&lt;/code&gt; opens the same results in the Foundry portal, where they also show up under the Evaluations tab, and the &lt;code&gt;reason&lt;/code&gt; field is where I'd start reading when a row fails.&lt;/p&gt;

&lt;p&gt;If your agent calls tools, the tool use evaluators are the ones worth adding next. Tool Call Accuracy checks that the agent made the right calls with correct parameters and without redundancy, Tool Selection checks that it chose the tools it needed and no extra ones, Tool Input Accuracy checks the parameters, Tool Output Utilization checks whether the agent used the results correctly, and Tool Call Success checks that the calls ended without technical errors. Four of them take &lt;code&gt;tool_definitions&lt;/code&gt; as an input, and Tool Call Accuracy scores on a scale of one to five before a threshold turns that score into a pass or a fail.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it costs and where it's restricted
&lt;/h2&gt;

&lt;p&gt;Risk and safety evaluations, which include the Violence evaluator used above, and agents playground evaluations are billed on consumption, and playground evaluations are enabled by default for every Foundry project. To turn them off, clear all the evaluators in the playground's metrics selector. Some features also have regional restrictions, namely rubric generation, synthetic and trace based dataset creation, and the risk and safety evaluators, so check the &lt;a href="https://learn.microsoft.com/azure/foundry/concepts/evaluation-regions-limits-virtual-network?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;supported regions&lt;/a&gt; before you run the sample in a project you already have. The monitoring data itself lives in the connected Application Insights resource, so its retention and billing follow however you've configured that resource.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3, keep evaluating after release
&lt;/h2&gt;

&lt;p&gt;The monitoring page describes continuous evaluation, which runs an evaluation on sampled traffic while the agent serves users. The code below creates an evaluation with an &lt;code&gt;azure_ai_source&lt;/code&gt; and attaches a rule that runs it whenever a response from the named agent completes. The project managed identity needs the Foundry User role for continuous evaluation rules to run, and the rule is capped by &lt;code&gt;max_hourly_runs&lt;/code&gt;, which the sample sets to 100 and which Microsoft documents as the default limit per hour. Once the cap is reached, further runs are skipped until the next hour or until you raise the value.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;azure.identity&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;DefaultAzureCredential&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;azure.ai.projects&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AIProjectClient&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;azure.ai.projects.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;EvaluationRule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ContinuousEvaluationRuleAction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;EvaluationRuleFilter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;EvaluationRuleEventType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;endpoint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;AZURE_AI_PROJECT_ENDPOINT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;agent_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;AZURE_AI_AGENT_NAME&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="nf"&gt;with &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nc"&gt;DefaultAzureCredential&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;credential&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nc"&gt;AIProjectClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;credential&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;credential&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;project_client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;project_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_openai_client&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;openai_client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;eval_object&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;openai_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;evals&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Continuous Evaluation&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;data_source_config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;azure_ai_source&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;scenario&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;responses&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;testing_criteria&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;azure_ai_evaluator&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;violence_detection&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;evaluator_name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;builtin.violence&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;rule&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;project_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;evaluation_rules&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_or_update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;my-continuous-eval-rule&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;evaluation_rule&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;EvaluationRule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;display_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;My Continuous Eval Rule&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Runs on agent response completions&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;ContinuousEvaluationRuleAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;eval_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;eval_object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_hourly_runs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;
            &lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;event_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;EvaluationRuleEventType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RESPONSE_COMPLETED&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nb"&gt;filter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;EvaluationRuleFilter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agent_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;agent_name&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;enabled&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Rule created (id: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;rule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To read the results, open the agent in the Foundry portal and select Monitor. The dashboard shows token usage, latency, run success rate, evaluation metrics and red teaming results for the time range you pick, and the documentation gives two starting points for interpreting it. Latency above ten seconds may point to model throttling, complex tool calls or network issues, and a run success rate below 95 percent warrants an investigation of the failed runs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running the same evaluation on every commit
&lt;/h2&gt;

&lt;p&gt;You can also run the evaluation from Step 2 in a pipeline through the &lt;code&gt;microsoft/ai-agent-evals@v3-beta&lt;/code&gt; GitHub Action, which the &lt;a href="https://learn.microsoft.com/azure/foundry/how-to/evaluation-github-action?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;documentation&lt;/a&gt; labels as preview. The step below goes after the checkout and an Azure login step that uses OpenID Connect, so the workflow also needs the &lt;code&gt;id-token: write&lt;/code&gt; permission and the client, tenant and subscription IDs stored as GitHub Actions variables.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run Evaluation&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;microsoft/ai-agent-evals@v3-beta&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;azure-ai-project-endpoint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;your-ai-project-endpoint&amp;gt;"&lt;/span&gt;
          &lt;span class="na"&gt;deployment-name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;your-deployment-name&amp;gt;"&lt;/span&gt;
          &lt;span class="na"&gt;agent-ids&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;your-ai-agent-ids&amp;gt;"&lt;/span&gt;
          &lt;span class="na"&gt;data-path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.workspace }}/path/to/your/data-file&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;agent-ids&lt;/code&gt; input takes values such as &lt;code&gt;my-agent:1,my-agent:2&lt;/code&gt;, an optional &lt;code&gt;baseline-agent-id&lt;/code&gt; sets the version used as the comparison, and the results show up in the workflow run summary with the score of each metric, confidence intervals and, when you compare several agents, a pairwise statistical comparison that tells you whether a difference is meaningful or just random variation. That makes it a reasonable quality gate for changes to an agent's instructions or model, since the same dataset runs against both versions.&lt;/p&gt;

&lt;p&gt;If you'd rather see the three pieces working together before you try them, the Azure Friday episode &lt;a href="https://learn.microsoft.com/shows/azure-friday/observe-and-evaluate-agents-for-quality-and-safety-in-microsoft-foundry?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;Observe and evaluate agents for quality and safety in Microsoft Foundry&lt;/a&gt; walks through evaluation, tracing and monitoring with a live demo in the Foundry portal and in Visual Studio Code.&lt;/p&gt;

&lt;p&gt;Which of those three questions is the hardest to answer about your agents today, what they did, how good their answers were, or whether they stay that good after release?&lt;/p&gt;

</description>
      <category>azure</category>
      <category>ai</category>
      <category>microsoft</category>
      <category>cloud</category>
    </item>
    <item>
      <title>How I prepared for the AZ-900 and why it's the certification where you should start with Azure</title>
      <dc:creator>Carlos José Castro Galante</dc:creator>
      <pubDate>Tue, 29 Sep 2026 00:49:31 +0000</pubDate>
      <link>https://dev.to/carlosjcastrog/how-i-prepared-for-the-az-900-and-why-its-the-certification-where-you-should-start-with-azure-5e28</link>
      <guid>https://dev.to/carlosjcastrog/how-i-prepared-for-the-az-900-and-why-its-the-certification-where-you-should-start-with-azure-5e28</guid>
      <description>&lt;p&gt;In 2023, when I prepared for the AZ-900, I was studying Computer Science and had limited hands-on experience with cloud infrastructure. What stood out when studying was the exam's scope: it doesn't start with Azure services but with cloud computing fundamentals, which makes it useful for both technical and non-technical profiles.&lt;/p&gt;

&lt;p&gt;The AZ-900 (Microsoft Azure Fundamentals) is the introductory certification to the Azure ecosystem. It doesn't require prior tech experience, doesn't assume knowledge of programming, and doesn't expect you to have administered a server. It's designed for anyone who wants to understand how Microsoft's cloud works, both from the technical side and the organizational and financial one.&lt;/p&gt;




&lt;h2&gt;
  
  
  How I got to this exam
&lt;/h2&gt;

&lt;p&gt;I got access through a program run by Microsoft and the &lt;a href="https://www.fundacionedison.org.ar/" rel="noopener noreferrer"&gt;Fundación Tomás Alva Edison&lt;/a&gt; in Mendoza, Argentina. The foundation has a partnership with Microsoft to bring these certifications to students and young people in the region.&lt;/p&gt;

&lt;p&gt;Today the exam is available through two channels. Most people book it through Pearson Vue, either in person at an authorized testing center or remotely from home through its OnVUE system. Students and educators can also book it through Certiport, which was the platform I used myself back in 2023 and remains active today as an option for that audience.&lt;/p&gt;

&lt;p&gt;I'm a Microsoft Student Ambassador, and part of what I do in that role is help other students work through this same path. That's why I put together a study plan on Microsoft Learn that organizes all the official modules in the order I'd recommend going through them: &lt;a href="https://learn.microsoft.com/plans/odp3ugt4okmdxk?source=docs&amp;amp;sharingId=D34D672B88CE3FAC&amp;amp;wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;AZ-900 Study Plan&lt;/a&gt;. You can start it right now while you read this.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the exam covers
&lt;/h2&gt;

&lt;p&gt;The AZ-900 has between 40 and 60 questions, requires a minimum score of 700 out of 1000 to pass, and you'll have 45 minutes to complete the assessment. There are no hands-on labs or case studies, and there's no penalty for wrong answers, so it's always worth answering even when you're not sure.&lt;/p&gt;

&lt;p&gt;The cost is $99 USD in the United States, though Microsoft adjusts pricing by country. In Argentina, the official price is $59 USD. Check the exact price for your region on the official scheduling page before booking.&lt;/p&gt;

&lt;p&gt;The content is organized into three major areas.&lt;/p&gt;




&lt;h2&gt;
  
  
  Cloud concepts
&lt;/h2&gt;

&lt;p&gt;The first area covers foundational concepts and is the most theoretical of the three. Before getting into Azure services specifically, the exam evaluates whether you understand what the cloud is and why it exists.&lt;/p&gt;

&lt;h3&gt;
  
  
  The consumption model
&lt;/h3&gt;

&lt;p&gt;One of the most important concepts is the difference between CapEx and OpEx. CapEx (Capital Expenditure) is the upfront investment in physical infrastructure you own: you buy servers, set them up, maintain them, and that cost stays with you for the life of the equipment. OpEx (Operational Expenditure) is the operating expense model: you pay for what you use, when you use it, with no upfront hardware investment. The cloud operates under OpEx, and that shifts the financial equation entirely, especially for smaller organizations.&lt;/p&gt;

&lt;h3&gt;
  
  
  The service models
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;IaaS (Infrastructure as a Service)&lt;/strong&gt; gives you the highest level of control. You rent the infrastructure (virtual servers, networking, storage) and you manage everything running on top: the operating system, middleware, applications, and data. A virtual machine on Azure is the clearest example. Think of it like renting an empty apartment where the building is already there but you decide everything that goes inside.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PaaS (Platform as a Service)&lt;/strong&gt; removes the responsibility for the operating system and underlying infrastructure. You get a ready-to-use environment for building and deploying applications. Azure App Service fits here: you push your code and the platform handles the rest. It's like working at a coworking space where the desk, internet, and infrastructure are already set up and you just show up to work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SaaS (Software as a Service)&lt;/strong&gt; is the model most people use every day without thinking about it. Microsoft 365, Teams, Gmail, and Notion are all SaaS. You use a finished application without thinking about any of the infrastructure underneath.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deployment models
&lt;/h3&gt;

&lt;p&gt;Public cloud is infrastructure the provider shares across multiple customers. Private cloud is infrastructure dedicated exclusively to a single organization. Hybrid cloud combines both, which is especially useful for organizations with legacy systems they can't fully migrate.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://learn.microsoft.com/plans/odp3ugt4okmdxk?source=docs&amp;amp;sharingId=D34D672B88CE3FAC&amp;amp;wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;study plan&lt;/a&gt; covers all of this with hands-on exercises where you can practice without needing your own subscription.&lt;/p&gt;




&lt;h2&gt;
  
  
  Azure architecture and services
&lt;/h2&gt;

&lt;p&gt;This is the most extensive area of the exam and the one that covers the greatest number of individual services. It's also the one that requires the most study time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Regions and availability zones
&lt;/h3&gt;

&lt;p&gt;Azure is physically distributed across more than 70 regions worldwide. A region is a geographic area containing one or more datacenters. When you create a resource, you choose which region it lives in, and that choice affects latency, cost, and your organization's data residency requirements.&lt;/p&gt;

&lt;p&gt;Within many regions there are availability zones, which are physically separate datacenters within that same region, connected by low-latency networks. If one zone fails, the others keep running. Azure also has region pairs: each primary region has a secondary region that's geographically distant, for disaster recovery at a larger scale.&lt;/p&gt;

&lt;h3&gt;
  
  
  Subscriptions and resource groups
&lt;/h3&gt;

&lt;p&gt;Every Azure resource lives inside a well-defined hierarchy. The top level is the Microsoft Entra ID tenant, which represents your organization. Within the tenant there are subscriptions, which are the billing unit. Within each subscription you create resource groups, which are logical containers that group related services for a project or application. Every Azure resource always belongs to exactly one resource group.&lt;/p&gt;

&lt;h3&gt;
  
  
  Compute
&lt;/h3&gt;

&lt;p&gt;Azure &lt;strong&gt;Virtual Machines&lt;/strong&gt; are the classic IaaS model: a full server in the cloud that you manage from the operating system upward. They're the most flexible option and also the one that carries the most management overhead.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Azure App Service&lt;/strong&gt; is the PaaS platform for web apps and APIs. It supports .NET, Java, Node.js, Python, and PHP, and you don't manage the underlying server.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Azure Functions&lt;/strong&gt; is the serverless option. Functions execute only when something triggers them (an HTTP request, an event, a queue message) and you pay only for actual execution time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Azure Kubernetes Service (AKS)&lt;/strong&gt; is the managed Kubernetes service for orchestrating containers at scale. &lt;strong&gt;Azure Container Instances&lt;/strong&gt; is the simpler option for running individual containers without needing a full cluster.&lt;/p&gt;

&lt;h3&gt;
  
  
  Networking
&lt;/h3&gt;

&lt;p&gt;A &lt;strong&gt;Virtual Network (VNet)&lt;/strong&gt; is the private network where your Azure resources live. You can segment it into subnets, control traffic with Network Security Groups (NSGs), and connect your on-premises network to Azure through VPN Gateway or Azure ExpressRoute, which provides a private dedicated connection that doesn't go through the public internet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Azure Load Balancer&lt;/strong&gt; distributes traffic across multiple instances to ensure availability. &lt;strong&gt;Azure Application Gateway&lt;/strong&gt; does something similar at the application layer and includes a Web Application Firewall. &lt;strong&gt;Azure CDN&lt;/strong&gt; brings static content closer to users by distributing it across points of presence around the world.&lt;/p&gt;

&lt;h3&gt;
  
  
  Storage
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Azure Blob Storage&lt;/strong&gt; is Azure's object storage service, designed for unstructured data like files, images, videos, and backups. It has three access tiers based on how frequently data is needed: Hot for frequent access, Cool for data accessed less than once a month, and Archive for data that's rarely retrieved and where hours of retrieval latency is acceptable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Azure Files&lt;/strong&gt; provides a shared file system compatible with the SMB protocol and accessible from Windows, Linux, and macOS. &lt;strong&gt;Azure Queue Storage&lt;/strong&gt; is a messaging service for decoupling components of an application.&lt;/p&gt;

&lt;p&gt;On the redundancy side, LRS replicates data three times within a single datacenter, ZRS replicates across availability zones, GRS replicates to a secondary region, and GZRS combines zone redundancy with geo-replication.&lt;/p&gt;

&lt;h3&gt;
  
  
  Identity and access
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Microsoft Entra ID&lt;/strong&gt; (formerly Azure Active Directory) is Microsoft's cloud identity service. It manages authentication and authorization for Azure, Microsoft 365, and third-party applications.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Role-Based Access Control (RBAC)&lt;/strong&gt; lets you assign specific permissions to users, groups, or service identities over specific resources, following the principle of least privilege. The most common built-in roles are Owner, Contributor, and Reader.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Multi-Factor Authentication (MFA)&lt;/strong&gt; adds a second verification layer beyond the password. &lt;strong&gt;Conditional Access&lt;/strong&gt; lets you define policies that evaluate the context of an access attempt, such as location, device, or application, before allowing or blocking it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Management and governance
&lt;/h2&gt;

&lt;p&gt;The third area covers how to manage, monitor, and control costs in Azure. It's the one most people underestimate during study and the one that carries real weight in the actual exam.&lt;/p&gt;

&lt;h3&gt;
  
  
  Management tools
&lt;/h3&gt;

&lt;p&gt;The &lt;strong&gt;Azure Portal&lt;/strong&gt; is the main web interface for creating and managing resources. &lt;strong&gt;Azure CLI&lt;/strong&gt; and &lt;strong&gt;Azure PowerShell&lt;/strong&gt; are the command-line alternatives for those who prefer automation or scripting. &lt;strong&gt;Azure Cloud Shell&lt;/strong&gt; is a terminal available directly from the browser, with Azure CLI and PowerShell already configured, with no local installation needed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Azure Resource Manager (ARM)&lt;/strong&gt; is the layer that processes all resource creation, modification, and deletion requests, whether they come from the portal, the CLI, or an API. ARM templates and Bicep allow you to define infrastructure as code and deploy it repeatably.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Azure Arc&lt;/strong&gt; extends Azure's management capabilities to resources running outside of Azure, like servers in other clouds or in on-premises datacenters.&lt;/p&gt;

&lt;h3&gt;
  
  
  Costs
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Azure Cost Management&lt;/strong&gt; lets you see your detailed spending, set budgets, and configure automatic alerts when consumption approaches a defined limit. The &lt;strong&gt;Azure Pricing Calculator&lt;/strong&gt; helps estimate the cost of an architecture before deploying anything. The &lt;strong&gt;Total Cost of Ownership (TCO) Calculator&lt;/strong&gt; helps compare the cost of maintaining on-premises infrastructure against migrating to the cloud.&lt;/p&gt;

&lt;p&gt;Pay-as-you-go lets you pay for what you use with no prior commitments. &lt;strong&gt;Azure Reservations&lt;/strong&gt; let you reserve specific capacity for one or three years in exchange for discounts of up to 72% compared to the pay-as-you-go price.&lt;/p&gt;

&lt;h3&gt;
  
  
  Governance
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Azure Policy&lt;/strong&gt; lets you define and enforce rules on resources within a subscription, for example requiring that all new resources have mandatory tags, or restricting which VM sizes can be created. &lt;strong&gt;Resource Locks&lt;/strong&gt; protect critical resources from accidental deletion or modification, even for accounts with administrator-level permissions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Monitoring
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Azure Monitor&lt;/strong&gt; is the platform's central monitoring service. It collects metrics and logs from all your resources and lets you create alerts, dashboards, and analysis workbooks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Application Insights&lt;/strong&gt; is an Azure Monitor extension built specifically for applications. It tracks response times, exceptions, dependencies, and user behavior in real time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Azure Service Health&lt;/strong&gt; reports on the current status of Azure services in each region and on any incidents or planned maintenance that might affect your resources.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Microsoft Defender for Cloud&lt;/strong&gt; assesses the security posture of your resources and generates concrete recommendations for reducing risk.&lt;/p&gt;




&lt;h2&gt;
  
  
  How to prepare
&lt;/h2&gt;

&lt;p&gt;Study time varies depending on your background. Without any prior tech experience, two to four weeks of consistent study is realistic. With a technical foundation already in place, under two weeks is very doable.&lt;/p&gt;

&lt;p&gt;Microsoft Learn has the official learning path completely free, with interactive modules and sandboxes where you can practice directly in Azure without needing your own subscription or a credit card. Microsoft also offers a free official practice assessment that lets you get familiar with the real exam format before the day you sit for it.&lt;/p&gt;

&lt;p&gt;The free Azure account includes $200 in credit to use within your first 30 days, plus permanent access to a set of always-free services. It's useful for experimenting beyond Learn's sandboxes.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://learn.microsoft.com/plans/odp3ugt4okmdxk?source=docs&amp;amp;sharingId=D34D672B88CE3FAC&amp;amp;wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;study plan I put together&lt;/a&gt; organizes the Microsoft Learn modules in the order I'd recommend working through them, grouped by exam area. You can complete each module at your own pace and track your progress as you go.&lt;/p&gt;




&lt;h2&gt;
  
  
  To close
&lt;/h2&gt;

&lt;p&gt;Once you pass the AZ-900, the natural next steps on the technical Azure path are the AZ-104 (Administrator) or the AZ-204 (Developer). On the AI side, the AI-900 is the equivalent introductory cert before moving to the AI-102 or AI-103.&lt;/p&gt;

&lt;p&gt;If you'd like to study alongside others, on October 15 we're organizing an event where we'll go through the most important exam topics together. Leave a comment or reach out directly if you're interested.&lt;/p&gt;

&lt;p&gt;The study plan is already there: &lt;a href="https://learn.microsoft.com/plans/odp3ugt4okmdxk?source=docs&amp;amp;sharingId=D34D672B88CE3FAC&amp;amp;wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;AZ-900 Study Plan on Microsoft Learn&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>azure</category>
      <category>cloud</category>
      <category>learning</category>
      <category>microsoft</category>
    </item>
    <item>
      <title>Azure Multicloud Interconnect, how Azure and AWS now connect without a middleman</title>
      <dc:creator>Carlos José Castro Galante</dc:creator>
      <pubDate>Tue, 22 Sep 2026 15:26:57 +0000</pubDate>
      <link>https://dev.to/carlosjcastrog/azure-multicloud-interconnect-how-azure-and-aws-now-connect-without-a-middleman-16jc</link>
      <guid>https://dev.to/carlosjcastrog/azure-multicloud-interconnect-how-azure-and-aws-now-connect-without-a-middleman-16jc</guid>
      <description>&lt;p&gt;A private connection between an Azure VNet and an AWS VPC has traditionally required assembling Azure ExpressRoute, AWS Direct Connect, a connectivity provider or colocation footprint, customer-managed routers, BGP sessions, and link-layer encryption, then owning the resiliency design and day-to-day operations across all of it. The result was typically slow to deliver, difficult to troubleshoot, and inconsistent in performance.&lt;/p&gt;

&lt;p&gt;On August 31, 2026, Microsoft and AWS jointly announced Azure Multicloud Interconnect, a co-engineered managed service that replaces that assembly with a single interconnect resource, turning the private path between clouds into something you provision rather than something you build.&lt;/p&gt;

&lt;p&gt;For the official overview and creation guide: &lt;a href="https://learn.microsoft.com/azure/multicloud-interconnect/overview?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;What is Azure Multicloud Interconnect?&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What the service actually is
&lt;/h2&gt;

&lt;p&gt;Azure Multicloud Interconnect isn't a rebranding of ExpressRoute with a different interface but rather a new resource type that sits on top of ExpressRoute's infrastructure and abstracts everything on both sides of the connection. Under the covers, Microsoft and AWS coordinate the circuits, routing, and encryption on your behalf using the open API specification for network interoperability that AWS introduced at re:Invent 2025 and brought to general availability in April 2026. Microsoft adopted that specification for Azure.&lt;/p&gt;

&lt;p&gt;The physical design is quad-redundant with a 400G-class backbone and MACsec encryption at the hardware layer, which means the encryption happens at the link level without consuming host CPU resources on either end. Without any of that configuration on your part, you create an interconnect resource, exchange an activation key with the AWS side, and the managed path between your Azure Virtual Network and your AWS VPC is ready.&lt;/p&gt;

&lt;p&gt;The distinction from ExpressRoute comes down to what you're managing: with a traditional ExpressRoute circuit connected to AWS through a carrier, you're the systems integrator for the full stack, while with Azure Multicloud Interconnect, Microsoft and AWS own the underlying complexity and you consume the outcome.&lt;/p&gt;

&lt;p&gt;For a detailed look at how the service fits into the existing connectivity landscape: &lt;a href="https://learn.microsoft.com/azure/multicloud-interconnect/faq?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;Azure Multicloud Interconnect FAQ&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How provisioning works
&lt;/h2&gt;

&lt;p&gt;The setup process moves through four stages. The first is creating an ExpressRoute circuit and selecting the Azure Multicloud Interconnect port type rather than a carrier-based port. The second is creating the Interconnect resource itself, which generates the activation key that coordinates the connection between providers. The third is provider setup on the AWS side, where you redeem the activation key and select the Direct Connect Gateway that the connection terminates on. The fourth is connecting your Azure Virtual Networks through an ExpressRoute gateway so that workloads in your Azure VNet and workloads in your AWS VPC can communicate over the managed path.&lt;/p&gt;

&lt;p&gt;Both sides must use separate, non-overlapping IP address spaces, which is a standard requirement for any BGP-routed private connection.&lt;/p&gt;

&lt;p&gt;On the AWS side, the experience mirrors what AWS already provides for Google Cloud and Oracle Cloud Infrastructure through AWS Interconnect, so teams that have already connected AWS to another cloud provider using that service will find the workflow familiar.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the preview limits mean in practice
&lt;/h2&gt;

&lt;p&gt;The Azure preview launched with constraints worth understanding before building production plans on top of it.&lt;/p&gt;

&lt;p&gt;Throughput during preview is capped at 1 Gbps, while the Google Cloud pairing, which reached general availability in April 2026, already supports up to 100 Gbps. AWS confirmed that general availability for the Azure connection is expected to reach the same ceiling eventually, but the timeline isn't published. For workloads that move large volumes of data between clouds, whether for AI training pipelines, database replication, or large-scale analytics, 1 Gbps is a meaningful constraint. For control-plane traffic, API calls between microservices split across providers, or proof-of-concept deployments, it's sufficient.&lt;/p&gt;

&lt;p&gt;There's no SLA during preview, which is standard for preview services but matters for teams evaluating the service for anything that needs an uptime commitment from the provider.&lt;/p&gt;

&lt;p&gt;Regional availability in preview covers four Azure regions: Australia East, East US, Germany West Central, and West US, each paired with a corresponding AWS region: Asia Pacific (Sydney), US East (N. Virginia), Europe (Frankfurt), and US West (N. California). Cross-region connectivity isn't supported during preview, meaning an Azure VNet in East US connects to the AWS US East (N. Virginia) pair and can't route through a different region pair.&lt;/p&gt;

&lt;p&gt;The official source for current supported regions, bandwidth tiers, and SLA status: &lt;a href="https://azure.microsoft.com/products/multicloud-interconnect/?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;Azure Multicloud Interconnect product page&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How it differs from ExpressRoute
&lt;/h2&gt;

&lt;p&gt;The FAQ for the service makes the distinction explicit: choose Azure Multicloud Interconnect for managed connectivity between Azure and a supported cloud service provider, and choose ExpressRoute for on-premises, colocation, carrier-based connectivity, or Microsoft peering scenarios. The two aren't alternatives for the same use case because they're designed for different connectivity targets that happen to share the same underlying technology.&lt;/p&gt;

&lt;p&gt;An important implication is that existing ExpressRoute circuits don't automatically become Multicloud Interconnect resources, so if you're already connecting Azure and AWS through an ExpressRoute circuit over a carrier, the new service requires provisioning a new resource with the Azure Multicloud Interconnect port type, which is a separate circuit from what you may already be paying for.&lt;/p&gt;

&lt;h2&gt;
  
  
  The broader context
&lt;/h2&gt;

&lt;p&gt;Azure Multicloud Interconnect launched as part of a pattern AWS established with the open interoperability specification it published at re:Invent 2025. Google Cloud was the first provider to adopt the specification and reached general availability with AWS in April 2026. Oracle Cloud Infrastructure entered preview in May and reached general availability in July, and Azure followed into preview in August.&lt;/p&gt;

&lt;p&gt;That sequence matters for teams comparing the maturity of each pairing. The Google Cloud and OCI connections through AWS Interconnect have already moved through preview and into production-grade service with published SLAs and higher bandwidth tiers. The Azure pairing is earlier in that lifecycle, but the underlying specification is the same and the GA trajectory follows the same path the other pairings already completed.&lt;/p&gt;

&lt;p&gt;For the full technical announcement from the Azure Networking team: &lt;a href="https://techcommunity.microsoft.com/blog/azurenetworkingblog/simpler-private-connectivity-between-azure-and-aws-with-azure-multicloud-interco/4550556?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;Simpler, private connectivity between Azure and AWS with Azure Multicloud Interconnect&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/azure/multicloud-interconnect/overview?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;What is Azure Multicloud Interconnect? - Microsoft Learn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/azure/multicloud-interconnect/faq?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;Azure Multicloud Interconnect FAQ - Microsoft Learn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://azure.microsoft.com/products/multicloud-interconnect/?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;Azure Multicloud Interconnect product page - Azure&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://azure.microsoft.com/blog/introducing-azure-multicloud-interconnect-for-aws/?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;Introducing Azure Multicloud Interconnect for AWS - Azure Blog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://techcommunity.microsoft.com/blog/azurenetworkingblog/simpler-private-connectivity-between-azure-and-aws-with-azure-multicloud-interco/4550556?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;Simpler, private connectivity between Azure and AWS - Azure Networking Blog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/azure/expressroute/expressroute-introduction?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;Azure ExpressRoute overview - Microsoft Learn&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Information based on official Microsoft and AWS documentation as of September 2026. Azure Multicloud Interconnect is in public preview and service capabilities including supported regions, bandwidth tiers, and SLA status are subject to change. Always verify current status at Microsoft Learn before building production architectures on preview features.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>azure</category>
      <category>aws</category>
      <category>cloud</category>
      <category>networking</category>
    </item>
    <item>
      <title>Azure Developer CLI, from zero to deployed in three commands</title>
      <dc:creator>Carlos José Castro Galante</dc:creator>
      <pubDate>Fri, 18 Sep 2026 16:00:00 +0000</pubDate>
      <link>https://dev.to/carlosjcastrog/azure-developer-cli-from-zero-to-deployed-in-three-commands-549m</link>
      <guid>https://dev.to/carlosjcastrog/azure-developer-cli-from-zero-to-deployed-in-three-commands-549m</guid>
      <description>&lt;p&gt;Deploying an application to Azure by hand means provisioning resources in the portal, noting their connection strings, configuring environment variables, writing a deployment script, setting up a CI/CD pipeline, and repeating most of that process every time you start a new project or add a new environment. The Azure Developer CLI, known as azd, replaces that sequence with a small set of commands that handle provisioning, deployment, and environment management together instead of as separate concerns you have to coordinate manually.&lt;/p&gt;

&lt;p&gt;This post covers what azd actually does, how to use it with a Next.js project, the configuration file that makes everything work, the commands you will use daily, and the significant updates that shipped in 2026, including multi-language hooks in April, the azd tool command group in May, and the extension framework reaching general availability in August.&lt;/p&gt;

&lt;p&gt;For the official overview and installation guide: &lt;a href="https://learn.microsoft.com/azure/developer/azure-developer-cli/overview?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;Azure Developer CLI overview on Microsoft Learn&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What azd does differently from the Azure CLI
&lt;/h2&gt;

&lt;p&gt;The Azure CLI (&lt;code&gt;az&lt;/code&gt;) is a general-purpose interface to Azure's API that gives you a command for every Azure operation without an opinion about how your project is structured or how to coordinate provisioning and deployment together. azd is opinionated. It assumes your project has infrastructure code, application code, and one or more environments, and it provides commands that work across all three at once.&lt;/p&gt;

&lt;p&gt;The workflow azd enables is: write your application, define your infrastructure with Bicep or Terraform, describe the mapping between them in &lt;code&gt;azure.yaml&lt;/code&gt;, and then use three commands for everything else. &lt;code&gt;azd init&lt;/code&gt; to set up the project, &lt;code&gt;azd provision&lt;/code&gt; to create Azure resources, and &lt;code&gt;azd deploy&lt;/code&gt; to push your code. &lt;code&gt;azd up&lt;/code&gt; does the last two in a single step. &lt;code&gt;azd down&lt;/code&gt; tears everything down when you're done.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# macOS&lt;/span&gt;
brew tap azure/azd &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; brew &lt;span class="nb"&gt;install &lt;/span&gt;azd

&lt;span class="c"&gt;# Windows (winget)&lt;/span&gt;
winget &lt;span class="nb"&gt;install &lt;/span&gt;microsoft.azd

&lt;span class="c"&gt;# Linux&lt;/span&gt;
curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://aka.ms/install-azd.sh | bash

&lt;span class="c"&gt;# Verify&lt;/span&gt;
azd version
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The current version as of August 2026 is 1.32.0. azd updates frequently with monthly releases, so running &lt;code&gt;azd version&lt;/code&gt; after installation is worth doing to confirm you have a current build.&lt;/p&gt;

&lt;p&gt;To install azd for the first time or update your installation: &lt;a href="https://learn.microsoft.com/azure/developer/azure-developer-cli/install-azd?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;Install or update the Azure Developer CLI&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The azure.yaml file
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;azure.yaml&lt;/code&gt; file at the root of your project is what azd reads to understand how your application maps to Azure. A typical file for a Next.js app looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-nextjs-app&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-nextjs-app@0.0.1-beta&lt;/span&gt;

&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;web&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;project&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
    &lt;span class="na"&gt;language&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;js&lt;/span&gt;
    &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;appservice&lt;/span&gt;

&lt;span class="na"&gt;hooks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;preprovision&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;shell&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sh&lt;/span&gt;
    &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./scripts/setup-env.sh&lt;/span&gt;
  &lt;span class="na"&gt;postdeploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;shell&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sh&lt;/span&gt;
    &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./scripts/verify-deployment.sh&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;services&lt;/code&gt; section maps each part of your application to an Azure host. The &lt;code&gt;hooks&lt;/code&gt; section runs scripts at specific points in the provisioning and deployment lifecycle. Starting in April 2026, hooks support Python, JavaScript, TypeScript, and .NET alongside the existing Bash and PowerShell options, with automatic dependency management so you don't have to install anything separately.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;hooks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;preprovision&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;shell&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pwsh&lt;/span&gt;
    &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./scripts/preflight.ps1&lt;/span&gt;
  &lt;span class="na"&gt;postprovision&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;language&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;python&lt;/span&gt;
    &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./scripts/seed-database.py&lt;/span&gt;
  &lt;span class="na"&gt;predeploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;language&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;typescript&lt;/span&gt;
    &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./scripts/build-check.ts&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the complete azure.yaml schema reference: &lt;a href="https://learn.microsoft.com/azure/developer/azure-developer-cli/azd-schema?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;azure.yaml schema documentation&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Core commands
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;azd init&lt;/code&gt; sets up a new project either from a template in the gallery or from your existing codebase:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Initialize from a template&lt;/span&gt;
azd init &lt;span class="nt"&gt;--template&lt;/span&gt; todo-nodejs-mongo

&lt;span class="c"&gt;# Initialize from the current directory&lt;/span&gt;
azd init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;azd provision&lt;/code&gt; creates the Azure resources defined in your Bicep or Terraform files without deploying any application code, which is useful when you want to inspect or validate infrastructure separately from your deployment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;azd provision

&lt;span class="c"&gt;# Override subscription and location without changing config&lt;/span&gt;
azd provision &lt;span class="nt"&gt;--subscription&lt;/span&gt; your-subscription-id &lt;span class="nt"&gt;--location&lt;/span&gt; eastus
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;--subscription&lt;/code&gt; and &lt;code&gt;--location&lt;/code&gt; flags arrived in February 2026 and make it straightforward to provision to different targets per environment without modifying &lt;code&gt;azure.yaml&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;azd deploy&lt;/code&gt; pushes your application code to the already-provisioned resources:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;azd deploy

&lt;span class="c"&gt;# Deploy a specific service only&lt;/span&gt;
azd deploy &lt;span class="nt"&gt;--service&lt;/span&gt; web
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;azd up&lt;/code&gt; runs provision and deploy together, which is what you will use most of the time, and &lt;code&gt;azd down&lt;/code&gt; deletes all resources associated with the current environment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;azd up

azd down

&lt;span class="c"&gt;# Delete without confirmation prompt&lt;/span&gt;
azd down &lt;span class="nt"&gt;--force&lt;/span&gt; &lt;span class="nt"&gt;--purge&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Environment management
&lt;/h2&gt;

&lt;p&gt;azd manages environments as named collections of configuration values that persist between commands, where each environment stores the Azure subscription, location, and any variables your infrastructure needs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Create environments for different stages&lt;/span&gt;
azd &lt;span class="nb"&gt;env &lt;/span&gt;new staging
azd &lt;span class="nb"&gt;env &lt;/span&gt;new production

&lt;span class="c"&gt;# Switch between environments&lt;/span&gt;
azd &lt;span class="nb"&gt;env &lt;/span&gt;&lt;span class="k"&gt;select &lt;/span&gt;staging
azd up

azd &lt;span class="nb"&gt;env &lt;/span&gt;&lt;span class="k"&gt;select &lt;/span&gt;production
azd up

&lt;span class="c"&gt;# See all environments and their active status&lt;/span&gt;
azd &lt;span class="nb"&gt;env &lt;/span&gt;list

&lt;span class="c"&gt;# Get all values from the active environment&lt;/span&gt;
azd &lt;span class="nb"&gt;env &lt;/span&gt;get-values
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each environment maps to a separate set of Azure resources, so staging and production never share infrastructure even when they come from the same template.&lt;/p&gt;

&lt;h2&gt;
  
  
  The azd tool command group
&lt;/h2&gt;

&lt;p&gt;One of the more useful additions that shipped in May 2026 is &lt;code&gt;azd tool&lt;/code&gt;, a unified command group for discovering, installing, and managing Azure development tools directly from the CLI without hunting for separate install documentation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# List all available tools with their install status and version&lt;/span&gt;
azd tool list

&lt;span class="c"&gt;# Show details for a specific tool&lt;/span&gt;
azd tool show azure-functions-core-tools

&lt;span class="c"&gt;# Install a tool&lt;/span&gt;
azd tool &lt;span class="nb"&gt;install &lt;/span&gt;azure-functions-core-tools

&lt;span class="c"&gt;# Update a tool&lt;/span&gt;
azd tool update azure-functions-core-tools

&lt;span class="c"&gt;# Uninstall with a dry run first to see what would be removed&lt;/span&gt;
azd tool uninstall azure-functions-core-tools &lt;span class="nt"&gt;--dry-run&lt;/span&gt;
azd tool uninstall azure-functions-core-tools
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This replaced the pattern of going to separate documentation pages for each tool and running different package manager commands depending on the operating system. The August 2026 release added guidance for bulk removal and per-agent skill output.&lt;/p&gt;

&lt;h2&gt;
  
  
  The extension framework
&lt;/h2&gt;

&lt;p&gt;The azd extension framework reached general availability in August 2026 and extensions can now be installed directly from an HTTPS URL without registering a source first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install an extension from the official registry&lt;/span&gt;
azd extension &lt;span class="nb"&gt;install &lt;/span&gt;microsoft.azd.extensions.foundry

&lt;span class="c"&gt;# Install directly from a URL&lt;/span&gt;
azd extension &lt;span class="nb"&gt;install &lt;/span&gt;https://your-server.com/my-extension.tar.gz

&lt;span class="c"&gt;# List installed extensions&lt;/span&gt;
azd extension list
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Microsoft ships two extensions worth knowing. The azd AI agent extension for Microsoft Foundry integrates azd workflows with Foundry projects, and the GitHub Copilot coding agent configuration extension sets up the configuration Copilot needs to understand and work with your azd project. Both are available through the official extension registry.&lt;/p&gt;

&lt;h2&gt;
  
  
  CI/CD pipeline integration
&lt;/h2&gt;

&lt;p&gt;azd can configure a GitHub Actions or Azure DevOps pipeline for your project with a single command that creates the workflow files, sets up the necessary federated credentials, and configures the repository secrets azd needs to run in CI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;azd pipeline config
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Starting in August 2026, federated authentication support for Azure DevOps is documented and supported, replacing the older approach that required managing client secrets manually.&lt;/p&gt;

&lt;p&gt;For CI/CD pipeline setup: &lt;a href="https://learn.microsoft.com/azure/developer/azure-developer-cli/configure-devops-pipeline?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;Explore Azure Developer CLI support for CI/CD pipelines&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Templates
&lt;/h2&gt;

&lt;p&gt;The template gallery is the fastest way to get a complete azd-ready project structure without writing Bicep from scratch, where each template includes the &lt;code&gt;azure.yaml&lt;/code&gt;, the infrastructure definitions, and often the application scaffolding:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Browse and select a template interactively&lt;/span&gt;
azd init

&lt;span class="c"&gt;# Initialize directly from a known template&lt;/span&gt;
azd init &lt;span class="nt"&gt;--template&lt;/span&gt; azure-samples/todo-python-mongo-swa-func
azd init &lt;span class="nt"&gt;--template&lt;/span&gt; azure-samples/azure-search-openai-demo
azd init &lt;span class="nt"&gt;--template&lt;/span&gt; azure-samples/chat-with-your-data-solution-accelerator
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The AI App Templates gallery is the relevant starting point for anything that involves Azure OpenAI, Azure AI Search, or Microsoft Foundry: &lt;a href="https://learn.microsoft.com/azure/developer/azure-developer-cli/azd-templates?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;azd templates and the template gallery&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/azure/developer/azure-developer-cli/overview?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;Azure Developer CLI overview - Microsoft Learn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/azure/developer/azure-developer-cli/install-azd?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;Install or update the Azure Developer CLI - Microsoft Learn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/azure/developer/azure-developer-cli/azd-schema?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;azure.yaml schema reference - Microsoft Learn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/azure/developer/azure-developer-cli/azd-templates?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;azd templates and the template gallery - Microsoft Learn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/azure/developer/azure-developer-cli/configure-devops-pipeline?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;CI/CD pipeline support with azd - Microsoft Learn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://devblogs.microsoft.com/azure-sdk/azure-developer-cli-azd-august-2026/?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;Azure Developer CLI August 2026 release - Azure SDK Blog&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Information based on official Microsoft documentation and verified sources as of September 2026. azd ships monthly releases and some commands or flags may have changed. Always verify current behavior at Microsoft Learn before implementing in production workflows.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>azure</category>
      <category>cloud</category>
      <category>typescript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Foundry IQ, the knowledge layer that stops every agent from rebuilding its own RAG</title>
      <dc:creator>Carlos José Castro Galante</dc:creator>
      <pubDate>Tue, 15 Sep 2026 16:30:00 +0000</pubDate>
      <link>https://dev.to/carlosjcastrog/foundry-iq-the-knowledge-layer-that-stops-every-agent-from-rebuilding-its-own-rag-1h46</link>
      <guid>https://dev.to/carlosjcastrog/foundry-iq-the-knowledge-layer-that-stops-every-agent-from-rebuilding-its-own-rag-1h46</guid>
      <description>&lt;p&gt;Every team that builds more than one AI agent eventually hits the same problem. The first agent gets its own retrieval pipeline with data connectors, chunking logic, embeddings, routing, and permission enforcement. The second agent needs to answer from the same documents, so the pipeline gets duplicated. By the third agent, there's a tangle of fragmented, siloed pipelines that are expensive to maintain and inconsistent in quality, with each one rebuilding the same foundation from scratch.&lt;/p&gt;

&lt;p&gt;Foundry IQ is Microsoft's answer to that problem. Rather than adding more tools to the pipeline, it moves the retrieval layer out of each agent and into a shared, managed knowledge layer that multiple agents can query through a single API. The agents stop owning retrieval; they delegate it.&lt;/p&gt;

&lt;p&gt;Before going further, it's worth clarifying a naming detail that causes confusion. Microsoft ships three distinct IQ products under the Microsoft Foundry umbrella. &lt;strong&gt;Foundry IQ&lt;/strong&gt; handles enterprise documents and knowledge sources, which is what this article is about. &lt;strong&gt;Work IQ&lt;/strong&gt; is a contextual intelligence layer for Microsoft 365 that captures signals from documents, meetings, chats, and workflows. &lt;strong&gt;Fabric IQ&lt;/strong&gt; models business data in OneLake and Power BI so agents can reason over analytics. They're separate products with different semantics, freshness characteristics, and authorization boundaries. A policy assistant may need only Foundry IQ, while a sales-planning agent might combine Work IQ, Fabric IQ, and web context. This article stays focused on Foundry IQ.&lt;/p&gt;

&lt;p&gt;For the official overview of what Foundry IQ is and how it fits into Microsoft Foundry, the Microsoft Learn documentation is the authoritative source: &lt;a href="https://learn.microsoft.com/azure/foundry/agents/concepts/what-is-foundry-iq?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;What is Foundry IQ?&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What Foundry IQ is built on
&lt;/h2&gt;

&lt;p&gt;Foundry IQ is built directly on Azure AI Search. This is the part most introductions skip over, and it matters for architecture decisions. A Foundry IQ knowledge base is not a separate service sitting next to Azure AI Search. It's a top-level object that lives on your Azure AI Search service and orchestrates agentic retrieval. When you create a knowledge base, you're creating an object in Azure AI Search that defines which knowledge sources to query and how retrieval behaves at query time.&lt;/p&gt;

&lt;p&gt;The practical consequence is that you don't choose between Foundry IQ and Azure AI Search. Foundry IQ is the new interface for building agents on top of Azure AI Search, wrapping the underlying search capabilities in a managed, permission-aware, multi-agent-ready layer.&lt;/p&gt;

&lt;p&gt;To explore the agentic retrieval capabilities that underpin knowledge bases, the Azure AI Search documentation covers the model in detail: &lt;a href="https://learn.microsoft.com/azure/search/agentic-retrieval-how-to-create-knowledge-base?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;Create a knowledge base in Azure AI Search&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Knowledge sources and knowledge bases
&lt;/h2&gt;

&lt;p&gt;Two concepts are foundational to how Foundry IQ is structured. A &lt;strong&gt;knowledge source&lt;/strong&gt; is a connection to data, whether that's an Azure Blob Storage container, a SharePoint site, an OneLake lakehouse, an existing Azure AI Search index, or a web source accessed through the public internet or an MCP server. A &lt;strong&gt;knowledge base&lt;/strong&gt; bundles one or more knowledge sources, up to ten per knowledge base, plus the parameters that control retrieval behavior. The knowledge base is what agents actually query.&lt;/p&gt;

&lt;p&gt;The distinction matters because multiple agents can share the same knowledge base. An HR assistant and a policy assistant might both need access to the same document library. With a traditional RAG approach, each agent builds its own pipeline to that library. With Foundry IQ, both agents point at the same knowledge base and the retrieval layer handles routing, permissions, and answer synthesis once.&lt;/p&gt;

&lt;p&gt;Knowledge sources come in two classes. Indexed sources are ingested into a search index on your Azure AI Search service, where the content is chunked, vectorized, and kept fresh through an indexer schedule. This class includes existing Azure AI Search indexes, Azure Blob Storage containers, and OneLake lakehouses, and these are generally available in the &lt;code&gt;2026-04-01&lt;/code&gt; REST API. Federated sources are queried at runtime without pre-ingestion, including web sources and MCP servers, and these remain in preview in the &lt;code&gt;2026-05-01-preview&lt;/code&gt; and &lt;code&gt;2026-08-01-preview&lt;/code&gt; APIs.&lt;/p&gt;

&lt;h2&gt;
  
  
  GA versus preview: what this means for production
&lt;/h2&gt;

&lt;p&gt;Some of the most useful Foundry IQ features are still in preview, and the distinction matters when deciding what to build on. The &lt;code&gt;2026-04-01&lt;/code&gt; REST API provides a production-endorsed path with general availability for knowledge bases and core indexed knowledge sources. Features that remain in preview as of the time of writing include answer synthesis using an LLM for query planning and response generation, configurable reasoning effort tiers, document-level permissions, multi-turn conversational retrieval, and most of the portal-based management experience. The Azure portal and Microsoft Foundry portal continue to provide preview-only access to all agentic retrieval features regardless of which API version you use.&lt;/p&gt;

&lt;p&gt;For a proof-of-concept or a hackathon project, the preview features are usable and the free tier of Azure AI Search is sufficient to get started. For a production deployment with SLA requirements, it's worth reviewing which specific capabilities you need against the GA boundary before committing to an architecture.&lt;/p&gt;

&lt;p&gt;The product page for Foundry IQ on Azure has a current overview of generally available capabilities: &lt;a href="https://azure.microsoft.com/products/ai-foundry/iq/?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;Foundry IQ on Azure&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating a knowledge base programmatically
&lt;/h2&gt;

&lt;p&gt;The Python SDK for working with Foundry IQ knowledge bases uses &lt;code&gt;azure-search-documents&lt;/code&gt; version &lt;code&gt;2026-05-01-preview&lt;/code&gt; or later for the knowledge base surface, combined with &lt;code&gt;azure-ai-projects&lt;/code&gt; version &lt;code&gt;2.0.0&lt;/code&gt; or later for the agent integration. Both are available on PyPI with no additional package feeds.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;azure.identity&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;DefaultAzureCredential&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;azure.search.documents.indexes&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SearchIndexClient&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;azure.search.documents.indexes.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;KnowledgeBase&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;KnowledgeSource&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;SearchIndexKnowledgeSource&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;credential&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;DefaultAzureCredential&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;search_endpoint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://your-search-service.search.windows.net&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="n"&gt;index_client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SearchIndexClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;search_endpoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;credential&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;credential&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;api_version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2026-05-01-preview&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;knowledge_source&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;KnowledgeSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;company-docs-source&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;SearchIndexKnowledgeSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;index_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;company-documents&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;knowledge_base&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;KnowledgeBase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;company-docs-kb&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Internal company documentation for agent grounding&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;knowledge_sources&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;knowledge_source&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;index_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_or_update_knowledge_base&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;knowledge_base&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Knowledge base created: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a knowledge base that wraps an existing Azure AI Search index. The knowledge base inherits the permissions model of the underlying search service, so access control lists configured on the index are enforced at query time without additional configuration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Connecting an agent to Foundry IQ
&lt;/h2&gt;

&lt;p&gt;Once the knowledge base exists, connecting a Foundry Agent to it requires declaring the knowledge base as an MCP tool in the agent configuration. The Foundry IQ MCP endpoint is exposed per knowledge base and any MCP-compatible agent can use it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;azure.ai.projects&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AIProjectClient&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;azure.ai.projects.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;McpTool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;AgentMcpToolDefinition&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;project_client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AIProjectClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://your-foundry-project.services.ai.azure.com/api/projects/your-project&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;credential&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;DefaultAzureCredential&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;mcp_tool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;McpTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;connection_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;your-search-service-connection-id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;tool_names&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;knowledge_base_retrieve&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;knowledge_base_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;company-docs-kb&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;project_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;agents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-4.1-mini&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;document-assistant&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;instructions&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
        You are an assistant that answers questions about company policy.
        When answering, always use the knowledge base to retrieve relevant information.
        You must never answer from your own training knowledge when company-specific
        information is available in the knowledge base.
        Always cite the source documents in your responses.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;AgentMcpToolDefinition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mcp&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;mcp_tool&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Agent created: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;knowledge_base_retrieve&lt;/code&gt; tool is what allows the agent to query the knowledge base through agentic retrieval. When the agent receives a question, it uses this tool to plan queries, run parallel searches across the configured knowledge sources, and aggregate results with citations before generating a response.&lt;/p&gt;

&lt;h2&gt;
  
  
  How agentic retrieval works
&lt;/h2&gt;

&lt;p&gt;The retrieval process that happens when an agent queries a knowledge base is more than a keyword or vector search. The agentic retrieval engine analyzes the incoming query, decomposes it into sub-queries when necessary, searches the configured knowledge sources in parallel using a combination of keyword, vector, and hybrid search depending on the source type, enforces user permissions at query time by checking access control lists and Microsoft Purview sensitivity labels, and returns extractive content with citations that trace each answer fragment back to its source document.&lt;/p&gt;

&lt;p&gt;The reasoning effort applied to this process is configurable. Lower effort tiers use fewer tokens and respond faster at the cost of retrieval quality for complex multi-part questions. Higher effort tiers apply more query planning and source selection logic but consume more tokens per request. The appropriate tier depends on the agent's use case and the complexity of the questions it needs to answer.&lt;/p&gt;

&lt;p&gt;For production agents, the permission enforcement behavior is particularly relevant. Foundry IQ synchronizes access control lists for supported knowledge sources and enforces them at query time, which means the agent never returns content that the querying user doesn't have permission to see, even if that content exists in the knowledge base. This is handled at the knowledge layer rather than requiring the application to implement permission filtering separately.&lt;/p&gt;

&lt;p&gt;The full guide to connecting agents to Foundry IQ knowledge bases, including the current API version requirements and role assignments, is in Microsoft Learn: &lt;a href="https://learn.microsoft.com/azure/foundry/agents/how-to/foundry-iq-connect?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;Connect Agents to Foundry IQ Knowledge Bases&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Querying the knowledge base directly
&lt;/h2&gt;

&lt;p&gt;Outside of a Foundry Agent context, knowledge bases can also be queried directly through the Azure AI Search REST API or SDK. This is useful for building custom application layers that need grounded retrieval without using Foundry Agent Service.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;azure.search.documents&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SearchClient&lt;/span&gt;

&lt;span class="n"&gt;search_client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SearchClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;search_endpoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;index_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;company-documents&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;credential&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;credential&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;api_version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2026-05-01-preview&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;search_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;knowledge_base_retrieve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;knowledge_base_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;company-docs-kb&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;What is the expense reimbursement policy for travel?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;effort&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;standard&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;top&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Source: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;metadata_source&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Content: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Score: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;@search.score&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;---&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;effort&lt;/code&gt; parameter controls the reasoning tier applied to the query. Available values depend on which API preview version is being used, with &lt;code&gt;standard&lt;/code&gt; being the baseline tier available across versions.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to use Foundry IQ versus building custom RAG
&lt;/h2&gt;

&lt;p&gt;Foundry IQ makes most sense when the same document corpus needs to serve multiple agents, when permission enforcement at the knowledge layer is a requirement, when the team doesn't want to own the chunking, embedding, and indexer infrastructure, or when the project is already built on Microsoft Foundry and Azure AI Search.&lt;/p&gt;

&lt;p&gt;A custom RAG implementation built directly on Azure AI Search makes more sense when the retrieval logic needs behavior that Foundry IQ doesn't expose as a configurable parameter, when the knowledge base abstraction adds more friction than it removes for a single-agent use case, or when specific preview features needed for the project might change in ways that would break a production deployment before reaching GA.&lt;/p&gt;

&lt;p&gt;The two approaches aren't mutually exclusive. The underlying Azure AI Search service is the same in both cases, which means migrating from a custom search index to a Foundry IQ knowledge base that wraps the same index is a configuration change rather than a data migration.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/azure/foundry/agents/concepts/what-is-foundry-iq?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;What is Foundry IQ? - Microsoft Learn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://azure.microsoft.com/products/ai-foundry/iq/?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;Foundry IQ product page - Azure&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/azure/foundry/agents/how-to/foundry-iq-connect?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;Connect Agents to Foundry IQ Knowledge Bases - Microsoft Learn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/azure/search/agentic-retrieval-how-to-create-knowledge-base?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;Create a knowledge base in Azure AI Search - Microsoft Learn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/training/modules/introduction-foundry-iq/?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;Build knowledge-enhanced AI agents with Foundry IQ - Microsoft Learn Training&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://devblogs.microsoft.com/foundry/build-smarter-agents-faster-with-foundry-iq/?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;Build smarter agents faster with Foundry IQ - Microsoft Foundry Blog&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Information based on official Microsoft documentation and verified sources as of September 2026. Foundry IQ features are actively evolving and some capabilities described may move from preview to general availability or change behavior between API versions. Always verify current status at Microsoft Learn before building production systems.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>azure</category>
      <category>ai</category>
      <category>webdev</category>
      <category>cloud</category>
    </item>
    <item>
      <title>How to authenticate Azure apps without storing credentials</title>
      <dc:creator>Carlos José Castro Galante</dc:creator>
      <pubDate>Sat, 05 Sep 2026 18:22:22 +0000</pubDate>
      <link>https://dev.to/carlosjcastrog/why-azure-managed-identity-replaces-stored-credentials-and-how-to-use-it-in-2026-29cj</link>
      <guid>https://dev.to/carlosjcastrog/why-azure-managed-identity-replaces-stored-credentials-and-how-to-use-it-in-2026-29cj</guid>
      <description>&lt;p&gt;Every Azure project eventually has the same conversation. Where do we store the connection string? Someone suggests an environment variable, and someone else points out that environment variables end up in deployment pipelines, in Docker compose files, in Terraform state, and occasionally in accidental commits. A secret manager gets proposed, and the secret manager needs its own credentials to access the secrets. The problem recurses.&lt;/p&gt;

&lt;p&gt;Managed Identity doesn't solve the secret manager problem by adding another layer. It removes the credential from the equation entirely for workloads running inside Azure, so the application doesn't authenticate with a stored credential but as itself, using an identity that Azure manages automatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Managed Identity actually does
&lt;/h2&gt;

&lt;p&gt;When you enable a Managed Identity on an Azure resource, Azure creates an identity in Microsoft Entra ID tied to that resource's lifecycle. The resource can then request short-lived tokens from the Azure Instance Metadata Service endpoint at &lt;code&gt;169.254.169.254&lt;/code&gt;, which is only reachable from within Azure infrastructure, and those tokens are what the resource uses to authenticate against other Azure services. There's nothing to store, nothing to rotate manually, and nothing that can be leaked in a repository because the credential never exists as a static string anywhere in your codebase or configuration.&lt;/p&gt;

&lt;p&gt;The key property from Microsoft's documentation is precise: managed identities give code running on an Azure resource access to other resources without developers needing to handle or put credentials directly into code. The emphasis on "code running on an Azure resource" matters because Managed Identity only works from within Azure. A local development machine can't reach the Instance Metadata Service endpoint, which means the local development flow still needs an alternative authentication mechanism, typically &lt;code&gt;az login&lt;/code&gt; or a service principal configured for development only.&lt;/p&gt;

&lt;h2&gt;
  
  
  System-assigned vs user-assigned
&lt;/h2&gt;

&lt;p&gt;There are two types of Managed Identity, and Microsoft's current recommendation, updated in its official best practice documentation, is that user-assigned identities are more efficient in a broader range of scenarios.&lt;/p&gt;

&lt;p&gt;A system-assigned identity is created directly on a resource and its lifecycle is tied to that resource, so when the resource is deleted, the identity is deleted too. This sounds convenient but creates a management problem at scale: every resource gets its own identity, every identity needs its own role assignments, and if you have twenty App Services that all need read access to the same storage account, you end up managing twenty separate identities with twenty separate role assignments that have to stay synchronized.&lt;/p&gt;

&lt;p&gt;A user-assigned identity is created as a standalone resource in Azure and can be assigned to multiple resources simultaneously, with its lifecycle independent of any particular resource. If you delete the App Service, the identity persists. You can pre-define what a user-assigned identity can access, get it approved by whoever owns your access control policy, and then assign it to new resources as they're created without going through a new approval cycle each time.&lt;/p&gt;

&lt;p&gt;Microsoft's naming recommendation makes the operational difference clear: name identities after their permission set rather than after the consumer. An identity named &lt;code&gt;id-blogreader-prod-eastus&lt;/code&gt; outlives any specific workload and its purpose stays readable in audit logs, while an identity named &lt;code&gt;id-appsvc-01&lt;/code&gt; doesn't communicate what it can do and its permissions tend to drift over time as the team changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The security mistake most developers make
&lt;/h2&gt;

&lt;p&gt;Turning on Managed Identity and removing the hardcoded credential is the right first step, but stopping there is where most teams leave a significant gap.&lt;/p&gt;

&lt;p&gt;Managed identities do not make a workload secure. They make it credential-free, and that distinction matters because the identity still holds whatever permissions you've granted it, and those permissions are available to anything running on the resource the identity is assigned to. Microsoft's own documentation states this explicitly: if a user has access to install or execute code on a resource with a managed identity, that user has access to everything the identity can reach, even if they have no direct access to those target resources.&lt;/p&gt;

&lt;p&gt;This means that granting a Managed Identity broad permissions like Contributor on a storage account and then assigning it to a shared compute resource where multiple teams run code effectively elevates every team's access to that storage account. The credential problem is gone but the permission problem remains and is now implicit rather than visible in a secrets manager.&lt;/p&gt;

&lt;p&gt;The practical fix is applying least-privilege role assignments: instead of Contributor, assign Storage Blob Data Reader if the workload only reads blobs, and instead of Key Vault Administrator, assign Key Vault Secrets User if the workload only reads secrets. Azure RBAC has purpose-specific built-in roles for most common scenarios, and using them reduces the blast radius if a workload is ever compromised.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using DefaultAzureCredential
&lt;/h2&gt;

&lt;p&gt;The practical implementation for most teams is &lt;code&gt;DefaultAzureCredential&lt;/code&gt;, part of the Azure Identity SDK and available for Python, JavaScript, Java, and .NET. It tries a sequence of authentication methods in order and uses the first one that succeeds, which means the same code works in both local development and in Azure without any environment-specific branching.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;azure.identity&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;DefaultAzureCredential&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;azure.storage.blob&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BlobServiceClient&lt;/span&gt;

&lt;span class="n"&gt;credential&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;DefaultAzureCredential&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BlobServiceClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;account_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://mystorageaccount.blob.core.windows.net&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;credential&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;credential&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In a local environment, &lt;code&gt;DefaultAzureCredential&lt;/code&gt; typically picks up the credentials from &lt;code&gt;az login&lt;/code&gt;, and on an Azure resource with Managed Identity enabled it picks up the managed identity token from the Instance Metadata Service. The same line of code handles both cases.&lt;/p&gt;

&lt;p&gt;The order of credential resolution matters when debugging authentication issues. &lt;code&gt;DefaultAzureCredential&lt;/code&gt; tries EnvironmentCredential, WorkloadIdentityCredential, ManagedIdentityCredential, SharedTokenCacheCredential, VisualStudioCodeCredential, AzureCliCredential, AzurePowerShellCredential, and AzureDeveloperCliCredential in that sequence. If you're seeing unexpected authentication behavior in local development, it's usually because an earlier credential in the chain is being picked up unexpectedly, typically an environment variable in the shell that's overriding the &lt;code&gt;az login&lt;/code&gt; credential.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;DefaultAzureCredential&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@azure/identity&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;SecretClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@azure/keyvault-secrets&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;credential&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;DefaultAzureCredential&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SecretClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://mykeyvault.vault.azure.net&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;credential&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;secret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getSecret&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;my-database-connection-string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Where Managed Identity doesn't work
&lt;/h2&gt;

&lt;p&gt;Two scenarios where a service principal with a client secret or certificate is still the right choice: workloads running outside Azure that can't reach the Instance Metadata Service, and federated scenarios where the workload needs to authenticate to Azure from a non-Azure environment like GitHub Actions or another cloud provider. For GitHub Actions specifically, Microsoft supports OIDC-based federation that eliminates the stored secret problem without requiring the workload to run in Azure.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 2026 shift toward identity-first
&lt;/h2&gt;

&lt;p&gt;Azure's direction in 2026 is increasingly identity-first by default, with system-assigned managed identities now the default for Kubernetes workloads on AKS, replacing API keys in that context. The pattern is becoming a baseline expectation rather than an advanced configuration, and for new projects starting on Azure, configuring Managed Identity from the beginning is significantly easier than retrofitting it into an existing codebase that passes credentials around as strings.&lt;/p&gt;

&lt;p&gt;For the full Microsoft documentation on Managed Identity and how to configure it for specific Azure services:&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/overview?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;Managed identities for Azure resources - Overview&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/managed-identity-best-practice-recommendations?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;Best practice recommendations for managed identities&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/overview-for-developers?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;Managed identities for Azure resources - Developer introduction and guidelines&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/azure/developer/python/sdk/authentication/credential-chains?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;Use DefaultAzureCredential in an application&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/azure/app-service/overview-managed-identity?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;Managed identity for Azure App Service&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/azure/security/fundamentals/identity-management-best-practices?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;Azure identity management and access control best practices&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Information based on official Microsoft documentation and verified sources as of September 2026. Azure services and recommendations may change. Verify current guidance at Microsoft Learn before implementing in production environments.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>azure</category>
      <category>security</category>
      <category>cloud</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How to activate Azure for Students without a university email in 2026</title>
      <dc:creator>Carlos José Castro Galante</dc:creator>
      <pubDate>Wed, 02 Sep 2026 14:07:32 +0000</pubDate>
      <link>https://dev.to/carlosjcastrog/how-to-activate-azure-for-students-without-a-university-email-in-2026-5d98</link>
      <guid>https://dev.to/carlosjcastrog/how-to-activate-azure-for-students-without-a-university-email-in-2026-5d98</guid>
      <description>&lt;p&gt;Azure for Students gives enrolled university students $100 in Azure credits, access to free services, and no credit card requirement. The standard path to activate it assumes your university email domain is registered in Microsoft's verification system, and for many institutions, especially in regions outside North America and Europe, it simply isn't. When you enter your university email and get the message "your email domain is not currently registered with us", that's not a rejection. It's the system telling you to use a different verification method.&lt;/p&gt;

&lt;p&gt;There are two paths that work when your institutional domain isn't recognized: verifying with official academic documentation, or using the GitHub Student Developer Pack as proof of student status. Both are supported by Microsoft and SheerID, the verification provider behind Azure for Students.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you need before starting either path
&lt;/h2&gt;

&lt;p&gt;Regardless of which path you take, you need a Microsoft account. If you already have an Outlook or Hotmail account, that works. If you don't, create one at account.microsoft.com before anything else. This is the account that will hold your Azure for Students subscription and the same one you will use later if you apply to the Microsoft Student Ambassadors program, so use one you plan to keep active.&lt;/p&gt;

&lt;p&gt;Your Microsoft account does not need to be linked to your university email. A personal Gmail cannot be a Microsoft account on its own, but you can create a Microsoft account using a Gmail address at signup.live.com. Either way, what matters is that you have a stable Microsoft account ready before starting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Path 1: Documentation-based verification
&lt;/h2&gt;

&lt;p&gt;This path works when you don't have access to a recognized institutional email and you're not yet verified on GitHub Education. It requires uploading an official document that proves your current enrollment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Go to the Azure for Students page&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Navigate to &lt;code&gt;azure.microsoft.com/free/students&lt;/code&gt; and click the "Activate now" button. Sign in with your Microsoft account when prompted.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2: Enter your information&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The form will ask for your email address. Enter your Microsoft account email, not your university email. When the system cannot verify the domain automatically, it will present an alternative verification option where you can submit academic documentation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3: Upload your enrollment document&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The document that works is an official enrollment certificate or student registration certificate issued by your university. It needs to show your full name, your institution's name, and confirmation of your current enrollment status. A student ID card alone is generally not sufficient because it doesn't always show the enrollment year or current academic status. An official letter or certificate from the registrar's office is the correct document.&lt;/p&gt;

&lt;p&gt;In my case I uploaded a constancia de alumno regular, which is the standard enrollment certificate issued by Argentine universities, and the verification was accepted without issues.&lt;/p&gt;

&lt;p&gt;The verification is processed by SheerID. In most cases it completes within minutes, but it can take up to 48 hours. If it hasn't resolved after 48 hours, you can contact Azure Education Support at &lt;code&gt;azureforeducation.microsoft.com/institutions/contact&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 4: Confirm activation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Once verified, you'll receive a confirmation email and your Azure for Students subscription will appear in the Azure portal at &lt;code&gt;portal.azure.com&lt;/code&gt;. The subscription includes $100 in credits valid for 12 months and access to a set of always-free services.&lt;/p&gt;

&lt;h2&gt;
  
  
  Path 2: GitHub Student Developer Pack
&lt;/h2&gt;

&lt;p&gt;This path works if you already have or plan to get the GitHub Student Developer Pack. GitHub Education accepts enrollment documents and student ID cards for verification, and once approved, the Pack can be used to activate Azure for Students directly without repeating the documentation process.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Apply for the GitHub Student Developer Pack&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Go to &lt;code&gt;education.github.com/pack&lt;/code&gt; and click "Get student benefits". Sign in with your GitHub account and follow the verification flow. You'll need to provide your university name, your enrollment email if you have one, and upload a proof of enrollment document if your email isn't recognized. A student ID, enrollment certificate, or official letter from your university all work here. GitHub's verification usually takes up to 72 hours.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2: Wait for confirmation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;GitHub sends a confirmation email when your student status is verified. The Pack and all its benefits become active at that point. Don't try to activate Azure before receiving that confirmation because the verification link won't work until GitHub completes the process on their end.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3: Activate Azure for Students through the Pack&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Once verified, go to &lt;code&gt;signup.azure.com/studentverification&lt;/code&gt; and sign in with your Microsoft account. When the page asks how you want to verify your student status, select the option to connect with GitHub and complete the GitHub OAuth step. This confirms to Microsoft that you're a verified student through GitHub Education.&lt;/p&gt;

&lt;p&gt;After completing the OAuth step, wait a few minutes and then open the Azure portal at &lt;code&gt;portal.azure.com&lt;/code&gt;. Your Azure for Students subscription should appear active in the subscriptions list. If it doesn't appear after 10 minutes, try signing out and back in.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you get an error saying your GitHub account doesn't have student benefits enabled&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This error can appear even when your Student Developer Pack is active and verified. The most reliable fix is to open a support ticket with GitHub Education by going to &lt;code&gt;support.github.com&lt;/code&gt; and selecting "I am a verified student having trouble redeeming my Global Campus offers." GitHub support typically resolves these within a few hours.&lt;/p&gt;

&lt;p&gt;Do not try to activate Azure multiple times before resolving this error. Azure for Students is limited to one activation per Microsoft account. If you trigger multiple failed attempts, the offer may become unavailable for that account and you'll need to contact Azure Education Support to reset it.&lt;/p&gt;

&lt;h2&gt;
  
  
  If both paths fail: manual support ticket
&lt;/h2&gt;

&lt;p&gt;Neither path above is guaranteed to work in every situation. The official Azure for Students documentation states that verification requires an institutional email address, and the document-upload option in the self-service flow is not available in all regions or for all institutions. If you complete both paths and still cannot activate the subscription, a manual support ticket is the next step.&lt;/p&gt;

&lt;p&gt;This process is not officially documented as a standard path, but multiple Microsoft Q&amp;amp;A threads from 2026 confirm that Microsoft's education support team can manually review student eligibility when the automated system fails. The outcome is not guaranteed, but it is the correct escalation channel.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to prepare before opening a ticket:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You will need clear scans or photos of your student ID card showing your name and current academic year, an official enrollment certificate or letter from your university's registrar office showing your full name, institution name, and current enrollment period, and a screenshot of the error or rejection message you received during the self-service verification flow. If you completed the GitHub Student Developer Pack, include a screenshot confirming your approved status there as well.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How to open the ticket:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you already have any Azure account (including a free account), go to &lt;code&gt;portal.azure.com&lt;/code&gt;, click "Help + Support" in the top right corner, and create a new support request. Select "Subscription Management" as the issue type, then "Account" and "Azure for Students verification" as the subcategories. Attach your documents and explain that your institution does not provide an institutional email address and that you are requesting manual verification.&lt;/p&gt;

&lt;p&gt;If you do not have any Azure account yet and cannot access the portal, use the Azure for Education contact form directly:&lt;/p&gt;

&lt;p&gt;👉 &lt;code&gt;azureforeducation.microsoft.com/institutions/contact&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;You can also post your situation with full details in the official Microsoft Q&amp;amp;A forum at &lt;code&gt;learn.microsoft.com/answers&lt;/code&gt;, where Microsoft moderators actively respond to student verification questions and can escalate your case internally.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Alternative if manual verification is not granted:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If Microsoft confirms that your institution is not supported and manual verification cannot be completed, the Azure for Students Starter program at &lt;code&gt;azure.microsoft.com/free/students/starter&lt;/code&gt; provides access to a limited set of free Azure services without requiring student verification or a credit card. It does not include the $100 credit, but it gives access to services like App Service, Azure Functions, and Azure SQL Database at no cost, which may be sufficient for learning purposes.&lt;/p&gt;




&lt;p&gt;Once your Azure for Students subscription is active, the Microsoft account holding it is the one you use for everything related to Microsoft programs. If you plan to apply to the Microsoft Student Ambassadors program, you'll register at &lt;code&gt;studentambassadors.microsoft.com&lt;/code&gt; using this same Microsoft account. The program verifies your Azure for Students subscription during registration, and the button to proceed only becomes active once that subscription is confirmed.&lt;/p&gt;

&lt;p&gt;To check your credit balance, remaining time, and the services included in your subscription, go to the Azure portal at &lt;code&gt;portal.azure.com&lt;/code&gt; and look for the &lt;strong&gt;Education&lt;/strong&gt; section in the left navigation menu. That is where Azure for Students accounts track their credits and usage, separately from the general Subscriptions section that paid accounts use. The $100 in credits is valid for 12 months from activation, and unused credits do not roll over, so it's important starting to explore Azure services once the subscription is active.&lt;/p&gt;

&lt;p&gt;For a full guide on the Microsoft Student Ambassadors program and how to get started after activating Azure for Students:&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://learn.microsoft.com/azure/education-hub/azure-dev-tools-teaching/azure-students-program?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;https://learn.microsoft.com/azure/education-hub/azure-dev-tools-teaching/azure-students-program?wt.mc_id=studentamb_510930&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;The information in this article is based on official Microsoft documentation, GitHub Education documentation, and personal experience as of September 2026. Verification processes, eligibility requirements, and available benefits may change at any time without notice. This article is provided for informational purposes only. The author is not responsible for any issues arising from following these steps, including but not limited to failed verifications, credit losses, or account restrictions. Always verify current requirements directly on the official Azure for Students page and the GitHub Student Developer Pack page before proceeding.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>azure</category>
      <category>microsoft</category>
      <category>cloud</category>
      <category>learning</category>
    </item>
    <item>
      <title>Azure OpenAI Service vs OpenAI API, which to use and when in 2026</title>
      <dc:creator>Carlos José Castro Galante</dc:creator>
      <pubDate>Wed, 26 Aug 2026 00:12:06 +0000</pubDate>
      <link>https://dev.to/carlosjcastrog/azure-openai-service-vs-openai-api-which-to-use-and-when-in-2026-1nic</link>
      <guid>https://dev.to/carlosjcastrog/azure-openai-service-vs-openai-api-which-to-use-and-when-in-2026-1nic</guid>
      <description>&lt;p&gt;When someone asks whether to use Azure OpenAI Service or the direct OpenAI API, the starting point is this: the models running on both platforms are identical. GPT-4o, GPT-5, and the o-series models you deploy on Azure have the same weights, the same capabilities, and the same output quality as the ones you call from platform.openai.com, and what changes between the two platforms is the infrastructure where they run, the authentication mechanism, and the compliance guarantees the provider can offer on those requests.&lt;/p&gt;

&lt;h2&gt;
  
  
  What changed in 2026
&lt;/h2&gt;

&lt;p&gt;Azure AI Foundry was renamed Microsoft Foundry on January 1, 2026, and Azure OpenAI Service now lives inside that unified platform alongside the model catalog, development tooling, and agents. References to Microsoft Foundry in new documentation point to what used to be Azure AI Foundry.&lt;/p&gt;

&lt;p&gt;In July 2026, the GPT-5.6 family arrived with Sol, Terra, and Luna available on Azure the same day as on the direct OpenAI API. Historically Azure lagged four to eight weeks behind new model releases because Microsoft validates them within their compliance frameworks before making them available, and while that gap still exists for some specific features and APIs, for the main models in the GPT-5 family availability is converging.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where data is processed
&lt;/h2&gt;

&lt;p&gt;When you call GPT-4o from the OpenAI API, the request goes to OpenAI's own infrastructure, which is centralized and gives you no control over which region processes your data. For most use cases that doesn't matter, but for organizations with data residency requirements, regulatory compliance needs, or industries like healthcare, banking, or government, that detail can determine whether the service is usable at all.&lt;/p&gt;

&lt;p&gt;Azure OpenAI runs the same models within the boundary of your Azure tenant, so the data you send in prompts doesn't leave to OpenAI's infrastructure but processes in the Azure regions you choose. That's what makes it possible to meet HIPAA, SOC 2, EU data residency, and other certifications that companies in regulated industries need before they can deploy to production.&lt;/p&gt;

&lt;h2&gt;
  
  
  Authentication
&lt;/h2&gt;

&lt;p&gt;The OpenAI API uses API keys, strings you need to store, rotate, distribute, and protect from ending up in a repository. Azure OpenAI can authenticate using &lt;code&gt;DefaultAzureCredential&lt;/code&gt;, which delegates authentication to Microsoft Entra ID and can use Managed Identity so the service obtains tokens automatically without any hardcoded or stored credentials anywhere.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;azure.identity&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;DefaultAzureCredential&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;openai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AzureOpenAI&lt;/span&gt;

&lt;span class="n"&gt;credential&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;DefaultAzureCredential&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;credential&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_token&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://cognitiveservices.azure.com/.default&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AzureOpenAI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;azure_endpoint&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://my-resource.openai.azure.com/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;azure_ad_token&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;api_version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2026-04-01-preview&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In environments where a security review is required before deploying anything to production, being able to remove API keys from the authentication flow carries practical weight in the approval process.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pricing and billing modes
&lt;/h2&gt;

&lt;p&gt;Prices for the main models are comparable between the two platforms. The GPT-5.6 family in Global Standard on Azure follows the same OpenAI list rates, with ranges from $0.20 to $5 per million input tokens for mainstream models in the current catalog. For the GPT-5.6 Sol model, Azure announced promotional pricing of $4.00 per million input tokens and $20.00 per million output tokens from September 1 through at least November 30, 2026.&lt;/p&gt;

&lt;p&gt;Azure has Provisioned Throughput Units (PTUs), reserved capacity blocks you pay for hourly rather than per token. When your application's sustained usage exceeds 60-70% of a PTU's capacity, that mode starts making economic sense. Below that threshold, pay-as-you-go per token is more efficient.&lt;/p&gt;

&lt;p&gt;Something most pricing guides don't mention is that in enterprise deployments with private networking, Azure adds supporting infrastructure costs including Azure AI Search, Blob Storage, private endpoints, and network egress that can add 15 to 40% on top of token costs in full private networking production deployments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Content filtering
&lt;/h2&gt;

&lt;p&gt;OpenAI has baseline moderation that runs on all requests. Azure OpenAI lets you configure custom filters through Azure AI Content Safety, with granular control over the thresholds for each content category based on your application's specific needs. For medical use cases where certain clinical terms might trigger general filters, that granularity can be necessary for the service to work correctly.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to use each one
&lt;/h2&gt;

&lt;p&gt;The direct OpenAI API makes sense when you're prototyping and need setup speed without bureaucracy, when you want immediate access to new features without waiting for Azure's validation cycle, or when you're building a consumer application where compliance requirements aren't a blocker. It has no quota gate on the standard tier, so you can start calling the API the same day without requesting additional capacity.&lt;/p&gt;

&lt;p&gt;Azure OpenAI makes sense when the project needs to meet specific regulations for the industry, when you need data to process within a specific Azure region, when the organization already has contracts and governance on Azure and needs the AI service integrated into that ecosystem, or when you need credential-free authentication through Managed Identity. Quota requests for high-demand models in specific regions can take a week or more to process, so starting that process early helps.&lt;/p&gt;

&lt;p&gt;A common and perfectly valid scenario is using both: you prototype with the OpenAI API which gives immediate access to everything, and when the project moves to production with compliance or private networking requirements, you migrate to Azure OpenAI with minimal code changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Migrating from one to the other
&lt;/h2&gt;

&lt;p&gt;If you already have code pointing to the OpenAI API and want to move it to Azure, the change is configuration rather than logic. The client changes from &lt;code&gt;OpenAI&lt;/code&gt; to &lt;code&gt;AzureOpenAI&lt;/code&gt;, you add your Azure resource endpoint and &lt;code&gt;api_version&lt;/code&gt;, and the rest of the code stays the same. Model names change because in Azure you deploy named instances rather than calling the model by its global name.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Before (direct OpenAI)
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;openai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OpenAI&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OpenAI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sk-...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# After (Azure OpenAI)
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;openai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AzureOpenAI&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AzureOpenAI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;azure_endpoint&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://my-resource.openai.azure.com/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;api_version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2026-04-01-preview&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With Managed Identity, the &lt;code&gt;api_key&lt;/code&gt; disappears from the code entirely and is replaced by the Entra ID credential flow shown above.&lt;/p&gt;

&lt;p&gt;To explore Azure OpenAI Service and the model catalog available in Microsoft Foundry:&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://azure.microsoft.com/products/ai-services/openai-service/?wt.mc_id=studentamb_510930" rel="noopener noreferrer"&gt;https://azure.microsoft.com/products/ai-services/openai-service/?wt.mc_id=studentamb_510930&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Information based on official Microsoft documentation and verified sources as of August 25, 2026. Prices and model availability may change. Verify current figures on the official Azure OpenAI pricing page before making architecture or budget decisions.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>azure</category>
      <category>ai</category>
      <category>machinelearning</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
