Sr. Content Developer at Microsoft, working remotely in PA, TechBash conference organizer, former Microsoft MVP, Husband, Dad and Geek.
160479 stories
·
33 followers

Azure Key Vault Control Plane API Update: Required Upgrade for Azure CLI and Azure PowerShell Users

1 Share

Deadline: February 27, 2027

All Azure Key Vault control plane (management) API versions released before 2026-02-01 will stop working on February 27, 2027.

This does NOT affect the data plane (getting/setting secrets, keys, and certificates) — only vault and access-control management operations are impacted.

TL;DR — What you need to do

  • Upgrade Azure CLI to 2.90.0 or later (run az upgrade).
  • Upgrade Az PowerShell's Az.KeyVault module to 6.7.0 or later, or upgrade the Az package to 16.3.0 or later.
  • Do this before February 27, 2027 to avoid any interruption to vault management operations.
  • No data plane changes are required — reading/writing secrets, keys, and certificates is unaffected.
  • For details, see Prepare for Key Vault API version 2026-02-01 and later.

 

What is changing

Current releases of Azure CLI 2.90.0 or later and Az PowerShell 16.3.0 or later (including Az.KeyVault 6.7.0 or later) already use Key Vault control plane API version 2026-02-01. Upgrade to these versions before February 27, 2027; after that date, earlier control plane API versions will no longer be served.

Who is affected

You are affected if you use either of the following:

  • Azure CLI scripts or automation that call az keyvault management commands (create, update, network-rule, access-policy, etc.).
  • Az PowerShell scripts that use the Az.KeyVault module's management cmdlets (New-AzKeyVault, Set-AzKeyVault, Update-AzKeyVault, etc.).

Minimum versions to upgrade to

Tool / SDK

Minimum version

How to upgrade

Azure CLI

2.90.0 or later

az upgrade

Az PowerShell (Az.KeyVault module)

Az.KeyVault 6.7.0 or later; or Az 16.3.0 or later

Update-Module Az -Force

 

Impact if you take no action

What breaks on and after February 27, 2027 if you have not upgraded

Key Vault management operations (create/update/delete vault, change network rules, change access policies or RBAC settings, etc.) issued with a pre-2026-02-01 API version will fail.

You’ll get the following error message: “Due to an RBAC security default, Key Vault's Resource Manager API versions older than 2026-02-01 will retire on 2027-04-01.”

Your vaults are NOT deleted and existing secrets/keys/certificates remain fully accessible via the data plane — only management (control plane) operations are blocked.

There is no way to opt out or request an extension once the retirement takes effect on February 27, 2027.

How to check your installed versions

Check your Azure CLI version

Run the following command:

   az version

Check the azure-cli value. If the installed version is earlier than 2.90.0, upgrade Azure CLI before February 27, 2027 by running az upgrade.

Check your Az PowerShell version

Run the following commands:

    Get-InstalledModule Az, Az.KeyVault | Select-Object Name, Version

If Az.KeyVault is earlier than 6.7.0, or the installed Az package is earlier than 16.3.0, upgrade before February 27, 2027. To upgrade the complete Az package, run:

    Update-Module Az -Force

Alternatively, to update only the Az.KeyVault module to the latest version, run:

    Update-Module Az.KeyVault -Force

Timeline

Date

Milestone

March 2026

Control plane API version 2026-02-01 generally available across public Azure regions, Azure Government, and Azure operated by 21Vianet.

Today → February 27, 2027

Preparation window. Upgrade to Azure CLI 2.90.0 or later, Az.KeyVault 6.7.0 or later, or Az 16.3.0 or later before February 27, 2027.

February 27, 2027

All control plane API versions released before 2026-02-01 are retired and stop being served.

 

Questions?

For any questions about Azure CLI or Azure PowerShell, please submit feedback through GitHub:

Read the whole story
alvinashcraft
just a second ago
reply
Pennsylvania, USA
Share this story
Delete

Decoding the New AI Lingo: Loops, Harnesses, Squads, and Hill Climbing

1 Share

In today’s episode, Cassidy, Marlene, and GPS tackle the ever-growing pile of new AI and agent terminology that's popped up seemingly overnight. They break down loop engineering (designing systems that prompt your agents instead of prompting them yourself), the Ralph loop that helped popularize it, and how squads and fleets let multiple agents split up planning, testing, and review work in parallel. They also dig into harness engineering (the scaffolding around a model that turns raw text generation into something useful), the newly coined “hill climbing,” and why forward deployed engineer might just be a solutions architect with better AI tooling. They wrap with a look at the spectrum between closed, open weight, and open source models, plus each host's open source pick of the week: Astro 7, the RAE API project, and Tau.

Links mentioned in the episode:

Loop Engineering by Addy Osmani
Ralph Wiggum as a "software engineer"
MCP
LangChain
Astro 7
RAE API
Tau

The GitHub Podcast is produced and edited by editaudio.


Hosted by Simplecast, an AdsWizz company. See pcm.adswizz.com for information about our collection and use of personal data for advertising.





Download audio: https://afp-920613-injected.calisto.simplecastaudio.com/98910087-00ff-4e95-acd0-a3da5b27f57f/episodes/4a915475-d195-451a-84e1-d466efda6749/audio/128/default.mp3?aid=rss_feed&awCollectionId=98910087-00ff-4e95-acd0-a3da5b27f57f&awEpisodeId=4a915475-d195-451a-84e1-d466efda6749&feed=ioCY0vfY
Read the whole story
alvinashcraft
17 seconds ago
reply
Pennsylvania, USA
Share this story
Delete

LoRA backdoor threat: How OpenShift AI mitigates the risk

1 Share

A researcher at a pharmaceutical company deploys a productivity assistant built on a well-regarded open-weight model. The base model is clean—it's been evaluated, its weights are public, its behavior is understood. A low-rank adaptation (LoRA) adapter is added to tune it for the lab's domain: it understands drug-discovery terminology, knows the team's workflows, and uses the right tools. The assistant is useful. Nobody looks too hard at the adapter file. Why would they?

The post LoRA backdoor threat: How OpenShift AI mitigates the risk appeared first on Red Hat Developer.

Read the whole story
alvinashcraft
31 seconds ago
reply
Pennsylvania, USA
Share this story
Delete

Getting started with RAG in ASP.NET Core

1 Share

This blog post is originally published on https://blog.elmah.io/getting-started-with-rag-in-asp-net-core/

Nowadays, everyone talks about AI. Building with AI, integrating AI into your application, and similar topics are hot topics. In today's post, I'll step into the .NET AI world by building a very simple Retrieval-Augmented Generation (RAG) implementation.

Getting started with RAG in ASP.NET Core

As it is our baby step, I intentionally designed a beginner-friendly introduction rather than a production-ready RAG implementation. We won't build a vector database or use embeddings yet. Instead, we'll use simple keyword matching to simulate the retrieval process and focus on understanding how the pieces of a RAG system fit together.

In future posts, we will climb the Everest of .NET AI, but for today sticking to the baby steps. Our idea is that instead of asking an AI model to answer a question using only what it already knows, we first retrieve relevant information from our own knowledge base and provide that information to the model as context.

What is Retrieval-Augmented Generation (RAG)?

RAG is an architecture for optimizing the performance of an artificial intelligence (AI) model by integrating it with external knowledge bases. RAG allows AI models to combine their powerful capabilities with a specific domain's or an organization's internal knowledge base to generate more relevant and accurate responses.

No matter how great an interior designer is, he cannot give you great design by just looking from the outside and not getting information about your home from inside. He needs to know what rooms look like and how they are placed. If you do not give him this information, he will make assumptions or give a very generic solution that sometimes proves wrong. A Large Language Model (LLM) works the same way, no matter how powerful Claude, ChatGPT, or any other model is, if you want it to work on your project, it needs its information. RAG basically combines AI's power with the organization's internal data to utilize it properly. Without the context, an AI model may rely on assumptions and generate information that may look credible but is not supported by your data. This is commonly known as an AI hallucination.

RAG helps AI propose concrete answers based on the data, otherwise, it can fall into guessing and lead to hallucinations.

RAG architecture
Image by AWS

So RAG works in 3 steps: Retrieve ➡️ Augment ➡️ Generate

We retrieve relevant information from our knowledge base, add it to the AI prompt as context, and then let the AI model generate an answer based on that information.

Starting with RAG in an ASP.NET Core API

To take our first baby step, let's design an API with a simple RAG architecture.

Step 1: Create Api project

dotnet new webapi -n RAGPlayground

Step 2: Create an input model

namespace RAGPlayground.Models;

public class QueryInp
{
    public string Question { get; set; } = string.Empty;
}

Step 3: Add a document store

The document store will represent our knowledge base, exposing a method called Search.

public interface IDocumentStore
{
    List<string> Search(string query);
}

Its implementation is as follows:

public class DocumentStore : IDocumentStore
{
    private readonly List<string> _documents =
    [
        "Minimal APIs simplify endpoint development in ASP.NET Core.",
        "Entity Framework Core streamlines database operations.",
        "Background services process long-running tasks efficiently.",
        "Dependency injection improves application maintainability."
    ];

    public List<string> Search(string query)
    {
        return _documents
            .Select(doc => new
            {
                Document = doc,
                Score = CalculateScore(doc, query)
            })
            .OrderByDescending(x => x.Score)
            .Where(x => x.Score > 0)
            .Take(3)
            .Select(x => x.Document)
            .ToList();
    }

    private int CalculateScore(string doc, string query)
    {
        var score = 0;

        var queryWords = query
            .ToLower()
            .Split(' ', StringSplitOptions.RemoveEmptyEntries);

        foreach (var word in queryWords)
        {
            if (doc.ToLower().Contains(word))
            {
                score++;
            }
        }

        return score;
    }
}

DocumentStore is a simple in-memory document repository that simulates the retrieval part of a RAG system. It stores a collection of documents and returns the most relevant ones based on keyword matching.

The _documents list contains the collection on which search is performed. Search involves four steps:

  • Calculate a relevance score for every document.
  • Sort documents from highest score to lowest.
  • Remove documents with a score of zero.
  • Return the top three matching documents.

The CalculateScore method converts the query string to lowercase before splitting it into individual words. Then it checks if each word appears in the document. The more query words found in the document, the higher the score becomes for that document.

Step 4: Create RAG layer

public interface IRagService
{
    Task<string> AskAsync(string question);
}

The service implementation.

using RAGPlayground.Contracts;

namespace RAGPlayground.Services;

public class RagService: IRagService
{
    private readonly IDocumentStore _documentStore;

    public RagService(IDocumentStore documentStore)
    {
        _documentStore = documentStore;
    }

    public async Task<string> AskAsync(string question)
    {
        var context = _documentStore.Search(question);

        if (context is null || !context.Any())
        {
            return "No relevant information found.";
        }
        var prompt = $"""
                      Based only on the context below, answer the user's question.

                      If the answer cannot be found in the context, say:
                      "I don't have enough information."

                      Context:
                      {context.First()}

                      Question:
                      {question}
                      """;

        return await Task.FromResult(prompt);
    }
}

The service layer is much simpler. It calls the underlying document store layer and returns the document with the highest score. The prompt variable concatenates context and question, mimicking the augmentation step of the RAG.

Step 5: Add a controller

using Microsoft.AspNetCore.Mvc;
using RAGPlayground.Contracts;
using RAGPlayground.Models;

namespace RAGPlayground.Controllers;

[ApiController]
[Route("api/[controller]")]
public class RagController : ControllerBase
{
    private readonly IRagService _ragService;

    public RagController(IRagService ragService)
    {
        _ragService = ragService;
    }

    [HttpPost("ask")]
    public async Task<IActionResult> Ask([FromBody] QueryInp input)
    {
        var answer = await _ragService.AskAsync(input.Question);
        return Ok(new { answer });
    }
}

Now, I am exposing everything to the user via an ask endpoint.

Step 6: Run the project

dotnet run

In Postman, we can test the ask endpoint:

Response

If a word is not found, a fitting answer is provided:

Not found

Step 7: Use simulation with an AI model

In production, you will use an AI such as OpenAI, Grok, Azure OpenAI, etc. In that case, the service layer method will include a contextual prompt. The AI client service is

public interface IAiClient
{
    Task<string> GenerateAsync(string prompt);
}

Its implementation is as follows:

public class AiClient : IAiClient
{
    public Task<string> GenerateAsync(string prompt)
    {
        // Call your actual AI provider here.
        // For example: OpenAI, Azure OpenAI, Claude, Grok, etc.

        throw new NotImplementedException(
            "Connect an AI provider here.");
    }
}

Dependency injection registration:

builder.Services.AddScoped<IAiClient, AiClient>();

The updated RagService will look like:

public class RagService: IRagService
{
    private readonly IDocumentStore _documentStore;
    private readonly IAiClient _aiClient;
    
    public RagService(IDocumentStore documentStore, IAiClient aiClient)
    {
        _documentStore = documentStore;
        _aiClient = aiClient;
    }

    public async Task<string> AskAsync(string question)
    {
        var context = _documentStore.Search(question);

        if (context is null || !context.Any())
        {
            return "No relevant information found.";
        }
        var prompt = $"""
                      Based only on the context below, answer the user's question.

                      If the answer cannot be found in the context, say:
                      "I don't have enough information."

                      Context:
                      {context.First()}

                      Question:
                      {question}
                      """;
        var answer = await _aiClient.GenerateAsync(prompt);

        return answer;
    }
}

The answer will be fetched via the AI client you are using. Finally, the answer is generated by an AI client, which we have abstracted for this article; in future posts, we will use a real AI client to go one step further. That last stage represents the generation part of the RAG.

What to consider while choosing RAG?

While RAG enhances the accuracy of AI models, you need to keep in mind a few points before going forward:

  • If the information your AI needs is very small and rarely changes, you can include it directly in the AI's system prompt instead of building a document search pipeline. Information like FAQs and product features is usually small enough to fit a few hundred tokens and send with every request. You don't need a RAG pipeline in such cases.
  • Structured questions such as how many customers onboarded last year or how much revenue our company generated are database queries rather than AI prompts. Consider function calling that lets the model query your APIs instead.
  • RAG is not good at answering questions that require analyzing an entire dataset, because RAG is designed to retrieve only the most relevant pieces, not every document. Don't use RAG for aggregation problems like finding this month's shopping trends or counting complaint categories.
  • A recommended approach to designing a RAG application is a layered approach, with prompt building, retrieval logic, and LLM integration residing in separate services.
  • To improve retrieval in a production-grade application, use embeddings instead of word matching. Once documents become embeddings, you need a vector database to store them.
  • Secure your API with JWT authentication and authorization to avoid information leaking to unauthorized users. Role-based Access Control (RBAC) is useful for restricting access to the application.
  • For a SaaS application, isolate tenants to better track and limit prompts for each client.

Conclusion

RAG empowers AI's accuracy by connecting it with system-specific knowledge. We saw how to use RAG architecture in an ASP.NET Core API using a basic knowledge base. For a real AI LLM integration, we will continue from here by implementing the AiClient class. Later, we list best practices to consider to use it effectively.

Code: https://github.com/elmahio-blog/RAGPlayground.git



Read the whole story
alvinashcraft
40 seconds ago
reply
Pennsylvania, USA
Share this story
Delete

Boost Your .NET Projects with Spargine: Simplify Thread Pool Execution with ThreadPoolHelper

1 Share
Spargine's ThreadPoolHelper simplifies concurrent execution in .NET applications by providing reusable APIs that manage thread pool operations. It supports single and batch operations, bounded concurrency, timeout management, cooperative cancellation, and non-throwing execution. By centralizing common patterns, ThreadPoolHelper enhances maintainability and reduces repetitive infrastructure code for developers.
Read the whole story
alvinashcraft
47 seconds ago
reply
Pennsylvania, USA
Share this story
Delete

Monitoring GitHub Copilot usage with the Aspire Dashboard

1 Share

When talking to developers I get wildly different opinions about how "good" GitHub Copilot actually was for them. Some love it, some find it slow, some weren't sure it was doing anything useful at all(just kidding). The problem: nobody had any data. We were talking about token usage, latency and tool calls purely from gut feeling.

GitHub Copilot can export all of that as OpenTelemetry traces, metrics and events. And the easiest way to look at it locally, without spinning up a cloud backend, is the Aspire Dashboard. Let's set that up.

Why do we need this?

Copilot isn't just autocomplete anymore. Every agent turn is a small orchestration: it calls a model, the model asks for tools, the tools run, the model answers. If you want to know where the time and the tokens go, you need to see that orchestration, not just the end result.

That's exactly what the Open Telemetry integration gives you. Every agent interaction produces a span tree:

invoke_agent copilot                           [~15s]
  ├── chat gpt-4o                              [~3s]  (LLM requests tool calls)
  ├── execute_tool readFile                    [~50ms]
  ├── execute_tool runCommand                  [~2s]
  ├── chat gpt-4o                              [~4s]  (LLM generates final response)
  └── (span ends)

Remark: Open Telemetry is off by default. No data is emitted, and the SDK isn't even loaded, until you explicitly enable it. Nothing to worry about on the privacy side unless you turn it on yourself.

Getting the Aspire Dashboard running

There are multiple ways to collect and visualize the Open Telemetry data. An easy way in is the Aspire Dashboard. It is a single container with a built-in OTLP endpoint and a trace viewer. No Azure subscription, no collector to configure. Run it with the Aspire CLI:

aspire dashboard run

Or, if you'd rather run the container image directly:

docker run --rm -d -p 18888:18888 -p 4318:18890 --name aspire-dashboard \
  mcr.microsoft.com/dotnet/aspire-dashboard:latest

That's it. Open http://localhost:18888 and you'll land on an empty Traces view, waiting for data.

Pointing Copilot Chat at the dashboard

Now tell Copilot Chat to actually emit telemetry. Open your VS Code settings.json and add:

{

  "github.copilot.chat.otel.enabled": true,
  "github.copilot.chat.otel.captureContent": true
}

enabled turns telemetry on. captureContent is optional — it makes Copilot include the actual prompt and response text in the spans, instead of just token counts and durations. Handy while you're exploring locally, less handy on a shared machine.

Reload the window (settings changes don't apply to an already-running one), then have a normal chat conversation with Copilot. Ask it to read a file, run a command, whatever you'd do anyway.

Tip: enabling the setting alone doesn't emit anything. You need to actually send a chat message, and OTel batches before sending, so give it 10-30 seconds after your chat turn before you go looking in the dashboard.

Reading the traces

Back in http://localhost:18888, go to Traces.

You should see a trace per agent turn, named invoke_agent copilot (or invoke_agent claude if you're running Claude agent sessions through the Copilot Chat extension). Expand one and you get the full breakdown: how long the model call took, which tools ran, how long each of those took.

This is where the "Copilot feels slow" complaints get an actual answer. Sometimes it's the model call. Sometimes it's a tool — a slow runCommand, an MCP server doing something expensive — hiding behind what looks like one long agent turn.

The spans carry the GenAI semantic-convention attributes, so the same data works with any OTel backend later:

  • gen_ai.usage.input_tokens / gen_ai.usage.output_tokens:  token spend per call
  • gen_ai.usage.cache_read.input_tokens: how much you're benefiting from prompt caching
  • gen_ai.response.model: which model actually answered (Copilot silently reroutes sometimes)
  • gen_ai.tool.name: which tool ran inside execute_tool spans

Remark: if you're running the Copilot CLI in a separate terminal session rather than the VS Code chat panel, those traces show up as independent root traces under the github-copilot service instead of copilot-chat. They're not linked to your chat panel traces, but they land in the same dashboard, so filter by service.name if you want to tell them apart.

Going beyond gRPC vs HTTP

The default exporter is otlp-http, which is what the config above uses. If you'd rather use gRPC:

{
  "github.copilot.chat.otel.enabled": true,
  "github.copilot.chat.otel.exporterType": "otlp-grpc",
  "github.copilot.chat.otel.otlpEndpoint": http://localhost:18888
}

Remark: the Copilot CLI terminal runtime only speaks otlp-http, even if you configure gRPC. The Aspire Dashboard happens to serve both protocols on the same port, so this works transparently — you don't need two separate setups for chat-panel and CLI traces.

From local dashboard to team dashboard

The Aspire Dashboard is great for "let me quickly see what just happened on my machine," but it doesn't persist data across restarts and it's not meant for a whole team. Once you want aggregated numbers (token spend per team, cache hit rate over a sprint,...) you're looking at forwarding the same OTLP stream to Application Insights, Grafana, or another long-term backend through an OTel Collector. 

Same settings, different endpoint. That's maybe a topic for another post.

For now: enable the setting, point it at a local Aspire Dashboard, and actually look at what your agent sessions are doing. Simple, useful, done.

More information

Read the whole story
alvinashcraft
57 seconds ago
reply
Pennsylvania, USA
Share this story
Delete
Next Page of Stories